10 Commits

Author SHA1 Message Date
ee9d5be015 stris.1: makes consistent with other docs 2025-02-25 00:02:01 -07:00
DTB
28f6d2508f stris(1): fix silly mistakes 2024-08-28 21:36:32 -06:00
DTB
cbf5032a22 tests: bonsai/stris.mk: broaden stris(1) test coverage 2024-08-28 21:36:02 -06:00
DTB
7d5443dee6 stris(1): fix typo 2024-08-28 18:54:12 -06:00
DTB
f9c84179fe stris(1): use String::default instead of String::new 2024-08-28 18:52:23 -06:00
DTB
72c3ac5df0 stris(1): modernize code 2024-08-28 18:50:21 -06:00
DTB
1796e772fb Merge branch 'main' into stris 2024-08-27 21:29:40 -06:00
DTB
7c9f640ee1 stris(1): fix bug only checking the first rune of strings 2024-05-03 21:36:23 -06:00
DTB
3910c341bd remove str(1) 2024-05-03 21:04:10 -06:00
DTB
cfef7aec1d stris(1) 2024-05-03 21:03:30 -06:00
19 changed files with 247 additions and 232 deletions

View File

@@ -19,7 +19,7 @@ PREFIX ?= /usr/local
# for conditionally compiling OS features
OS != uname
OS_INCLUDE != test -e include/$(OS).mk && printf 'include/$(OS).mk\n' \
|| printf '/dev/null\n'
|| include/None.mk
# normalized prefix
PREFIX_N != dirname $(PREFIX)/.
@@ -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

View File

@@ -1,59 +0,0 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\"
.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
<https://gnu.org/licenses/gpl.html>.
.\"
.SH SEE ALSO
.BR ctype (3p),
.BR strcmp(1),
.BR ascii(7)

104
docs/stris.1 Normal file
View File

@@ -0,0 +1,104 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\"
.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
<https://gnu.org/licenses/gpl.html>.
.\"
.SH SEE ALSO
.BR ascii (7),
.BR ctype (3p),
.BR strcmp (1)

0
include/None.mk Normal file
View File

View File

