Compare commits

..

9 Commits
pg ... stris

9 changed files with 247 additions and 455 deletions

View File

@@ -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
@@ -102,15 +102,11 @@ mm: build/bin/mm
build/bin/mm: src/mm.c build
$(CC) $(CFLAGS) -o $@ src/mm.c
.PHONY: npc
npc: build/bin/npc
build/bin/npc: src/npc.c build
$(CC) $(CFLAGS) -o $@ src/npc.c
.PHONY: pg
pg: build/bin/pg
build/bin/pg: src/pg.c build
$(CC) $(CFLAGS) -o $@ src/pg.c
$(CC) $(CFLAGAS) -o $@ src/npc.c
.PHONY: rpn
rpn: build/bin/rpn
@@ -122,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
@@ -134,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

View File

@@ -1,41 +0,0 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\"
.\" 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 PG 1
.SH NAME
pg \(en paginate
.SH SYNOPSIS
pg
.RB ( -p
.RB [ prompt ])
.SH DESCRIPTION
Pg prints standard input to standard output, accepting commands between pages.
.SH OPTIONS
.B -p
.RS
Replaces the default prompt (": ") with the next argument.
.RE
.SH DIAGNOSTICS
Pg returns an unsuccessful exit code if the tty couldn't be opened or if pg was
invoked incorrectly (with any arguments).
.SH RATIONALE
Plan 9 from Bell Labs had p(1), a similar "cooked"-mode paginator (as opposed
to "raw" mode, which a vast majority of paginators use).
.SH SEE ALSO
more(1)

View File

@@ -1,58 +0,0 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 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
.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 <trinity@trinity.moe>.
.SH COPYRIGHT
Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/gpl.html>.
.SH SEE ALSO
ctype(3p), strcmp(1), ascii(7)

118
docs/stris.1 Normal file
View File

@@ -0,0 +1,118 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 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
.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 <trinity@trinity.moe>.
.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 © 20232024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/gpl.html>.
.SH SEE ALSO
ascii(7), ctype(3p), strcmp(1)

View File

@@ -106,6 +106,15 @@ oserr(char *s, char *r){
} \
return retval
/* Prints a usage text, in which s is the program being run (i.e. argv[0]), and
* returns an exit status appropriate for a usage error. */
int usage(char *s){
fprintf(stderr, "Usage: %s (-aenu) (-i [input])... (-o [output])...\n", s);
return EX_USAGE;
}
int main(int argc, char *argv[]){
int c;
struct Files files[2]; /* {read, write} */
@@ -178,12 +187,15 @@ int main(int argc, char *argv[]){
k = 1;
break;
default:
fprintf(stderr, "Usage: %s (-aenu) (-i [input])..."
" (-o [output])...\n", argv[0]);
retval = EX_USAGE;
retval = usage(argv[0]);
terminate;
}
if(optind != argc){
retval = usage(argv[0]);
terminate;
}
files[0].s += files[0].s == 0;
files[1].s += files[1].s == 0;

260
src/pg.c
View File

@@ -1,260 +0,0 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* 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 <errno.h> /* errno */
#include <stdio.h> /* fclose(3), feof(3), fgetc(3), fgets(3), fopen(3),
* fprintf(3), fputc(3), stderr, stdin, stdout, EOF, FILE,
* NULL */
#include <stdlib.h> /* size_t */
#include <string.h> /* strchr(3), strcmp(3), strerror(3), strtok(3) */
#include <unistd.h> /* getopt(3) */
/* Commands start with cmd_. They take an argc and NULL-terminated argv, like
* main, and return a status from <sysexits.h>. Return values other than EX_OK
* and EX_USAGE cause pg(1) to exit with that value, except EX_UNAVAILABLE,
* which causes pg(1) to exit with the status EX_OK. */
#include <sysexits.h>
#define CMDLINE_MAX 99+1 /* Maximum length of command line. */
static char *whitespace = " \n\r\t\v";
static struct {
size_t quantity;
enum { LINES = 0, BYTES = 1 } type;
} default_page_unit = { 22, LINES } /* Plan 9 default */;
static FILE *input;
static char *prompt = ": ";
static char *program_name = "pg";
static char *permute_out(char *str, size_t index){
size_t j;
for(j = index; str[j - 1] != '\0'; ++j)
str[j - 1] = str[j];
return str;
}
/* strtok(3p), but supports double-quotes and escapes (but only for escaping
* quotes). UTF-8 is safe only in str. Unmatched quotes in str are considered
* literal. The behavior of strtok_quoted when '"' or '\\' are in sep is
* undefined. */
/* TODO: Seems to only ever return NULL. */
static char *strtok_quoted(char *str, char *sep){
static char *s;
size_t i;
size_t j;
if(str != NULL)
s = str;
while(strchr(sep, *s) == NULL)
if(*++s == '\0')
return NULL; /* no remaining characters except seperators */
{
char in_escape;
int in_quotes;
in_escape = 0;
in_quotes = -1;
for(i = 0; s[i] != '\0'; ++i)
switch(s[i]){
case '\\':
if(in_escape)
permute_out(s, i--);
in_escape = !in_escape;
break;
case '"':
if(in_escape){
s[i] = s[i - 1];
permute_out(s, i--);
}else if(in_quotes != -1){
permute_out(s, in_quotes);
--i;
permute_out(s, i--);
in_quotes = -1;
}else
in_quotes = i;
break;
case '\0':
return s;
default:
if(!in_escape && strchr(sep, s[i]) != NULL){
s[i] = '\0';
return s;
}
}
}
return s;
}
/* Page at most l bytes from f without consideration of a buffer (print them to
* stdout). */
static int pg_b_u(FILE *f, size_t l){
int c;
while((c = fgetc(f)) != EOF)
if((c = fputc(c, stdout)) == EOF || --l == 0)
break;
return c;
}
/* Page at most l lines, which are lengths of characters terminated by nl, from
* f without consideration of a buffer (print them to stdout). */
static int pg_l_u(FILE *f, size_t l, char nl){
int c;
while((c = fgetc(f)) != EOF)
if((c = fputc(c, stdout)) == EOF || (l -= (c == nl)) == 0)
break;
return c;
}
static int cmd_quit(int argc, char **argv, char **envp){return EX_UNAVAILABLE;}
static int cmd_default_page(int argc, char **argv, char **envp){
if(argc > 1) /* This shouldn't be possible. */
return EX_USAGE;
if(default_page_unit.type == BYTES)
return pg_b_u(input, default_page_unit.quantity) == EOF
? EX_UNAVAILABLE
: EX_OK;
else if(default_page_unit.type == LINES)
return pg_l_u(input, default_page_unit.quantity, '\n') == EOF
? EX_UNAVAILABLE
: EX_OK;
else
return EX_SOFTWARE;
}
static int cmd_page_down_lines(int argc, char **argv, char **envp){
switch(argc){
case 1:
return cmd_default_page(argc, argv, envp);
case 2: /* not implemented */
default:
fprintf(stderr, "Usage: %s" /*" (lines)"*/ "\n", argv[0]);
return EX_USAGE;
}
}
/* A CmdMap must be NULL-terminated. */
static struct CmdMap{ char *name; int (*fn)(int, char **, char **); }
builtins[] = {
{ "", cmd_default_page },
{ "+", cmd_page_down_lines },
/* don't make the user feel trapped */
{ ":q", cmd_quit }, { ":q!", cmd_quit },
{ "exit", cmd_quit }, { "q", cmd_quit }, { "Q", cmd_quit },
{ "quit", cmd_quit }, { "ZZ", cmd_quit },
{ NULL, NULL }
};
#define ARGV_MAX 10
/* Find and execute the command in the command map, given a corresponding
* command line. */
static int cmdline_exec(struct CmdMap *map, char *cmdline, char **envp){
/* Command line word splitting is naive and based on whitespace ONLY; no
* fancy quoting or escaping here. Adding that would (ideally) entail
* replacing strtok(3) with something specific to this task. */
static int argc;
static char *argv[ARGV_MAX];
if((argv[(argc = 0)] = strtok(cmdline, whitespace)) == NULL){
while(cmdline[0] != '\0')
cmdline = &cmdline[1];
argv[argc] = cmdline;
argv[++argc] = NULL;
}else{
while((argv[++argc] = strtok(NULL, whitespace)) != NULL
&& argc < ARGV_MAX);
}
for(; map->name != NULL; map = &map[1])
if(strcmp(map->name, argv[0]) == 0)
return map->fn(argc, argv, envp);
fprintf(stderr, "%s: %s: not found\n", program_name, argv[0]);
return EX_USAGE;
}
int usage(char *s){
fprintf(stderr, "Usage: %s (-p [prompt])\n", s);
return EX_USAGE;
}
int main(int argc, char *argv[]){
unsigned char cmd[CMDLINE_MAX];
FILE *t;
input = stdin;
if(argc < 1)
program_name = argv[0];
else{
int c;
while((c = getopt(argc, argv, "p:")) != -1)
switch(c){
case 'p': prompt = optarg; break;
default:
return usage(program_name);
}
}
if(argc > optind)
return usage(program_name);
if((t = fopen("/dev/tty", "rb")) == NULL){
fprintf(stderr, "%s: /dev/tty: %s\n", program_name, strerror(errno));
return EX_OSERR;
}
for(;;){
fputs(prompt, stderr);
fgets((char *)cmd, (sizeof cmd) / (sizeof *cmd), t);
if(strchr((char *)cmd, '\n') == NULL){ /* fast-forward stream */
int c;
while((c = fgetc(t)) != '\n')
if(c == EOF)
break;
}
if(feof(t))
return EX_OK;
{ int r;
switch((r = cmdline_exec(builtins, (char *)cmd, NULL))){
case EX_OK: case EX_USAGE: break;
case EX_UNAVAILABLE: return EX_OK;
default: return r;
} }
}
/* UNREACHABLE */
return EX_SOFTWARE;
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* Copyright (c) 20232024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
@@ -17,13 +18,15 @@
*/
#include <stdio.h> /* fprintf(3), stderr, NULL */
#include <stdlib.h> /* EXIT_FAILURE */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <string.h> /* memset(3), strchr(3) */
#ifndef EX_USAGE
# include <sysexits.h>
#endif
#include <unistd.h> /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */
#include <sys/stat.h> /* lstat(3), stat struct, S_ISBLK, S_ISCHR, S_ISDIR,
* S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK,
* S_ISUID, S_ISVTX */
#include <sysexits.h>
static char args[] = "bcdefghkprsuwxLS";
static char ops[(sizeof args) / (sizeof *args)];
@@ -57,7 +60,7 @@ int main(int argc, char *argv[]){
argv += optind;
do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1)
return 1; /* doesn't exist or isn't stattable */
return EXIT_FAILURE; /* doesn't exist or isn't stattable */
for(i = 0; ops[i] != '\0'; ++i)
if(ops[i] == 'e')
@@ -97,8 +100,8 @@ usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n",
&& !S_ISLNK(buf.st_mode))
|| (ops[i] == 'S'
&& !S_ISSOCK(buf.st_mode)))
return 1;
return EXIT_FAILURE;
}while(*++argv != NULL);
return 0;
return EXIT_SUCCESS;
}

View File

@@ -1,75 +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) */
#include <stdlib.h> /* EXIT_FAILURE */
#include <string.h> /* strcmp(3) */
#include <sysexits.h>
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;
}

93
src/stris.rs Normal file
View File

@@ -0,0 +1,93 @@
/*
* 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;
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::<Vec<String>>();
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
}