From cfef7aec1d7439f14cebb7d89cd10264b849b0dd Mon Sep 17 00:00:00 2001 From: DTB Date: Fri, 3 May 2024 21:03:30 -0600 Subject: [PATCH] stris(1) --- Makefile | 13 +++++- docs/stris.1 | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/stris.rs | 93 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 docs/stris.1 create mode 100644 src/stris.rs diff --git a/Makefile b/Makefile index 68efd20..b2e3a1a 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 str strcmp stris swab true build: # keep build/include until bindgen(1) has stdin support @@ -123,6 +123,14 @@ 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 build/bin/strcmp: src/strcmp.c build @@ -130,7 +138,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/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/stris.rs b/src/stris.rs new file mode 100644 index 0000000..60c7339 --- /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 { + 'args: 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 'args; + } else { + return ExitCode::FAILURE; + } + } + } + } + + ExitCode::SUCCESS +}