42 Commits

Author SHA1 Message Date
DTB
2bd7006d7a scroll(1): renamed from pg(1) 2024-07-31 17:31:09 -06:00
DTB
4c36ec1807 pg(1): more work on strtok_quoted 2024-07-31 17:28:26 -06:00
DTB
c01af89e52 pg(1): finish style changes 2024-07-31 02:52:35 -06:00
DTB
73b84f9719 pg(1): update style 2024-07-31 02:40:38 -06:00
DTB
383d53168f Merge branch 'main' into pg 2024-07-31 02:04:59 -06:00
6a5739ea9d Merge branch 'dj-formatting' 2024-07-29 22:40:18 -06:00
DTB
e90d25e30f Merge branch 'strcmp' 2024-07-15 19:52:26 -06:00
DTB
71f372d2c2 Merge branch 'intcmp' 2024-07-15 15:21:31 -06:00
DTB
27ff64dffa intcmp(1): add comments 2024-07-15 14:51:54 -06:00
DTB
becb3bac4e strcmp(1): code clarification 2024-07-15 14:40:26 -06:00
e7a6632b41 mm(1): improves comments 2024-07-15 12:52:02 -06:00
48dbea0228 mm(1): imports full enum 2024-07-15 12:52:02 -06:00
64f3f73d96 mm(1): formatting 2024-07-15 12:52:02 -06:00
eda5385058 mm.1: small correction 2024-07-15 12:52:02 -06:00
7a0ad78000 mm.1: reflects new changes 2024-07-15 12:52:02 -06:00
DTB
8dc763f05e mm(1): treats trailing arguments as optargs for the last opt 2024-07-15 12:52:01 -06:00
20692d581a mm(1): makes -e block inferring stdout as an output, mm.1: reflects changes to -e 2024-07-15 12:52:01 -06:00
8d693b6664 mm(1): removes debug print 2024-07-15 12:52:01 -06:00
37804aab6b mm(1): fixes comment about flags -a & -t 2024-07-15 12:52:01 -06:00
3b76254599 mm(1): fixes creating files 2024-07-15 12:52:01 -06:00
e972ff468a mm(1): added -t for disabling truncation; mm.1: updated docs 2024-07-15 12:52:01 -06:00
1b3b03cae0 mm(1): rewritten in Rust 2024-07-15 12:51:48 -06:00
DTB
efb3ce626d strcmp(1): fix program_name type 2024-07-15 04:29:43 -06:00
DTB
16f23e11c0 strcmp.1: update docs to match utility 2024-07-15 04:26:57 -06:00
DTB
d87c278be5 strcmp(1): re-style, tweak exits 2024-07-15 04:21:50 -06:00
DTB
5caefbb465 strcmp(1): note used sysexit 2024-07-15 03:45:36 -06:00
DTB
8d743dab7a strcmp(1): add copyright header
I could trace strcmp(1) as far back as
<293436c5ad/src/streq.c>
in my repo.
2024-07-15 03:43:25 -06:00
DTB
2f2270322a intcmp(1): rewrite in rust 2024-07-15 03:14:57 -06:00
DTB
699893af89 intcmp(1): initial rust impl 2024-07-15 03:09:00 -06:00
DTB
9addfc9284 Merge branch 'dj' 2024-07-15 02:36:07 -06:00
DTB
b7bc1f16ad swab(1): use the getopt error message 2024-07-08 14:34:42 -06:00
ca6865688a swab(1): updates getopt usage 2024-07-08 13:23:23 -06:00
DTB
1fd768057c swab(1): don't accept positional arguments 2024-07-08 11:45:01 -06:00
DTB
35d54d84b0 swab(1): don't use the getopt error message 2024-07-08 11:31:35 -06:00
DTB
a141b95293 swab(1): remove -f 2024-07-08 11:30:21 -06:00
7a452794b1 pg.1: updates documentation 2024-06-26 18:54:43 -06:00
DTB
7142994367 pg(1): start work on a quoted strtok(3) and internal environment 2024-04-26 19:08:48 -06:00
DTB
eb454a6dad pg(1): Simplify exit statuses from subcommands 2024-04-23 19:39:25 -06:00
DTB
9c1e0d785d pg(1): add -p option 2024-04-22 07:52:41 -06:00
DTB
68919bf877 pg(1): put command system in 2024-04-22 01:12:02 -06:00
DTB
ea8b58554e pg(1): make it fit the project better 2024-04-21 06:30:01 -06:00
DTB
e26d8e0e1b pg(1): import from trinity/src 2024-04-21 06:21:15 -06:00
12 changed files with 663 additions and 386 deletions