@@ -1,6 +1,5 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: CC0
*
* This work is marked with CC0 1.0. To view a copy of this license, visit
@@ -11,9 +10,9 @@
# include <unistd.h> /* pledge(2) */
#endif
int main(void) {
int main() {
#ifdef __OpenBSD__
pledge(NULL, NULL);
#endif
return 1;
return 1;
}

View File

@@ -39,7 +39,7 @@ fn main() {
let mut d = '\u{1E}'.to_string(); /* ASCII record separator */
let mut optind = 1;
#[cfg(target_os="openbsd")] {
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio proc exec");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());

View File

@@ -86,7 +86,7 @@ fn main() -> ExitCode {
return ExitCode::from(EX_USAGE as u8);
}
#[cfg(target_os="openbsd")] {
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());

View File

@@ -34,6 +34,7 @@ use sysexits::EX_USAGE;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[cfg(target_os="openbsd")] use strerror::StrError;
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", s);
ExitCode::from(EX_USAGE as u8)
@@ -42,7 +43,7 @@ fn usage(s: &str) -> ExitCode {
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());

View File

@@ -51,8 +51,8 @@ fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]);
#[cfg(target_os="openbsd")] {
let promises = Promises::new("cpath rpath stdio unveil wpath");
if cfg!(target_os="openbsd") {
let promises = Promises::new("rpath stdio unveil");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
@@ -76,11 +76,29 @@ fn main() -> ExitCode {
Ok("t") => t = false,
Ok("i") => { /* add inputs */
let input = opt.arg().unwrap();
if cfg!(target_os="openbsd") {
let perms = UnveilPerms::new(vec!['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
ins.push(input);
mode = Some(In); /* latest argument == -i */
},
Ok("o") => { /* add output */
let output = opt.arg().unwrap();
let output = opt.arg().unwrap();
if cfg!(target_os="openbsd") {
let perms = UnveilPerms::new(vec!['w', 'c']);
if let Err(e) = unveil(Some(&output), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
outs.push(output);
mode = Some(Out); /* latest argument == -o */
},
@@ -106,25 +124,7 @@ fn main() -> ExitCode {
}
}
#[cfg(target_os="openbsd")] {
for input in &ins {
let perms = UnveilPerms::new(vec!['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
for output in &outs {
let perms = UnveilPerms::new(vec!['c', 'w']);
if let Err(e) = unveil(Some(&output), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
if cfg!(target_os="openbsd") {
if let Err(e) = unveil(None, None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);

View File

@@ -198,7 +198,7 @@ fn round_precise(value: &f64, precision: usize) -> f64 {
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());

View File

@@ -1,96 +0,0 @@
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* Copyright (c) 2023 Marceline Cramer <mars@tebibyte.media>
* 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 <ctype.h>
#include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3), perror(3) */
#include <stdlib.h> /* size_t, EXIT_FAILURE */
#include <string.h> /* strcmp(3) */
#include <sysexits.h> /* EX_OSERR, EX_USAGE */
#ifdef __OpenBSD__
# include <unistd.h> /* 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;
}

87
src/stris.rs Normal file
View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 20232024 DTB <trinity@trinity.moe>
* Copyright (c) 2023 Marceline Cramer <mars@tebibyte.media>
* 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::<Vec<String>>();
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
}

View File

@@ -54,7 +54,7 @@ fn usage(s: &str) -> ExitCode {
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
return oserr(&argv[0], e);

View File

@@ -1,6 +1,5 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: CC0
*
* This work is marked with CC0 1.0. To view a copy of this license, visit
@@ -11,7 +10,7 @@
# include <unistd.h> /* pledge(2) */
#endif
int main(void) {
int main() {
#ifdef __OpenBSD__
pledge(NULL, NULL);
#endif

View File

@@ -19,7 +19,7 @@ dj_tests: dj_help dj_full dj_null # dj_skip_stdin
dj_full: $(BIN)/dj /dev/full
case "$$(uname)" in \
Linux) \
! $(BIN)/dj -Hi /dev/zero -o /dev/full 2>&1 \
$(BIN)/dj -Hi /dev/zero -o /dev/full 2>&1 \
| tee /dev/stderr \
| xargs -I out test '1+0 > 0+0; 1024 > 0' = out \
;; \

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 2024 E$(NAME)a Tebibyte <e$(NAME)a@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
@@ -6,7 +6,7 @@
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: mm_tests
mm_tests: mm_args mm_help mm_stderr mm_remaining
mm_tests: mm_args mm_help mm_stderr
.PHONY: mm_none
mm_none: $(BIN)/mm
@@ -25,10 +25,3 @@ mm_help: $(BIN)/mm
# check if stderr is empty upon specifying -e
mm_stderr: $(BIN)/mm
test "$$(printf 'test\n' | $(BIN)/mm -e 2>&1 >/dev/null )" = "test"
.PHONY: mm_remaining
# check to make sure remaining arguments are used
mm_remaining: $(BIN)/mm
test "$$($(BIN)/mm -i README COPYING)" = "$$(cat README COPYING)"
$(BIN)/mm -i README -o /tmp/mm_test0 /tmp/mm_test1
diff /tmp/mm_test0 /tmp/mm_test1

View File

@@ -32,29 +32,11 @@ npc_ascii: npc_ascii_controls npc_ascii_symbols npc_ascii_uppers # \
.PHONY: npc_ascii_controls
# (control characters)
npc_ascii_controls:
# The following test prints the bytes 0x00 (inclusive) through 0x20
# (exclusive) and pipes them through npc(1). npc(1) should then replace all
# non-printing, non-space (in the isspace(3p) sense) characters with their
# graphical carat-char counterparts (see the npc(1) man page). The head(1p)
# invocation then strips off everything past the first line (or past the
# first newline byte, 0x0A) and xargs(1p) is used to test(1p) the output
# against the known good answer.
# Immediately before that newline, 0x09 is printed - in ASCII, the
# horizontal tab. If xargs' -I option is used, tr(1p) should used to delete
# that tab. If the tab is left as part of input, OpenBSD's xargs(1)
# implementation has been observed to strip it along with the other
# trailing whitespace (the newline), but Busybox's and GNU's xargs(1)
# implementations have been observed to leave the tab in. All three
# implementations strip off the trailing tab if `-I` is not used. The POSIX
# specification for `-I` is ambiguous as to which behavior is correct.
# This comment is the result of much bewilderment and debugging.
# ASCII 0x00 to 0x0a (before the newline, due to xargs(1p) issues)
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); }' \
| $(BIN)/npc \
| head -n 1 \
| xargs test "^@^A^B^C^D^E^F^G^H" =
| xargs -I out test "^@^A^B^C^D^E^F^G^H" = out
# ASCII 0x0a (otherwise the head|tail sequence won't work) to 0x1f
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); print }' \

View File

@@ -30,7 +30,7 @@ rpn_mul: $(BIN)/rpn
.PHONY: rpn_div
rpn_div: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 /)" = 2.4
test "$$($(BIN)/rpn 3 0 /)" = inf
test "$$($(BIN)/rpn 3 0 /)" -eq inf
.PHONY: rpn_mod
rpn_mod: $(BIN)/rpn

View File

@@ -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