diff --git a/Makefile b/Makefile index 68efd20..dd318e3 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \ CFLAGS += -I$(SYSEXITS) .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 build: # keep build/include until bindgen(1) has stdin support @@ -118,10 +118,13 @@ 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 build/o/libgetopt.rlib \ + build/o/libsysexits.rlib + $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ + --extern sysexits=build/o/libsysexits.rlib \ + -o $@ src/stris.rs .PHONY: strcmp strcmp: build/bin/strcmp @@ -130,7 +133,8 @@ build/bin/strcmp: src/strcmp.c build .PHONY: swab swab: build/bin/swab -build/bin/swab: src/swab.rs build build/o/libsysexits.rlib +build/bin/swab: src/swab.rs build build/o/libgetopt.rlib \ + build/o/libsysexits.rlib $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ --extern sysexits=build/o/libsysexits.rlib \ -o $@ src/swab.rs diff --git a/docs/str.1 b/docs/str.1 deleted file mode 100644 index ecf71ee..0000000 --- a/docs/str.1 +++ /dev/null @@ -1,58 +0,0 @@ -.\" Copyright (c) 2023–2024 DTB -.\" Copyright (c) 2023 Emma Tebibyte -.\" -.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, -.\" visit . - -.TH STR 1 - -.SH NAME - -str \(en test the character types of string arguments - -.SH SYNOPSIS - -str -.RB [ type ] -.RB [ string... ] - -.SH DESCRIPTION - -Str tests each character in an arbitrary quantity of string arguments against -the function of the same name within ctype(3). - -.SH DIAGNOSTICS - -Str exits successfully if all tests pass and unsuccessfully if a test failed. -.PP -Str will exit unsuccessfully if a string is empty, as none of its contents -passed the test. -.PP -Str will print a message to standard error and exit unsuccessfully if used -improperly. - -.SH DEPRECATED FEATURES - -Str used to have an "isvalue" type as an extension to ctype(3). This was -removed in favor of using strcmp(1) to compare strings against the empty string -(''). - -.SH BUGS - -There's no way of knowing which argument failed the test without re-testing -arguments individually. -.PP -If a character in a string isn't valid ASCII str will exit unsuccessfully. - -.SH AUTHOR - -Written by DTB . - -.SH COPYRIGHT - -Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later -. - -.SH SEE ALSO - -ctype(3p), strcmp(1), ascii(7) diff --git a/docs/stris.1 b/docs/stris.1 new file mode 100644 index 0000000..bd4288e --- /dev/null +++ b/docs/stris.1 @@ -0,0 +1,118 @@ +.\" Copyright (c) 2023–2024 DTB +.\" Copyright (c) 2023 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH STRIS 1 + +.SH NAME + +stris \(en test the character types of string arguments + +.SH SYNOPSIS + +stris +.RB ( -7bcdlu ) +.RB ( -i [ inclusions ]) +.RB [ strings... ] + +.SH DESCRIPTION + +Stris tests each rune in an arbitrary quantity of string arguments, ensuring +each meets any of the parameters specified in the program options. + +.SH OPTIONS + +.B -7 +.RS +Tests to see if runes are fit within seven bits; that is, that they are encoded +with ASCII. +.RE + +.B -b +.RS +Tests to see if runes are blank or "whitespace"; characters that do not print +but fill a predictable amount of space. +.RE + +.B -c +.RS +Tests to see if runes are control characters; characters that are not printing +or graphical. +.RE + +.B -d +.RS +Tests to see if runes are numeric. This test does not only allow the ASCII +digits but any numeric symbol. +.RE + +.B -i +.RS +Permits, in addition to the given specified parameters, all of the runes +supplied in its option argument. +.RE + +.B -l +.RS +Tests to see if runes are in their lower case. +.RE + +.B -u +.RS +Tests to see if runes are in their upper case, or capitalized. +.RE + +.SH DIAGNOSTICS + +Stris exits successfully if all runes in all given strings meet any of the +specified parameters, or if no parameters were specified but all given strings +were legibly encoded. It exits unsuccessfully if the previous is untrue, and if +invalid options were given, or if no strings were given, a usage synopsis will +be printed to the standard error. + +.SH BUGS + +There's no way of knowing which argument failed the test without re-testing +arguments individually. + +Some runes that can losslessly be encoded into ASCII from UTF-8 but in an +"overlong encoding", where the rune was encoded with unnecessary leading +zeroes causing it to span multiple bytes, won't be detected as ASCII. + +.SH EXAMPLES + +This is an sh(1p) snippet that checks to see if an environment variable is an +ASCII digit. + +.RS +.R stris -7 "$v" && stris -d "$v" && echo ASCII digit. +.RE + +This is an sh(1p) snippet that checks to see if an environment variable is a +hexadecimal number. + +.RS +.R stris -7 "$v" && stris -di ABCDEFabcdef "$v" && echo Hexadecimal number. +.RE + +.SH AUTHOR + +Written by DTB . + +.SH HISTORY + +Stris replaces the former str(1) which took the name of a function from +ctype(3) as its first argument and checked the following strings against it; +str(1) exited unsuccessfully when it encountered any non-ASCII runes and could +only have one parameter specified. + +.SH COPYRIGHT + +Copyright © 2023–2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +ascii(7), ctype(3p), strcmp(1) diff --git a/src/str.c b/src/str.c deleted file mode 100644 index ae03b1d..0000000 --- a/src/str.c +++ /dev/null @@ -1,75 +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) */ -#include /* EXIT_FAILURE */ -#include /* strcmp(3) */ -#include - -static 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 } -}; - -int main(int argc, char *argv[]){ - int ctype; - int i; - int r; - - if(argc >= 3){ - for(ctype = 0; ctype < (sizeof ctypes) / (sizeof *ctypes); - ++ctype) - if(strcmp(argv[1], ctypes[ctype].name) == 0) - goto pass; - } - - fprintf(stderr, "Usage: %s [type] [string...]\n", - argv[0] == NULL ? program_name : argv[0]); - - return EX_USAGE; - -pass: for(argv += 2, r = 1; *argv != NULL; ++argv) - for(i = 0; argv[0][i] != '\0'; ++i) - /* 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 && !ctypes[ctype].f(argv[0][i])) - return 1; - else - r = 0; - - return r; -} diff --git a/src/stris.rs b/src/stris.rs new file mode 100644 index 0000000..4336484 --- /dev/null +++ b/src/stris.rs @@ -0,0 +1,93 @@ +/* + * 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; +use getopt::{ Opt, Parser }; + +extern crate sysexits; +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 opts = Parser::new(&argv, "7bcdi:lu"); + let mut reqs = Reqs { + ascii: false, blank: false, cntrl: false, digit: false, lower: false, + upper: false, inuse: false, extra: String::new() + }; + + loop { + match opts.next() { + None => break, + Some(opt) => { + match opt { + Ok(Opt('7', None)) => reqs.ascii = true, + Ok(Opt('b', None)) => reqs.blank = true, + Ok(Opt('c', None)) => reqs.cntrl = true, + Ok(Opt('d', None)) => reqs.digit = true, + Ok(Opt('i', Some(arg))) => reqs.extra = arg, + Ok(Opt('l', None)) => reqs.lower = true, + Ok(Opt('u', None)) => reqs.upper = true, + _ => { return usage(&argv[0]); } + } + reqs.inuse = true; + } + } + } + + if argv.len() == opts.index() { + return usage(&argv[0]); + } + + drop(argv); + + if reqs.inuse { + for arg in args().skip(opts.index()) { + 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 +}