View File

@@ -32,7 +32,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 scroll scrut str strcmp swab true
# keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703
@@ -109,18 +109,23 @@ build/bin/hru: src/hru.rs build rustlibs
.PHONY: intcmp
intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.c build
$(CC) $(CFLAGS) -o $@ src/intcmp.c
build/bin/intcmp: src/intcmp.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/intcmp.rs
.PHONY: mm
mm: build/bin/mm
build/bin/mm: src/mm.c build
$(CC) $(CFLAGS) -o $@ src/mm.c
build/bin/mm: src/mm.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/mm.rs
.PHONY: npc
npc: build/bin/npc
build/bin/npc: src/npc.c build
$(CC) $(CFLAGAS) -o $@ src/npc.c
$(CC) $(CFLAGS) -o $@ src/npc.c
.PHONY: scroll
scroll: build/bin/scroll
build/bin/scroll: src/scroll.c build
$(CC) $(CFLAGS) -o $@ src/scroll.c
.PHONY: rpn
rpn: build/bin/rpn

View File

@@ -3,14 +3,14 @@
.\" 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 MM 1 2024-06-17 "Harakit X.X.X"
.TH MM 1 2024-07-14 "Harakit X.X.X"
.SH NAME
mm \(en middleman
.\"
.SH SYNOPSIS
mm
.RB [ -aenu ]
.RB [ -aetu ]
.RB [ -i\ input ]
.RB [ -o\ output ]
.\"
@@ -21,19 +21,25 @@ Catenate input files and write them to the start of each output file or stream.
.SH OPTIONS
.IP \fB-a\fP
Opens subsequent outputs for appending rather than updating.
Opens outputs for appending rather than updating.
.IP \fB-e\fP
Use the standard error as an output.
.IP \fB-i\fP\ \fIinput\fP
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
no inputs are specified, the standard input shall be used.
.IP \fB-o\fP\ \fIoutput\fP
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
no outputs are specified, the standard output shall be used.
.IP \fB-t\fP
Causes outputs to be overwritten instead of being truncated.
.IP \fB-u\fP
Ensures neither input or output will be buffered.
.IP \fB-n\fP
Causes SIGINT signals to be ignored.
.IP \fB-i\fP\ \fIinput\fP
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
no inputs are specified, the standard input shall be used. If specified as the
last option and if there are trailing arguments to the program, they shall be
appended to the list of files to use as inputs.
.IP \fB-o\fP\ \fIoutput\fP
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
no outputs are specified and the
.B -e
option is not specified, the standard output shall be used. If specified as the
last option and if there are trailing arguments to the program, they shall be
appended to the list of files to use as outputs.
.\"
.SH DIAGNOSTICS
@@ -45,10 +51,6 @@ exits with the appropriate
.BR sysexits.h (3)
status.
.\"
.SH CAVEATS
Existing files are not truncated on ouput and are instead overwritten.
.\"
.SH RATIONALE
The

52
docs/scroll.1 Normal file
View File

@@ -0,0 +1,52 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2024 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 PG 1 2024-07-31 "Harakit X.X.X"
.SH NAME
scroll \(en present output
.\"
.SH SYNOPSIS
scroll
.RB ( -p
.RB [ prompt ])
.\"
.SH DESCRIPTION
Print standard input to standard output, accepting commands between pages.
.\"
.SH OPTIONS
.IP -p
Replace the default prompt (\(lq: \(rq) with the option argument.
.\"
.SH DIAGNOSTICS
In the event of an error, a debug message will be printed and the program will
exit with the appropriate
.BR sysexits.h (3)
error code.
.\"
.SH RATIONALE
Plan 9 from Bell Labs had
.BR p (1),
a similar \(lqcooked\(rq-mode paginator (as opposed to \(lqraw\(rq mode, which a
vast majority of paginators use).
.\"
.SH AUTHOR
Written by DTB
.MT trinity@trinity.moe
.ME .
.\"
.SH COPYRIGHT
Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.\"
.SH SEE ALSO
.BR more (1p)

View File

@@ -4,7 +4,7 @@
.\" 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 STRCMP 1 2024-06-17 "Harakit X.X.X"
.TH STRCMP 1 2024-07-15 "Harakit X.X.X"
.SH NAME
strcmp \(en compare strings
.\"
@@ -20,15 +20,15 @@ Check whether string arguments are the same.
.SH DIAGNOSTICS
The program will exit successfully if the strings are identical. Otherwise, it
will exit with an error code of 1 if a string passed has a lesser byte value
than one of the prior strings:
will exit with an error code less than 128 if a string passed has a lesser byte
value than one of the prior strings:
.RS
strcmp b a
.RE
or with an error code of 255 if it has a greater byte value than one of the
prior strings:
or with an error code greater than 128 if it has a greater byte value than one
of the prior strings:
.RS
strcmp a b

View File

@@ -11,7 +11,6 @@ swab \(en swap bytes
.SH SYNOPSIS
swab
.RB [ -f ]
.RB [ -w\ word_size ]
.\"
.SH DESCRIPTION
@@ -20,8 +19,6 @@ Swap the latter and former halves of a block of bytes.
.\"
.SH OPTIONS
.IP \fB-f\fP
Ignore SIGINT signal.
.IP \fB-w\fP\ \fIword_size\fP
Configures the word size; that is, the size in bytes of the block size on which
to operate. The default word size is 2. The word size must be cleanly divisible

View File

@@ -1,91 +0,0 @@
/*
* Copyright (c) 2023 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
* 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> /* fprintf(3), stderr */
#include <stdlib.h> /* strtol(3), size_t, EXIT_FAILURE */
#include <unistd.h> /* getopt(3), optind */
#include <sysexits.h> /* EX_OK, EX_USAGE */
#define /* 0b00? */ EQUAL 0x01 /* | -e | 0b001 | 1 */
#define /* 0b0?0 */ GREATER 0x02 /* | -g | 0b010 | 2 */
/* Equal | Greater 0x03 | -ge | 0b011 | 3 */
#define /* 0b?00 */ LESSER 0x04 /* | -l | 0b100 | 4 */
/* Equal | Lesser 0x05 | -le | 0b101 | 5 */
/* (Inequal) Greater | Lesser 0x06 | -gl | 0b110 | 6 */
char *program_name = "intcmp";
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-egl] integer integer...\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
int c;
size_t i;
unsigned char mode;
int r; /* reference integer */
mode = 0;
if (argc < 3) {
return usage(argv[0] == NULL ? program_name : argv[0]);
}
while ((c = getopt(argc, argv, "egl")) != -1) {
switch (c) {
case 'e': mode |= EQUAL; break;
case 'g': mode |= GREATER; break;
case 'l': mode |= LESSER; break;
default: return usage(argv[0]);
}
}
if (optind + 2 /* ref cmp */ > argc) { return usage(argv[0]); }
i = optind;
do {
r = c;
c = strtol(argv[i], &argv[i], 10);
if (*argv[i] != '\0' || errno != 0) {
fprintf(
stderr,
"%s: argument #%d: Invalid integer\n",
argv[0],
(int)i
);
return EX_USAGE;
}
if (i == optind) { continue; }
/* rule enforcement; if a mode isn't permitted and the numbers
* correspond to it, return 1 */
if ( (!(mode & EQUAL) && r == c)
|| (!(mode & GREATER) && r > c)
|| (!(mode & LESSER) && r < c)
) { return 1; }
} while (++i < argc);
return EX_OK;
}

79
src/intcmp.rs Normal file
View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 20232024 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/.
*/
use std::{
env::args,
process::ExitCode
};
extern crate getopt;
use getopt::GetOpt;
extern crate sysexits;
use sysexits::EX_USAGE;
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", s);
ExitCode::from(EX_USAGE as u8)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut e = false; /* args can be == */
let mut g = false; /* args can be > */
let mut l = false; /* args can be < */
let mut optind = 0;
if argv.len() < 3 { return usage(&argv[0]); }
while let Some(opt) = argv.getopt("egl") {
match opt.opt() {
Ok("e") => e = true,
Ok("g") => g = true,
Ok("l") => l = true,
_ => { return usage(&argv[0]); },
}
optind = opt.ind();
}
if argv.len() - optind < 2 /* see usage */ { return usage(&argv[0]); }
let mut prev: Option<usize> = None; /* no previous operand */
let mut currn: usize;
for arg in argv.iter().skip(optind) { /* iterate operands */
match arg.parse::<usize>() { /* parse current operand */
Ok(n) => currn = n,
_ => {
eprintln!("{}: {}: Invalid integer", &argv[0], arg);
return ExitCode::from(EX_USAGE as u8);
}
}
if let Some(prevn) = prev { /* if there was a previous opr., test */
if (!e && prevn == currn)
|| (!g && prevn > currn)
|| (!l && prevn < currn)
{ return ExitCode::FAILURE; }
}
prev = Some(currn); /* there is a previous operand */
}
ExitCode::SUCCESS
}

242
src/mm.c
View File

@@ -1,242 +0,0 @@
/*
* Copyright (c) 2024 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
* 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 <signal.h> /* signal(2), SIG_ERR, SIG_IGN, SIGINT */
#include <stdio.h> /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3),
* setvbuf(3), size_t, _IONBF, NULL */
#include <stdlib.h> /* free(3), realloc(3) */
#include <string.h> /* strcmp(3), strerror(3) */
#include <unistd.h> /* getopt(3) */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */
/* This structure is how open files are tracked. */
struct Files{
size_t a; /* allocation */
size_t s; /* used size */
char *mode; /* file opening mode */
char **names; /* file names */
FILE **files; /* file pointers */
};
/* How much to grow the allocation when it's saturated. */
#ifndef ALLOC_INCREMENT
# define ALLOC_INCREMENT 1
#endif
/* How much to grow the allocation at program start. */
#ifndef ALLOC_INITIAL
# define ALLOC_INITIAL 10
#endif
/* pre-allocated strings */
char *program_name = "mm";
static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>";
static char *stderr_name = "<stderr>";
static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} };
static char *wharsh = "wb";
/* Adds the open FILE pointer for the file at the path s to the files struct,
* returning the FILE if successful and NULL if not, allocating more memory in
* the files buffers as needed. */
static FILE *
Files_append(struct Files *files, FILE *file, char *name) {
if (file == NULL || (files->s == files->a
&& ((files->files = realloc(files->files,
(files->a += (files->a == 0)
? ALLOC_INITIAL
: ALLOC_INCREMENT)
* sizeof *(files->files))) == NULL
|| (files->names = realloc(files->names,
files->a * sizeof *(files->names))) == NULL)))
return NULL;
files->names[files->s] = name;
return files->files[files->s++] = file;
}
/* Opens the file at the path p and puts it in the files struct, returning NULL
* if either the opening or the placement of the open FILE pointer fail. */
#define Files_open(files, p) \
Files_append((files), fopen((p), (files)->mode), (p))
/* Prints a diagnostic message based on errno and returns an exit status
* appropriate for an OS error. */
static int
oserr(char *s, char *r) {
(void)fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno));
return EX_OSERR;
}
static int
usage(char *argv0) {
(void)fprintf(
stderr,
"Usage: %s [-aenu] [-i input]... [-o output]...\n",
argv0
);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
struct Files files[2]; /* {read, write} */
int retval;
/* Initializes the files structs with their default values, standard
* input and standard output. If an input or an output is specified
* these initial values will be overwritten, so to, say, use mm(1)
* equivalently to tee(1p), -o - will need to be specified before
* additional files to ensure standard output is still written. */
for (size_t i = 0; i < 2; ++i) {
files[i].a = 0;
files[i].s = 0;
files[i].mode = fmode[i];
files[i].files = NULL;
files[i].names = NULL;
if (Files_append(
&files[i],
i == 0 ? stdin : stdout,
i == 0 ? stdin_name : stdout_name
) == NULL) {
return oserr(program_name, i == 0 ? stdin_name : stdout_name);
}
files[i].s = 0;
}
if (argc > 0) {
int c;
char unbuffered = 0;
program_name = argv[0];
while ((c = getopt(argc, argv, "aehi:no:u")) != -1) {
switch (c) {
case 'a': /* "rb+" -> "ab" */
files[1].mode[0] = 'a';
files[1].mode[2] = '\0';
break;
case 'e':
if (Files_append(&files[1], stderr, stderr_name) == NULL) {
return oserr(program_name, stderr_name);
}
break;
case 'i':
if (
(strcmp(optarg, "-") == 0
&& Files_append(&files[0], stdin, stdin_name) != NULL)
|| Files_open(&files[0], optarg) != NULL
) { break; }
return oserr(program_name, optarg);
case 'o':
if (
(strcmp(optarg, "-") == 0
&& Files_append(&files[1], stdout, stdout_name) != NULL)
|| Files_open(&files[1], optarg) != NULL
) { break; }
/* does not exist, so try to create it */
if (errno == ENOENT) {
files[1].mode = wharsh;
if (Files_open(&files[1], optarg) != NULL) {
files[1].mode = fmode[1];
break;
}
}
return oserr(program_name, optarg);
case 'n':
if (signal(SIGINT, SIG_IGN) == SIG_ERR) {
return oserr(program_name, "-n");
}
break;
case 'u': unbuffered = 1; break;
default: return usage(program_name);
}
}
if (unbuffered) { /* Unbuffer files. */
for (
size_t i = 0;
i < files[0].s;
setvbuf(files[0].files[i++], NULL, _IONBF, 0)
);
for (
size_t i = 0;
i < files[1].s;
setvbuf(files[1].files[i++], NULL, _IONBF, 0)
);
}
}
if (optind != argc) { return usage(program_name); }
files[0].s += files[0].s == 0;
files[1].s += files[1].s == 0;
retval = EX_OK;
/* Actual program loop. */
for (size_t i = 0; i < files[0].s; ++i) { /* iterate ins */
int c;
while ((c = getc(files[0].files[i])) != EOF) { /* iterate chars */
for (size_t j = 0; j < files[1].s; ++j) { /* iterate outs */
if (putc(c, files[1].files[j]) == EOF) { /* notebook's full */
retval = EX_IOERR;
(void)fprintf(
stderr,
"%s: %s: %s\n",
program_name,
files[1].names[j],
strerror(errno)
);
if (fclose(files[1].files[j]) == EOF) {
(void)fprintf(
stderr,
"%s: %s: %s\n",
program_name,
files[1].names[j],
strerror(errno)
);
}
/* massage out the tense muscle */
for(size_t k = j--; k < files[1].s - 1; ++k){
files[1].files[k] = files[1].files[k+1];
files[1].names[k] = files[1].names[k+1];
}
if(--files[1].s == 0) { return retval; }
}
}
}
}
return retval;
}

185
src/mm.rs Normal file
View File

@@ -0,0 +1,185 @@
/*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* 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/.
*/
use std::{
env::args,
fs::File,
io::{ stdin, stdout, stderr, BufWriter, Read, Write },
os::fd::{ AsRawFd, FromRawFd },
process::{ exit, ExitCode },
};
extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::GetOpt;
use strerror::StrError;
use sysexits::{ EX_IOERR, EX_USAGE };
use ArgMode::*;
enum ArgMode { In, Out }
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]);
let mut a = false; /* append to the file */
let mut e = false; /* use stderr as an output */
let mut t = true; /* do not truncate the file before writing */
let mut u = false; /* unbuffer i/o */
let mut ins = Vec::new(); /* initial input file path vector */
let mut outs = Vec::new(); /* initial output file path vector */
let mut mode: Option<ArgMode> = None; /* mode set by last-used option */
let mut optind = 0;
while let Some(opt) = argv.getopt("aei:o:tu") {
match opt.opt() {
Ok("a") => a = true,
Ok("e") => e = true,
Ok("u") => u = true,
Ok("t") => t = false,
Ok("i") => { /* add inputs */
let input = opt.arg().unwrap();
ins.push(input);
mode = Some(In); /* latest argument == -i */
},
Ok("o") => { /* add output */
let output = opt.arg().unwrap();
outs.push(output);
mode = Some(Out); /* latest argument == -o */
},
Err(_) | Ok(_) => {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
},
};
optind = opt.ind();
}
let remaining = argv.iter().skip(optind);
/* check the last flag specified */
if let Some(m) = mode {
for arg in remaining {
/* move the subsequent arguments to the list of inputs or outputs */
match m {
In => ins.push(arg.to_string()),
Out => outs.push(arg.to_string()),
};
}
} else {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
}
/* use stdin if no inputs are specified */
if ins.is_empty() { ins.push("-".to_string()); }
/* use stdout if no outputs are specified */
if outs.is_empty() && !e { outs.push("-".to_string()); }
/* map all path strings to files */
let inputs = ins.iter().map(|file| {
/* if a file is “-”, it is stdin */
if *file == "-" {
/* portable way to access stdin as a file */
return unsafe { File::from_raw_fd(stdin().as_raw_fd()) };
}
match File::open(file) {
Ok(f) => f,
Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
},
}
}).collect::<Vec<_>>();
/* map all path strings to files */
let mut outputs = outs.iter().map(|file| {
/* of a file is “-”, it is stdout */
if *file == "-" {
/* portable way to access stdout as a file */
return unsafe { File::from_raw_fd(stdout().as_raw_fd()) };
}
let options = File::options()
/* dont truncate if -t is specified, append if -a is specified */
.truncate(t)
.append(a)
/* enable the ability to create and write to files */
.create(true)
.write(true)
/* finally, open the file! */
.open(file);
match options {
Ok(f) => return f,
Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
},
};
}).collect::<Vec<_>>();
/* if -e is specified, use stderr */
if e {
/* portable way to access stderr as a file */
outputs.push(unsafe { File::from_raw_fd(stderr().as_raw_fd()) });
}
let mut outputs = outputs.iter().map(|o| {
if u {
/* unbuffered writing through a buffer of capacity 0 */
BufWriter::with_capacity(0, o)
} else {
/* theoretically buffered writing */
BufWriter::new(o)
}
}).collect::<Vec<_>>();
for file in inputs {
for byte in file.bytes().map(|b| {
b.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
})
}) {
for out in &mut outputs {
if let Err(e) = out.write(&[byte]) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_IOERR as u8);
}
if u {
/* immediately flush the output for -u */
if let Err(e) = out.flush() {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_IOERR as u8);
}
}
}
}
}
ExitCode::SUCCESS
}

