diff --git a/Makefile b/Makefile index 1bdd5c9..d547b3b 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ BIN = build/bin default: all test .PHONY: all -all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true +all: dj false fop hru intcmp mm npc rpn scrut strcmp stris swab true # keep build/include until bindgen(1) has stdin support # https://github.com/rust-lang/rust-bindgen/issues/2703 @@ -147,10 +147,10 @@ scrut: build/bin/scrut build/bin/scrut: src/scrut.c build $(CC) $(CFLAGS) -o $@ src/scrut.c -.PHONY: str -str: build/bin/str -build/bin/str: src/str.c build - $(CC) $(CFLAGS) -o $@ src/str.c +.PHONY: stris +stris: build/bin/stris +build/bin/stris: src/stris.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) -o $@ src/stris.rs .PHONY: strcmp strcmp: build/bin/strcmp diff --git a/docs/str.1 b/docs/str.1 deleted file mode 100644 index 1a4d8e4..0000000 --- a/docs/str.1 +++ /dev/null @@ -1,59 +0,0 @@ -.\" Copyright (c) 2023–2024 DTB -.\" Copyright (c) 2023–2024 Emma Tebibyte -.\" -.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, -.\" visit . -.\" -.TH STR 1 2024-06-17 "Harakit X.X.X" -.SH NAME -str \(en test string arguments -.\" -.SH SYNOPSIS - -str -.B type string... -.\" -.SH DESCRIPTION - -Test the character types of string arguments. - -The tests in this program are equivalent to the functions with the same names in -.BR ctype.h (0p) -and are the methods by which string arguments are tested. -.\" -.SH DIAGNOSTICS - -If all tests pass, the program will exit successfully. If any of the tests fail, -the program will exit unsuccessfully with an error code of 1. - -When invoked incorrectly, a debug message will be printed and the program will -exit with the appropriate -.BR sysexits.h (3) -error code. -.\" -.SH CAVEATS - -None of an empty string\(cqs contents pass any of the tests, so the program will -exit unsuccessfully if one is specified. - -There\(cqs no way of knowing which argument failed the test without re-testing -arguments individually. - -If a character in a string isn\(cqt valid ASCII, the program will exit -unsuccessfully. -.\" -.SH AUTHOR - -Written by DTB -.MT trinity@trinity.moe -.ME . -.\" -.SH COPYRIGHT - -Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later -. -.\" -.SH SEE ALSO -.BR ctype (3p), -.BR strcmp(1), -.BR ascii(7) diff --git a/docs/stris.1 b/docs/stris.1 new file mode 100644 index 0000000..a80d18f --- /dev/null +++ b/docs/stris.1 @@ -0,0 +1,104 @@ +.\" Copyright (c) 2023–2024 DTB +.\" Copyright (c) 2023–2024 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . +.\" +.TH STRIS 1 2024-10-22 "Harakit X.X.X" +.SH NAME +stris \(en test the character types of string arguments +.\" +.SH SYNOPSIS + +stris +.RB [ -7bcdlu ] +.RB [ -i\ inclusions ] +.RB strings... +.\" +.SH DESCRIPTION + +Test each character in any number of string arguments, ensuring each meets any +of the parameters specified by program options. +.\" +.SH OPTIONS + +.IP \fB-7\fP +Tests if the character encoding is ASCII. +.IP \fB-b\fP +Tests if characters are whitespace. +.IP \fB-c\fP +Tests if characters are control characters. +.IP \fB-d\fP +Tests if characters are numeric. +.IP \fB-i\fP \fIinclusions...\fP +In addition to specified options, also permits characters in +.IR inclusions . +.IP \fB-l\fP +Tests if characters are in lower case. +.IP \fB-u\fP +Tests if characters are in upper case. +.\" +.SH DIAGNOSTICS + +If no test cases pass for a character in +.IR strings , +the program will exit unsuccessfully. + +.SH CAVEATS + +If no options are specified, the program will exit successfully as long as the +input +.I strings +are legibly encoded. + +Neither which test failed nor which of the +.I strings +failed a test cannot be known without further invocations of the program. + +Characters that can be encoded losslessly into ASCII from UTF-8 but which +are in an \(lqoverlong encoding\(rq, where the character was encoded with +unnecessary leading zeroes causing it to span multiple bytes, won\(cqt be +detected as ASCII. +.\" +.SH EXAMPLES + +This is an +.BR sh (1p) +snippet which tests if an environment variable is an ASCII digit. + +.RS +stris -7 "$v" && stris -d "$v" && echo ASCII digit. +.RE + +This is an +.BR sh (1p) +snippet that tests if an environment variable is a hexadecimal number. + +.RS +stris -7 "$v" && stris -di ABCDEFabcdef "$v" && echo Hexadecimal number. +.RE +.\" +.SH AUTHOR + +Written by DTB +.MT trinity@trinity.moe +.ME . +.\" +.SH HISTORY + +This program replaces the former str(1) which took the name of a function from +.BR ctype (3p) +as its first argument and checked the following strings against it; +.BR str (1) +exited unsuccessfully when it encountered any non-ASCII characters and could +only have one parameter specified. +.\" +.SH COPYRIGHT + +Copyright \(co 2023\(en2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +. +.\" +.SH SEE ALSO +.BR ascii (7), +.BR ctype (3p), +.BR strcmp (1) diff --git a/src/str.c b/src/str.c deleted file mode 100644 index 9ed27c2..0000000 --- a/src/str.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2023 DTB - * Copyright (c) 2023 Marceline Cramer - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#include -#include /* NULL */ -#include /* fprintf(3), perror(3) */ -#include /* size_t, EXIT_FAILURE */ -#include /* strcmp(3) */ -#include /* EX_OSERR, EX_USAGE */ - -#ifdef __OpenBSD__ -# include /* pledge(2) */ -#endif - -char *program_name = "str"; - -static struct { - char *name; - int (*f)(int); -} ctypes[] = { - { "isalnum", isalnum }, - { "isalpha", isalpha }, - { "isblank", isblank }, - { "iscntrl", iscntrl }, - { "isdigit", isdigit }, - { "isxdigit", isxdigit }, - { "isgraph", isgraph }, - { "islower", islower }, - { "isprint", isprint }, - { "ispunct", ispunct }, - { "isspace", isspace }, - { "isupper", isupper }, - { NULL, NULL } /* marks end */ -}; - -static int -usage(char *argv0) { - (void)fprintf(stderr, "Usage: %s type string...\n", argv0); - - return EX_USAGE; -} - -int main(int argc, char *argv[]) { - size_t ctype; // selected from ctypes.h; index of ctype - int retval; // initially fail but becomes success on the first valid char - program_name = argv[0] == NULL ? program_name : argv[0]; - -#ifdef __OpenBSD__ - if (pledge("stdio", NULL) == -1) { - perror(program_name); - return EX_OSERR; - } -#endif - - if (argc < 3) { return usage(program_name); } - - for ( /* iterate ctypes */ - ctype = 0; - ctypes[ctype].f != NULL /* break at the end of ctypes */ - && strcmp(argv[1], ctypes[ctype].name) != 0; /* break at match */ - ++ctype - ); - - if (ctypes[ctype].f == NULL) { return usage(argv[0]); } - - /* iterate args */ - for (argv += 2, retval = EXIT_FAILURE; *argv != NULL; ++argv) { - for (size_t i = 0; argv[0][i] != '\0'; ++i) { /* iterate arg bytes */ - /* First checks if argv[0][i] is valid ASCII; ctypes(3) don't - * handle non-ASCII. This is bad. */ - if( - (unsigned char)argv[0][i] < 0x80 // argv[0][i] is ASCII, - && !ctypes[ctype].f(argv[0][i]) // so use ctypes(3) - ) { return EXIT_FAILURE; } - else { retval = EXIT_SUCCESS; } - } - } - - return retval; -} diff --git a/src/stris.rs b/src/stris.rs new file mode 100644 index 0000000..c9f1b22 --- /dev/null +++ b/src/stris.rs @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023–2024 DTB + * Copyright (c) 2023 Marceline Cramer + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +use std::{ + env::args, + process::ExitCode, +}; + +extern crate getopt; +extern crate sysexits; + +use getopt::GetOpt; +use sysexits::EX_USAGE; + +struct Reqs { + ascii: bool, blank: bool, cntrl: bool, digit: bool, lower: bool, + upper: bool, inuse: bool, extra: String +} + +fn usage(s: &str) -> ExitCode { + eprintln!("Usage: {} [-7bcdlu] [-i inclusions] [strings...]", s); + ExitCode::from(EX_USAGE as u8) +} + +fn main() -> ExitCode { + let argv = args().collect::>(); + let mut optind = 1; + let mut reqs = Reqs { + ascii: false, blank: false, cntrl: false, digit: false, lower: false, + upper: false, inuse: false, extra: String::default() + }; + + while let Some(opt) = argv.getopt("7bcdi:lu") { + match opt.opt() { + Ok("7") => reqs.ascii = true, + Ok("b") => reqs.blank = true, + Ok("c") => reqs.cntrl = true, + Ok("d") => reqs.digit = true, + Ok("i") => reqs.extra = opt.arg().unwrap(), + Ok("l") => reqs.lower = true, + Ok("u") => reqs.upper = true, + _ => { return usage(&argv[0]); } + } + optind = opt.ind(); + reqs.inuse = true; + } + + if argv.len() == optind { return usage(&argv[0]); } + + drop(argv); + + if reqs.inuse { + for arg in args().skip(optind) { + for c in arg.chars() { + if (reqs.ascii && c.is_ascii()) + || (reqs.blank && c.is_whitespace()) + || (reqs.cntrl && c.is_control()) + || (reqs.digit && c.is_numeric()) + || (reqs.lower && c.is_lowercase()) + || (reqs.upper && c.is_uppercase()) + || reqs.extra.contains(c) { + continue; + } else { + return ExitCode::FAILURE; + } + } + } + } + + ExitCode::SUCCESS +} diff --git a/tests/bonsai/str.mk b/tests/bonsai/stris.mk similarity index 51% rename from tests/bonsai/str.mk rename to tests/bonsai/stris.mk index 38ad9ca..7d93b6d 100755 --- a/tests/bonsai/str.mk +++ b/tests/bonsai/stris.mk @@ -7,14 +7,19 @@ .PRAGMA: command_comment -.PHONY: str_tests -str_tests: str_help str_isalpha +.PHONY: stris_tests +stris_tests: stris_help stris_7 stris_b -.PHONY: str_help -str_help: $(BIN)/str - ! $(BIN)/str -h +.PHONY: stris_help +stris_help: $(BIN)/stris + ! $(BIN)/stris -h -.PHONY: str_isalpha -str_isalpha: $(BIN)/str - $(BIN)/str isalpha c - ! $(BIN)/str isalpha 3 +.PHONY: stris_7 +stris_7: $(BIN)/stris + $(BIN)/stris -7 !1Aa' ' + ! $(BIN)/stris -7 今日は + +.PHONY: stris_b +stris_b: $(BIN)/stris + $(BIN)/stris -b "$(printf ' \t\v\r\n')" + ! $(BIN)/stris -b !1Aa