288
src/scroll.c Normal file
View File

@@ -0,0 +1,288 @@
/*
* 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 <assert.h> /* assert(3) */
#include <errno.h> /* errno */
#include <stdbool.h> /* bool */
#include <stdio.h> /* fclose(3), feof(3), fgetc(3), fgets(3), fopen(3),
* fprintf(3), fputc(3), perror(3), stderr, stdin, stdout,
* EOF, FILE, NULL */
#include <stdlib.h> /* size_t */
#include <string.h> /* strchr(3), strcmp(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 Tube {
char *name; // command line
FILE *in; // process stdin
FILE *out; // process stdoout
size_t index; // in pipeline
};
static struct {
size_t quantity;
enum { LINES = 0, BYTES = 1 } type;
} default_page_unit = { 22, LINES } /* Plan 9 default */;
static char *prompt = ": ";
static char *program_name = "pg";
static char *
permute_out(char *s, size_t i) {
for (
;
s[i] != '\0';
s[i] = s[i + 1], ++i
);
return s;
}
/* strtok(3p), but supports double-quotes and escapes (but only for escaping
* quotes). Unmatched quotes in str are considered literal. The behavior of
* strtok_quoted when '\'', '"', or '\\' are in sep is undefined. Use of UTF-8
* separators with strtok_quoted is undefined. */
static char *
strtok_quoted(char *str, char *sep) {
static char *s;
if (str != NULL) { s = str; }
while (strchr(sep, *s) != NULL) { // skip beginning whitespace
if(*++s == '\0') { return NULL; } // no remaining except seps
}
{
bool in_escape = 0; // previous char was '\\'
char quote = '\0'; // quotation mark used, or '\0' if none
for (int i = 0; s[i] != '\0'; ++i)
switch (s[i]) {
case '\\':
// if literal "\\", permute out a backslash
if (in_escape) { (void)permute_out(s, i--); }
in_escape = !in_escape;
break;
case '\'': case '"':
if (in_escape) { // \"
s[i] = s[i - 1];
(void)permute_out(s, i--); // permute out backslash
} else if (s[i] == quote) {
quote = '\0';
(void)permute_out(s, i--); // second quote
} else {
quote = s[i];
if (strchr(&s[i + 1], quote) != NULL) { // has a match
(void)permute_out(s, i--); // permute out lquote
}
}
break;
case '\0': return s;
default:
if (!in_escape
&& quote == '\0'
&& (strchr(sep, s[i]) != NULL || s[i] == '\0')) {
char *t; // start of current token
t = s;
s = s[i] != '\0'
? &t[i + 1] // store start of next token,
: &t[i]; // or the address of the nul if found
s[i] = '\0'; // NUL terminate current token
return t;
}
}
}
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) { return EX_USAGE; } // should be impossible
else if (default_page_unit.type == BYTES) {
return pg_b_u(stdin, default_page_unit.quantity) == EOF
? EX_UNAVAILABLE
: EX_OK;
} else if (default_page_unit.type == LINES) {
return pg_l_u(stdin, 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:
(void)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[] = {
/* 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) {
static int argc;
static char *argv[ARGV_MAX];
if ((argv[(argc = 0)] = strtok_quoted(cmdline, whitespace)) == NULL) {
while (cmdline[0] != '\0') { cmdline = &cmdline[1]; }
argv[argc] = cmdline;
argv[++argc] = NULL;
} else {
while (
(argv[++argc] = strtok_quoted(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);
}
}
(void)fprintf(stderr, "%s: %s: not found\n", program_name, argv[0]);
return EX_USAGE;
}
static int
ioerr(char *argv0) {
perror(argv0);
return EX_IOERR;
}
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-p prompt]\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
unsigned char cmd[CMDLINE_MAX];
FILE *t;
if (argc > 0) {
int c;
program_name = argv[0];
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) {
perror(program_name);
return EX_OSERR;
}
for (;;) {
if (fputs(prompt, stderr) == EOF) { return ioerr(program_name); }
// if the line...
if (fgets((char *)cmd, (sizeof cmd) / (sizeof *cmd), t) != NULL) {
if (strchr((char *)cmd, '\n') == NULL) { // was taken incompletely
int c;
while ((c = fgetc(t)) != '\n') { // ...fast-forward stream
if (c == EOF) { break; }
}
}
} else { fputc('\n', stdout); } // EOF at start of line; finish prompt
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 */ assert(0);
}

View File

@@ -16,7 +16,6 @@
* 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 <stdio.h> /* fprintf(3), stderr */
#include <sysexits.h> /* EX_OK, EX_USAGE */

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 2024 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
@@ -18,7 +19,7 @@
use std::{
env::args,
io::{ stdin, stdout, Error, ErrorKind, Read, Write },
io::{ stdin, stdout, Error, Read, Write },
process::ExitCode,
vec::Vec
};
@@ -28,13 +29,21 @@ extern crate sysexits;
extern crate strerror;
use getopt::GetOpt;
use sysexits::{ EX_IOERR, EX_OK, EX_USAGE };
use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE };
use strerror::StrError;
fn err(s: &str, e: Error) { eprintln!("{}: {}", s, e.strerror()); }
fn oserr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_OSERR as u8)
}
fn ioerr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_IOERR as u8)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-f] [-w word_size]", s);
eprintln!("Usage: {} [-w word_size]", s);
ExitCode::from(EX_USAGE as u8)
}
@@ -43,26 +52,26 @@ fn main() -> ExitCode {
let mut buf: Vec<u8> = Vec::new(); // holds the sequence getting swabbed
let mut input = stdin();
let mut output = stdout().lock();
let mut force = false;
let mut optind: usize = 1; // argv[0]
let mut wordsize: usize = 2; // default; mimics dd(1p) conv=swab
while let Some(opt) = argv.getopt("fw:") {
while let Some(opt) = argv.getopt("w:") {
match opt.opt() {
Ok("f") => force = true,
Ok("w") => { // sets new sequence length
if let Some(arg) = opt.arg() {
match arg.parse::<usize>() {
// an odd sequence length is nonsensical
Ok(w) if w % 2 == 0 => { wordsize = w; () },
Ok("w") => {
match opt.arg().unwrap().parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; },
_ => { return usage(&argv[0]); },
}
}
optind = opt.ind();
},
_ => { return usage(&argv[0]); }
}
}
if optind < argv.len() {
return usage(&argv[0]);
}
buf.resize(wordsize, 0);
loop {
@@ -73,22 +82,16 @@ fn main() -> ExitCode {
if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) {
err(&argv[0], e);
break ExitCode::from(EX_IOERR as u8) // write error
break ioerr(&argv[0], e);
}
},
Ok(v) => { // partial read; partially write
if let Err(e) = output.write(&buf[..v]) {
err(&argv[0], e);
break ExitCode::from(EX_IOERR as u8) // write error
break ioerr(&argv[0], e);
}
},
Err(e) if e.kind() == ErrorKind::Interrupted && force => continue,
Err(e) => {
err(&argv[0], e);
break ExitCode::from(EX_IOERR as u8) // read error (or signal)
}
Err(e) => break oserr(&argv[0], e)
}
}
}