Compare commits

..

12 Commits
trunk ... pg

51 changed files with 842 additions and 1841 deletions

View File

@ -1,29 +1,26 @@
Make sure to read our code of conduct in the CONDUCT file.
Copyright Information
=====================
When editing a file, create a copyright statement correlated to your
identity so that it is easier to keep track of who has touched what file.
Pseudonymous contributions are welcome (and encouraged). Place new copyright
information below existing copyright information. If there is an existing
copyright statement:
When contributing a pull request to the main branch, please sign your commits
with a PGP key and add your name and the year to the bottom of the list of
copyright holders for the file. For example, an existing copyright header might
read:
* Copyright (c) 20222023 Emma Tebibyte <emma@tebibyte.media>
you would add your name below it like this:
You would add your name below it like this:
* Copyright (c) 20222023 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 20XX Your Name <your e-mail address or website>
We accept contributions from people using aliases.
Only list years in which you modified the source file. For example:
* Copyright (c) 20202021, 2023 Your Name <your-address@example.com>
This header shows that “Your Name” worked on this source file in 2020, 2021, and
2023. Please use the en dash (“–”, U+2013) to separate consecutive years in the
copyright notice.
2023. Please use the en dash (“–”) to separate the years in the copyright
notice.
If you are contributing a new file, please prepend the following license header
text to it, replacing the proper text on the copyright line:
@ -95,263 +92,19 @@ notice:
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
Make sure lines never exceed 80 columns in width when using four-character
indentation steps. This helps contributors with smaller screens, those using
side-by-side editor windows or panes, and those who have no text wrapping in
their editor or terminal.
Style
=====
For usage text and help messages, do not implement a -h option. Instead, print
usage information when any erroneous option is specified. Follow the NetBSD
style guide for the usage texts output format [1].
“Everyone knows that debugging is twice as hard as writing a program in the
first place. So if youre as clever as you can be when you write it, how
will you ever debug it?”
Brian Kernighan, The Elements of Programming Style
[1] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
The following guidelines are conducive to clear and readable code that is
consistent with the style of the rest of the Bonsai Computer System.
Use:
0. A single line for control flow statements short enough to be easily
understood at a glance:
if !(argc < 0) { usage(program_name); }
This applies to C switch statements and cases and Rust match statements, as
well:
switch (value) { /* aligning stuff to make it easier to read is fine */
case possibility: variable = foo; break;
default: variable = NULL; break;
}
1. Switch cases in C and match arms in Rust should start another level of
indentation:
switch (value) {
case possibility:
statement;
break;
default:
statement;
break;
}
match result {
Ok(n) => variable = n,
Err(e) => error = e,
}
2. Braces in control flow where their inclusion is left optional in C:
if (condition) { statement; }
3. Empty lines between different kinds of statements:
int t;
assert(io->bufuse > 0);
assert(io->bufuse <= io->bs);
if ((t = write(io->fd, io->buf, io->bufuse)) < 0) {
io->error = errno;
t = 0;
} else if (t > 0) {
memmove(io->buf, &(io->buf)[t], (io->bufuse -= t));
}
io->bytes += t;
io->prec += (t > 0 && io->bufuse > 0);
io->rec += (t > 0 && io->bufuse == 0);
return io;
4. Compiler options that yield the most useful warnings, such as -Wpedantic in
a lot of C compilers. Fix the warnings, too [0].
5. One more level of indentation and one argument per line when a function
call or statement header is too long to fit on one line:
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
6. One more level of indentation than the keyword that initiated a multi-line
block.
if (condition) {
statement;
statement;
}
7. The return value of all non-void functions, or explicitly ignore them (like
casting to void in C) [0]:
if ((a = malloc(sizeof char)) == NULL) { /* handle this error */
(void)fprintf(stderr, "oh noes!"); /* explicitly ignore this one */
return EX_OSERR; /* ...because the program is exiting anyway */
}
8. The smallest possible scope for data [0].
9. Comments noting all the symbols and macros used from a C header file, next
to its include macro:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
(space-aligned) * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
10. Spaces in control flow statements, after the keyword and before the
opening brace:
for (i = 2; i < argc; ++i) {
11. In Rust, a trailing comma on all arguments or fields that are on their own
lines:
return Err(EvaluationError {
message: format!("{}: Invalid token", i),
code: EX_DATAERR,
})
12. In Rust, place extern statements after use statements that include standard
library crates. Group like statements:
use std::fs::Path;
extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE };
13. If text is on the same line as a brace, spaces after an opening brace and
before a closing one:
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
14. Alphabetic sorting, where applicable:
use std::io::{ BufWriter, Read, Write, stderr, stdin, stdout }
15. In Rust, use the to_owned() method on string types (str, OsStr, CStr, etc.)
and the to_string() method on other types.
Avoid:
16. Unbounded loops [0].
17. Function pointers [0].
18. Heap memory allocation [0].
19. Using too much nested logic (within reason).
20. Too many levels of dereferences [0]:
/* do not do this */
for (size_t i = 0; i < sizeof a / sizeof *a; ++i) {
if (a[i].id == MATCH) { a[i].val = 0; }
}
/* do this */
for (struct MadeUp *s = &a[0]; *s != NULL; s = &s[1]) {
if (s->id == MATCH) { s->val = 0; }
}
21. Using C preprocessor macros; the fewer, the better [0].
22. The exit(3p) and std::process::exit() functions; returning from the main
function skips a system call.
Do not use:
23. More than the length of one printed page for a function [0].
24. Recursion, as its complex and can unexpectedly overflow the stack [0].
25. Any functionality not in the POSIX C specification and language features not
in C99.
26. Do-while loops, as theyre unique to C and confusing for casual programmers.
27. Labels and goto statements; use sensible flow control [0].
28. Pointer arithmetic, as it tends to be confusing and unnecessary; use
index-reference patterns like &p[1] instead of p + 1. &p[n] is the address at
p + sizeof p * n, not p + n, like pointer arithmetic suggests.
29. C struct bitfields in unions, to access certain bits of bigger data types,
as its poorly defined in the C standards; use bit arithmetic.
30. C trigraphs.
31. Inclusions in C header files, to prevent multiple file inclusions.
32. C preprocessor variables to prevent multiple inclusions of the same file,
such as:
#ifdef _FILE
#define _FILE
/* file body */
#endif /* ifdef _FILE */
Instead, take the time to ensure other files arent including any files twice.
33. The gets(3p) function from <stdio.h>, as its impossible to prevent buffer
overflows when it's used; use fgets(3p) from <stdio.h>.
34. The scanf(3p) function from <stdio.h> [1].
35. Any functionality not described in the latest POSIX make(1) specification.
36. Macros which panic on failure in Rust (such as the print!() and println!()
macros). Use a function and handle any errors. However, do use the eprintln!()
macro for error messages. Handling an error for writing an error message is
redundant.
37. A -h option for help text. Instead, print usage information when any
erroneous option is specified. See the Usage Text section below.
38. Lines which exceed 80 columns in width when using four-column indentation
steps. This helps contributors with smaller screens, those using side-by-side
editor windows or panes, and those who have no text wrapping in their editor or
terminal.
Usage Text
==========
This section is adapted from the NetBSD style guide [2].
When programs are invoked incorrectly and in the synopsis of manual pages, uasge
text should be provided to the user. The following is the format used by this
project for this purpose:
All optional arguments are to be placed in square brackets (U+005B, U+005D).
Mutually exclusive arguments can be separated by a vertical line (U+007C).
Groups of arguments should be specified in alphabetical order in most cases. The
order of arguments and an example of these rules follows:
0. Options with no option arguments.
1. Options with option arguments. Arguments should be specified inside the same
square brackets as the options.
3. Non-option arguments.
"usage: f [-aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\n"
"usage: f [-a | -b] [-c [-de] [-n number]]\n"
Committing
==========
When contributing to Bonsai, please sign your commit with a PGP key and create
the commit with an identity which can be easily contacted.
If committing a new utility, please include tests and documentation (see
tests/ and docs/) for the new tool.
Format commit messages following these guidelines:
If committing a new source file, format the commit message following these
guidelines:
$ git commit -m 'tool(1): add feature x'
@ -377,20 +130,6 @@ $ git commit -m 'tool(1): fix #42 & add feature x'
Commit messages should be written in the present tense.
References
==========
[0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf>
[1] <http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html>
[2] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
--
Copyright © 20232024 Emma Tebibyte <emma@tebibyte.media>
Copyright © 2024 DTB <trinity@trinity.moe>
Copyright © Wikipedia contributors
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit
<http://creativecommons.org/licenses/by-sa/4.0/>.
This work © 20232024 by Emma Tebibyte is licensed under CC BY-SA 4.0. To view a
copy of this license, visit <http://creativecommons.org/licenses/by-sa/4.0/>

View File

@ -1,4 +1,4 @@
# Copyright (c) 20232025 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232024 DTB <trinity@trinity.moe>
# Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media>
# Copyright (c) 2024 Aaditya Aryal <aryalaadi123@gmail.com>
@ -16,33 +16,23 @@
DESTDIR ?= dist
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'
# normalized prefix
PREFIX_N != realpath $(PREFIX)
MANDIR != test $(PREFIX_N) = / && printf '/usr/share/man\n' \
|| printf '$(PREFIX_N)/man\n'
PREFIX_N != (test -d $(PREFIX) && [ '-' != $(PREFIX) ] \
&& CDPATH= cd -P -- $(PREFIX) && pwd -P)
MANDIR != [ $(PREFIX_N) = / ] && printf '/usr/share/man\n' \
|| printf '/share/man\n'
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | tr ' ' '\n' \
| sed -n 's/sysexits\.h//p' || printf 'include\n'
CC ?= cc
RUSTC ?= rustc
RUSTFLAGS += --extern getopt=build/o/libgetopt.rlib \
--extern strerror=build/o/libstrerror.rlib \
--extern sysexits=build/o/libsysexits.rlib
RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
--extern strerror=build/o/libstrerror.rlib
CFLAGS += -I$(SYSEXITS)
# testing requires the absolute path to the bin directory set
BIN = build/bin
.PHONY: default
default: all test
.PHONY: all
all: dj false fileis fop hru intcmp mm npc peek rpn 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
@ -54,25 +44,18 @@ clean:
rm -rf build dist
dist: all docs
mkdir -p $(DESTDIR)/$(PREFIX_N)/bin $(DESTDIR)/$(MANDIR)/man1
cp build/bin/* $(DESTDIR)/$(PREFIX_N)/bin
cp build/docs/*.1 $(DESTDIR)/$(MANDIR)/man1
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
.PHONY: install
install: dist
cp -r $(DESTDIR)/* /
TESTFILES != for file in tests/*.mk; do printf '%s ' "$$file"; done
TESTS != printf '%s\n' "$(TESTFILES)" | xargs -n1 basename \
| sed 's/\.mk/_tests/g'
include $(TESTFILES)
.PHONY: test
test: all $(TESTS) /tmp/getopt
@echo $(TESTS)
test: build /tmp/getopt
/tmp/getopt
tests/posix-compat.sh
/tmp/getopt: src/libgetopt.rs
$(RUSTC) --test -o /tmp/getopt src/libgetopt.rs
@ -84,12 +67,9 @@ docs: docs/ build
"s/X\.X\.X/$$(git describe --tags --long | cut -d'-' -f1)/g")"; \
sed "s/$$original/$$title/g" <"$$file" >"build/$$file"; done
# include OS feature libraries for compilation
include $(OS_INCLUDE)
.PHONY: rustlibs
rustlibs: build/o/libgetopt.rlib build/o/libstrerror.rlib \
build/o/libsysexits.rlib $(OSLIB)
rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \
build/o/libstrerror.rlib
build/o/libgetopt.rlib: build src/libgetopt.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \
@ -100,7 +80,7 @@ build/o/libstrerror.rlib: build src/libstrerror.rs
src/libstrerror.rs
build/o/libsysexits.rlib: build/include/sysexits.h
bindgen --fit-macro-constant-types --default-macro-constant-type unsigned --use-core --formatter=none \
bindgen --default-macro-constant-type signed --use-core --formatter=none \
build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ -
# bandage solution until bindgen(1) gets stdin support
@ -117,45 +97,45 @@ false: build/bin/false
build/bin/false: src/false.c build
$(CC) $(CFLAGS) -o $@ src/false.c
.PHONY: fileis
fileis: build/bin/fileis
build/bin/fileis: src/fileis.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/fileis.rs
.PHONY: fop
fop: build/bin/fop
build/bin/fop: src/fop.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/fop.rs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/fop.rs
.PHONY: hru
hru: build/bin/hru
build/bin/hru: src/hru.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/hru.rs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/hru.rs
.PHONY: intcmp
intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/intcmp.rs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/intcmp.rs
.PHONY: mm
mm: build/bin/mm
build/bin/mm: src/mm.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/mm.rs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/mm.rs
.PHONY: npc
npc: build/bin/npc
build/bin/npc: src/npc.c build
$(CC) $(CFLAGS) -o $@ src/npc.c
.PHONY: peek
peek: build/bin/peek
build/bin/peek: src/peek.c build
$(CC) $(CFLAGS) -o $@ src/peek.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
build/bin/rpn: src/rpn.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/rpn.rs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/rpn.rs
.PHONY: scrut
scrut: build/bin/scrut
build/bin/scrut: src/scrut.c build
$(CC) $(CFLAGS) -o $@ src/scrut.c
.PHONY: str
str: build/bin/str
@ -170,7 +150,7 @@ build/bin/strcmp: src/strcmp.c build
.PHONY: swab
swab: build/bin/swab
build/bin/swab: src/swab.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/swab.rs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/swab.rs
.PHONY: true
true: build/bin/true

50
README
View File

@ -1,29 +1,24 @@
“Seek not to walk the path of the masters; seek what they sought.”
Matsuo Basho
Bonsais Harakit is an alternative to the standard POSIX utilities that aims to
be simpler, easier, and more powerful. These tools are the result of careful
examination of the current state common Unix utilities, POSIX-compliant and
otherwise, following frustrations with design decisions and implementation
details. They represent a vision of accomplishing everyday use cases with tools
that follow the Unix philosophy of “do one thing and do it well” without
clinging to the past.
The Bonsai harakit utilities are a replacement for standard POSIX utilities
which aim to fill its niche while expanding on their capabilities. These new
tools are the result of the careful examination of the current state of POSIX
and Unix utilies. The Unix Philosophy of “do one thing and do it well” are their
core but they avoid clinging to the past.
The intent of Harakit is not to conform to or extend POSIX, like the GNU or BSD
utilities do, but to invent new utilities to perform the same tasks in more
intuitive ways. GNU and BSD extensions are convenient but often unhealthy,
forgetting the purposes of the tools they extend, or building into existing
utilities features that would be more useful as their own tools to be used
anywhere. Other utility sets aim to provide a number of fully-featured
programs to be used individually, Harakit utilities are meant to be easily
composable and work together in pipelines.
The era of the original Unix tools has been long and fruitful, but they have
their flaws. This project originated from frustrations with the way certain
tools work and how other projects that extend POSIX dont make anything better.
See docs/ for more on the specific utilities currently implemented and see
CONTRIBUTING for guidelines for contributions.
This project will not follow in the footsteps of GNU; extensions of POSIX will
not be found here. GNU extensions are a gateway to the misuse of the shell. The
harakit utilities will intentionally discourage use of the shell for purposes
beyond its scope.
See docs/ for more on the specific utilities currently implemented.
Building
========
Harakit utilities require a POSIX-compliant environment to compile, including a
C compiler and preprocessor (cc(1) and cpp(1) by default), an edition 2023 Rust
@ -48,22 +43,7 @@ To remove all build and distributable files:
$ make clean
Contributing
============
See the CONTRIBUTING file for contribution guidelines.
Community
=========
xmpp://bonsai@covenant.murderu.us
irc://feeling.murderu.us/#bonsai
Read More
=========
An Introduction to the Unix Shell
<https://porkmail.org/era/unix/shell>
@ -77,10 +57,6 @@ Master Foo Discourses on the Unix-Nature
Shell Programming!
<https://tldp.org/LDP/abs/html/why-shell.html>
UNIX Style, or cat -v Considered Harmful
<http://harmful.cat-v.org/cat-v/>
--
Copyright © 20232024 Emma Tebibyte <emma@tebibyte.media>
Copyright © 2024 DTB <trinity@trinity.moe>

124
STYLE Normal file
View File

@ -0,0 +1,124 @@
The following guidelines are conducive to clear and readable code that is
consistent with the style of the rest of the Bonsai Computer System.
0. Braces are mandatory for all control flow.
1. Nested indentation should be kept to a minimum.
2. Empty lines should be placed between different kinds of statements:
int t;
assert(io->bufuse > 0);
assert(io->bufuse <= io->bs);
if ((t = write(io->fd, io->buf, io->bufuse)) < 0) {
io->error = errno;
t = 0;
} else if (t > 0) {
memmove(io->buf, &(io->buf)[t], (io->bufuse -= t));
}
io->bytes += t;
io->prec += (t > 0 && io->bufuse > 0);
io->rec += (t > 0 && io->bufuse == 0);
return io;
3. Each block of code should be indented once more than the keyword which
initiated the block:
switch (c) {
case 'e': mode |= EQUAL; break;
case 'g': mode |= GREATER; break;
case 'l': mode |= LESS; break;
default: return usage(s);
}
4. In C, spaces should be placed in control flow statements after the keyword
and before the opening brace:
for (i = 2; i < argc; ++i) {
5. If a function, a C control flow statement, or a Rust macro has arguments that
cause the statement to be broken into multiple lines, this should be done by
placing the arguments on a new line inside the parentheses:
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
6. If Rust function arguments or fields are on their own lines, they should
always have a trailing comma:
return Err(EvaluationError {
message: format!("{}: Invalid token", i),
code: EX_DATAERR,
})
7. If text is on the same line as a brace, spaces should be placed after an
opening curly brace and before a closing one:
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
8. If a control flow statement is short enough to be easily understood in a
glance, it may be placed on a single line:
if !(argc < 0) { usage(program_name); }
9. In C, note everything you use from a library in a comment subsequent to its
#include statement:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
10. In Rust, place extern statements after use statements that include standard
library crates. Group alike statements:
use std::fs::Path;
extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE };
11. Do not use do while loops in C.
12. Adhere to the following rules from the paper The Power of 10: Rules for
Developing Safety-Critical Code [0]:
1. Avoid complex flow constructs, such as goto and recursion.
2. All loops must have fixed bounds. This prevents runaway code.
3. Avoid heap memory allocation.
4. Restrict functions to the length of a single printed page.
6. Restrict the scope of data to the smallest possible.
7. Check the return value of all non-void functions, or cast to void to
indicate the return value is useless (such as in the case of using
fprintf(3p) to print to the standard error).
8. Use the preprocessor sparingly.
9. Limit pointer use to a single dereference, and do not use function
pointers.
10. Compile with all possible warnings active; all warnings should then be
addressed before release of the software (for C compilers, compile with
-Wpedantic).
13. Remember this quote from The Elements of Programming Style by Brian
Kernighan:
Everyone knows that debugging is twice as hard as writing a program in the
first place. So if you're as clever as you can be when you write it, how
will you ever debug it?
References
==========
[0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf>
--
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>
Copyright © Wikipedia contributors
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit
<http://creativecommons.org/licenses/by-sa/4.0/>.

View File

@ -207,7 +207,7 @@ which are not reported.
This program was based on the
.BR dd (1p)
utility as specified in \*(Px. While character conversion may have been the
utility as specified in POSIX. While character conversion may have been the
original intent of
.BR dd (1p),
it is irrelevant to its modern use. Because of this, this program eschews

View File

@ -15,7 +15,7 @@ always be returned.
.\"
.SH RATIONALE
In \*(Px.1-2024,
In POSIX.1-2017,
.BR false (1p)
exists for the construction of control flow and loops based on a failure. This
implementation functions as described in that standard.

View File

@ -75,7 +75,7 @@ this is elegant but unintuitive.
.\"
.SH RATIONALE
The traditional tool for integer comparisons in \*(Px and other Unix shells has
The traditional tool for integer comparisons in POSIX and other Unix shells has
been
.BR test (1).
This tool also handles string comparisons and file scrutiny. These parts of its

View File

@ -57,7 +57,7 @@ The
.BR cat (1p)
and
.BR tee (1p)
programs specified in \*(Px together provide similar functionality. The
programs specified in POSIX together provide similar functionality. The
separation of the two sets of functionality into separate APIs seemed
unncessary.
.\"

View File

@ -45,7 +45,7 @@ The program operates in single-byte chunks regardless of intended encoding.
.\"
.SH RATIONALE
\*(Px currently lacks a way to display non-printing characters in the terminal
POSIX currently lacks a way to display non-printing characters in the terminal
using a standard tool. A popular extension to
.BR cat (1p),
the

View File

@ -1,111 +0,0 @@
.\" Copyright (c) 2023-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 PEEK 1 2024-08-14 "Harakit X.X.X"
.SH NAME
peek \(en read from the standard input, furtively
.\"
.SH SYNOPSIS
peek
.RB [ -i ]
.\"
.SH DESCRIPTION
Read input from the standard input with terminal echo disabled.
.\"
.SH OPTIONS
.IP \fB-i\fP
Allows input to come from sources other than terminals (pipes).
.\"
.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.
In order to ensure the user\(cqs terminal is still usable after premature
termination, the program attempts to handle the SIGINT signal; if it cannot,
an error message is printed and execution continues. If the program is
interrupted, it exits unsuccessfully without an error message.
.\"
.SH RATIONALE
This tool was originally written to accept passwords in shell scripts as an
extremely simple alternative to the GNU Privacy Guard project\(cqs
.BR pinentry (1)
utility.
Accepting input without showing what is being typed is useful when keying in
secrets in public settings or in places where surveillance cameras are
installed.
.\"
.SH CAVEATS
This program does nothing to prevent others from seeing the key presses input to
a keyboard. It also does not protect against the sound of typing being analyzed
to determine what was input without needing to see screen or keyboard.
Accepting secrets in shell scripts is probably not advisable.
On systems that support it, the
.BR ioctl (2)
command TIOCSTI can be used to insert characters into the standard input. This
doesn't allow snooping but can be used for general mischief.
.\"
.SH EXAMPLES
This is an
.BR sh (1p)
command line that hashes a given password. It uses
.BR head (1p)
to only accept one line of input,
.BR xargs (1p)
and
.BR printf (1p)
to strip the trailing newline,
.BR htpasswd (1)
from Apache\(cqs utilities to hash the input with the bcrypt algorithm, and
.BR cut (1p)
to print only the resulting hash:
.RS
$ peek | head -n 1 | xargs printf '%s' | htpasswd -nBi _ | cut -d : -f 2
.RE
This is an
.BR sh (1p)
command line that allows a user to write blindly into a text file but displaying
only written lines. Some writers have the habit of prematurely revising their
work and use tools with functionality similar to this to prevent it.
It uses
.BR mm (1)
to pipe the output of the program to both the standard error and the regular
file writing.txt:
.RS
$ echo Input ^D to quit. && peek | mm -eo - >writing.txt
.RE
.\"
.SH AUTHOR
Written by DTB
.MT trinity@trinity.moe
.ME .
.\"
.SH COPYRIGHT
Copyright \(co 2023-2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/gpl.html>.
.\"
.SH SEE ALSO
.BR ioctl (2),
.BR ioctl_tty (2),
.BR read (1p),
.BR sh (1p),
.BR stty (1p)

View File

@ -58,7 +58,7 @@ hardware of any given machine.
An infix notation calculation utility,
.BR bc (1p),
is included in the \*(Px standard, but does not accept expressions as arguments;
is included in the POSIX standard, but does not accept expressions as arguments;
in scripts, any predefined, non-interactive input must be piped into the
program. A
.BR dc (1)

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

@ -1,15 +1,15 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 20242025 Emma Tebibyte <emma@tebibyte.media>
.\" 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 FILEIS 1 2025-02-24 "Harakit X.X.X"
.TH SCRUT 1 2024-06-06 "Harakit X.X.X"
.SH NAME
fileis \(en scrutinize file properties
scrut \(en scrutinize file properties
.SH SYNOPSIS
fileis
scrut
.RB [ -LSbcdefgkprsuwx ]
.B file...
.\"
@ -82,6 +82,5 @@ Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
.\"
.SH SEE ALSO
.BR access (3p),
.BR chown (1p),
.BR lstat (3p),
.BR test (1p)

View File

@ -47,13 +47,13 @@ visual similarity and not byte similarity.
.\"
.SH RATIONALE
The traditional tool for string comparisons in \*(Px and other Unix shells has
The traditional tool for string comparisons in POSIX and other Unix shells has
been
.BR test (1).
This tool also handles integer comparisons and file scrutiny. These parts of its
functionality have been broken out into multiple utilities.
This program\(cqs functionality may be performed on a \*(Px-compliant system
This program\(cqs functionality may be performed on a POSIX-compliant system
with
.BR test (1p).
.\"

View File

@ -33,7 +33,6 @@ line:
.RS
printf 'hello world!\(rsn' | swab
.RE
.\" If you change this, make sure to change it in tests/bonsai/swab.mk too.
Produces the following output:

View File

@ -15,7 +15,7 @@ always be returned.
.\"
.SH RATIONALE
In \fI\*(Px.1-2024\fP,
In \fIPOSIX.1-2017\fP,
.BR true (1p)
exists for the construction of control flow and loops based on a success. This
implementation functions as described in that standard.

View File

@ -1,6 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.

View File

@ -1,13 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
OSLIB = build/o/libopenbsd.rlib
RUSTFLAGS += --extern openbsd=$(OSLIB)
$(OSLIB): src/libopenbsd.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=openbsd \
-o $@ src/libopenbsd.rs

View File

@ -26,8 +26,7 @@
#include <string.h> /* memcpy(3), memmove(3), memset(3) */
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* pledge(2), unveil(2), optarg, optind, STDIN_FILENO,
* STDOUT_FILENO */
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
#include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR */
char *program_name = "dj";
@ -108,10 +107,8 @@ Io_write(struct Io *io) {
}
static int
oserr(char *e, int n) { /* program_name: [failing component:] error */
(void)fprintf(stderr, "%s: ", program_name);
if (e != NULL) { (void)fprintf(stderr, "%s: ", e); }
(void)fprintf(stderr, "%s\n", strerror(n));
oserr(char *e, int n) {
(void)fprintf(stderr, "%s: %s: %s\n", program_name, e, strerror(n));
return EX_OSERR;
}
@ -177,12 +174,6 @@ int main(int argc, char *argv[]) {
bool retry; /* false if exits on partial reads or writes */
struct Io io[2 /* { in, out } */];
#ifdef __OpenBSD__
if (pledge("cpath rpath stdio unveil wpath", NULL) == -1) {
return oserr(NULL, errno);
}
#endif
/* Set defaults. */
align = -1;
count = -1;
@ -218,12 +209,6 @@ int main(int argc, char *argv[]) {
} else {
int fd;
#ifdef __OpenBSD__
if (unveil(optarg, i == 0 ? "r" : "wc") == -1) {
return oserr(NULL, errno);
}
#endif
if (
(fd = open(optarg, io[i].fl, creat_mode)) != -1
&& (fdisstd(io[i].fd) || close(io[i].fd) == 0)
@ -264,10 +249,6 @@ int main(int argc, char *argv[]) {
}
}
#ifdef __OpenBSD__
if (unveil(NULL, NULL) == -1) { return oserr(NULL, errno); }
#endif
assert(io->fd != STDIN_FILENO || io->fl == read_flags);
assert(io->fd != STDOUT_FILENO || io->fl == write_flags);

View File

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

View File

@ -1,91 +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/.
*/
use std::{
env::args,
fs::metadata,
os::unix::fs::{ FileTypeExt, MetadataExt },
process::ExitCode,
};
extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::GetOpt;
use strerror::StrError;
use sysexits::EX_USAGE;
const OPTS: &str = "bcdefgkprsuwxLS";
fn usage(argv0: &str) -> ExitCode {
eprintln!("Usage: {} [-{}] file...", argv0, OPTS);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let mut sel = String::with_capacity(OPTS.len()); // selected options
let mut optind: usize = 1; // argv[0]
while let Some(opt) = argv.getopt(OPTS) {
if let Ok(optchr) = opt.opt() { sel.push_str(optchr); }
else { return usage(&argv[0]); }
optind = opt.ind();
}
if optind == argv.len() { return usage(&argv[0]); }
for arg in argv.iter().skip(optind) {
let fmeta = match metadata(arg) {
Ok(m) => m,
Err(e) => { // no perms or nonexistent
eprintln!("{}: {}: {}", argv[0], arg, e.strerror());
return ExitCode::FAILURE;
},
};
let fmode = fmeta.mode();
let ftype = fmeta.file_type();
for selection in sel.chars() { // run all selected tests
match selection {
'b' if ftype.is_block_device() => (),
'c' if ftype.is_char_device() => (),
'e' => (), // exists or metadata would have errored
'd' if fmeta.is_dir() => (),
'f' if fmeta.is_file() => (),
'g' if fmode & 0o2000 /* S_ISGID */ != 0 => (), // setgid
'k' if fmode & 0o1000 /* S_ISVTX */ != 0 => (), // setvtx
'p' if ftype.is_fifo() => (),
'r' if fmode & 0o0400 /* S_IRUSR */ != 0 => (), // read access
'u' if fmode & 0o4000 /* S_ISUID */ != 0 => (), // setuid
'w' if fmode & 0o0200 /* S_IWUSR */ != 0 => (), // write access
'x' if fmode & 0o0100 /* S_IXUSR */ != 0 => (), // exec access
'L' if fmeta.is_symlink() => (),
'S' if ftype.is_socket() => (),
_ => { return ExitCode::FAILURE; }
}
}
}
ExitCode::SUCCESS
}

View File

@ -18,8 +18,8 @@
use std::{
env::args,
io::{ Error, Read, Write, stdin, stdout },
process::{ Command, ExitCode, Stdio, exit },
io::{ Read, stdin, stdout, Write },
process::{ Command, exit, Stdio },
};
extern crate getopt;
@ -30,38 +30,15 @@ use getopt::GetOpt;
use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
fn err(argv0: &String, e: Error) {
eprintln!("{}: {}", argv0, e.strerror());
}
fn usage(argv0: &String) -> u8 {
eprintln!("Usage: {} [-d delimiter] index command [args...]", argv0);
EX_USAGE
}
fn main() -> ExitCode {
fn main() {
let argv = args().collect::<Vec<String>>();
let mut d = '\u{1E}'.to_string(); /* ASCII record separator */
let mut optind = 1;
#[cfg(target_os="openbsd")] {
let promises = Promises::new("exec proc stdio unveil");
if let Err(e) = pledge(Some(promises), None) {
err(&argv[0], e);
return ExitCode::from(EX_OSERR);
}
if let Err(e) = unveil(None, None) {
err(&argv[0], e);
return ExitCode::from(EX_OSERR);
}
}
if argv.len() == 1 { return ExitCode::from(usage(&argv[0])); }
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
while let Some(opt) = argv.getopt("d:") {
match opt.opt() {
@ -71,7 +48,8 @@ fn main() -> ExitCode {
optind = opt.ind();
},
_ => {
return ExitCode::from(usage(&argv[0]));
eprintln!("{}", usage);
exit(EX_USAGE);
}
};
}
@ -79,7 +57,7 @@ fn main() -> ExitCode {
/* parse the specified index as a number we can use */
let index = argv[optind].parse::<usize>().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[1], e);
exit(EX_DATAERR.into());
exit(EX_DATAERR);
});
/* index of the argv[0] for the operator command */
@ -87,14 +65,15 @@ fn main() -> ExitCode {
/* argv[0] of the operator command */
let operator = argv.get(command_arg).unwrap_or_else(|| {
exit(usage(&argv[0]).into());
eprintln!("{}", usage);
exit(EX_USAGE);
});
/* read entire standard input into memory */
let mut buf = String::new();
if let Err(e) = stdin().read_to_string(&mut buf) {
err(&argv[0], e);
exit(EX_IOERR.into());
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
};
/* split the buffer by the delimiter (by default, '\u{1E}') */
@ -114,14 +93,18 @@ fn main() -> ExitCode {
.stdout(Stdio::piped()) /* piped stdout to handle output ourselves */
.spawn()
.unwrap_or_else( |e| {
err(&argv[0], e);
exit(EX_UNAVAILABLE.into());
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_UNAVAILABLE);
});
/* get field we want to pipe into spawned program */
let field = fields.get(index).unwrap_or_else(|| {
eprintln!("{}: {}: no such index in input", argv[0], index);
exit(EX_DATAERR.into());
eprintln!(
"{}: {}: No such index in input",
argv[0],
index.to_string(),
);
exit(EX_DATAERR);
});
/* get the stdin of the newly spawned program and feed it the field val */
@ -131,8 +114,8 @@ fn main() -> ExitCode {
}
let output = spawned.wait_with_output().unwrap_or_else(|e| {
err(&argv[0], e);
exit(EX_IOERR.into());
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_IOERR);
});
/* get the output with which the original field will be replaced */
@ -148,8 +131,8 @@ fn main() -> ExitCode {
/* convert the output of the program to UTF-8 */
let new_field = String::from_utf8(replace).unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e);
exit(EX_IOERR.into());
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e);
exit(EX_IOERR);
});
/* store the new field in the old fields vector */
@ -159,9 +142,7 @@ fn main() -> ExitCode {
stdout().write_all(
fields.join(&d.to_string()).as_bytes()
).unwrap_or_else(|e| {
err(&argv[0], e);
exit(EX_IOERR.into());
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
});
ExitCode::SUCCESS
}

View File

@ -19,7 +19,7 @@
use std::{
cmp::Ordering,
env::args,
io::{ Write, stdin, stdout },
io::{ stdin, stdout, Write },
process::{ ExitCode, exit },
};
@ -27,11 +27,7 @@ extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE };
/* list of SI prefixes */
const LIST: [(u32, &str); 10] = [
@ -47,16 +43,6 @@ const LIST: [(u32, &str); 10] = [
(30, "Q"), /* quetta */
];
fn err(argv0: &String, message: String, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, message);
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {}", argv0);
ExitCode::from(EX_USAGE)
}
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
/* preserve decimal places in output by casting to a float */
let mut out = (input as f64, (0_u32, ""));
@ -90,49 +76,40 @@ fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
if let Some(_) = argv.get(1) { return usage(&argv[0]); }
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e.strerror(), Some(EX_OSERR));
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), Some(EX_OSERR));
}
}
let mut buf = String::new();
if let Err(e) = stdin().read_line(&mut buf) {
return err(&argv[0], e.strerror(), Some(EX_IOERR));
}
while let Ok(_) = stdin().read_line(&mut buf) {
if buf.is_empty() { return ExitCode::SUCCESS; }
if buf.is_empty() { return ExitCode::SUCCESS; }
let n: u128 = match buf.trim().parse() {
Ok(f) => {
buf.clear();
f
},
Err(err) => {
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_DATAERR as u8);
},
};
let n: u128 = match buf.trim().parse() {
Ok(f) => {
buf.clear();
f
},
Err(e) => return err(&argv[0], e.to_string(), Some(EX_DATAERR)),
};
let (number, prefix) = match convert(n) {
Ok(x) => x,
Err(err) => {
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_SOFTWARE as u8);
},
};
let (number, prefix) = convert(n).unwrap_or_else(|e| {
let _ = err(&argv[0], e.to_string(), None);
exit(EX_SOFTWARE.into());
});
let si_prefix = format!("{}B", prefix.1);
let si_prefix = prefix.1.to_owned() + "B";
/* round output number to one decimal place */
let out = ((number * 10.0).round() / 10.0).to_string();
/* round output number to one decimal place */
let rounded = (number * 10.0).round() / 10.0;
let out = rounded.to_string() + " " + &si_prefix + &'\n'.to_string();
if let Err(e) = stdout().write_all(out.as_bytes()) {
return err(&argv[0], e.strerror(), Some(EX_IOERR));
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes())
.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
});
}
ExitCode::SUCCESS

View File

@ -1,6 +1,5 @@
/*
* 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
@ -23,41 +22,18 @@ use std::{
};
extern crate getopt;
extern crate sysexits;
use getopt::GetOpt;
use sysexits::{ EX_DATAERR, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] extern crate strerror;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
#[cfg(target_os="openbsd")] use strerror::StrError;
extern crate sysexits;
use sysexits::EX_USAGE;
fn err(argv0: &String, e: String, code: u8) -> ExitCode {
eprintln!("{}: {}", argv0, e);
ExitCode::from(code)
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", argv0);
ExitCode::from(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>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e.strerror(), EX_OSERR);
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), EX_OSERR);
}
}
let mut e = false; /* args can be == */
let mut g = false; /* args can be > */
let mut l = false; /* args can be < */
@ -70,13 +46,11 @@ fn main() -> ExitCode {
Ok("e") => e = true,
Ok("g") => g = true,
Ok("l") => l = true,
_ => return usage(&argv[0]),
_ => { return usage(&argv[0]); },
}
optind = opt.ind();
}
if !e & !g & !l { return usage(&argv[0]); }
if argv.len() - optind < 2 /* see usage */ { return usage(&argv[0]); }
let mut prev: Option<usize> = None; /* no previous operand */
@ -85,9 +59,9 @@ fn main() -> ExitCode {
for arg in argv.iter().skip(optind) { /* iterate operands */
match arg.parse::<usize>() { /* parse current operand */
Ok(n) => currn = n,
Err(e) => {
let error = arg.to_owned() + ": " + &e.to_string();
return err(&argv[0], error, EX_DATAERR);
_ => {
eprintln!("{}: {}: Invalid integer", &argv[0], arg);
return ExitCode::from(EX_USAGE as u8);
}
}

View File

@ -1,99 +0,0 @@
/*
* 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/.
*/
use std::{
ffi::{ CString, c_char },
io::Error,
ptr::null,
};
mod openbsd {
use std::ffi::{ c_char, c_int };
extern "C" {
pub fn pledge(arg1: *const c_char, arg2: *const c_char) -> c_int;
pub fn unveil(arg1: *const c_char, arg2: *const c_char) -> c_int;
pub fn __errno() -> *mut c_int;
}
}
pub struct Promises(*const c_char);
impl Promises {
pub fn new(promises: &str) -> Self {
let p = CString::new(promises).unwrap();
Promises(p.into_raw() as *const c_char)
}
}
impl Default for Promises {
fn default() -> Self { Promises::new("") }
}
pub fn pledge(
promises: Option<Promises>, execpromises: Option<Promises>
) -> Result<(), Error> {
/* From pledge(2):
*
* Passing NULL to promises or execpromises specifies to not change
* the current value. */
let arg1 = promises.unwrap_or(Promises(null())).0;
let arg2 = execpromises.unwrap_or(Promises(null())).0;
unsafe {
match openbsd::pledge(arg1, arg2) {
-1 => Err(Error::from_raw_os_error(*openbsd::__errno())),
0 => Ok(()),
_ => panic!(), /* unreachable */
}
}
}
pub struct UnveilPerms(CString);
impl UnveilPerms {
pub fn new<T: IntoIterator<Item = char>>(permissions: T) -> Self {
let perms = CString::new(
permissions.into_iter().collect::<String>()
).unwrap();
UnveilPerms(perms)
}
}
pub fn unveil(
path: Option<&str>,
permissions: Option<UnveilPerms>,
) -> Result<(), Error> {
let path_c = path.map(CString::new).map(Result::unwrap);
let arg1 = path_c.map(|p| p.into_raw() as *const c_char).unwrap_or(null());
let arg2 = permissions.map(|p| {
p.0.into_raw() as *const c_char
}).unwrap_or(null());
unsafe {
match openbsd::unveil(arg1, arg2) {
-1 => Err(Error::from_raw_os_error(*openbsd::__errno())),
0 => Ok(()),
_ => panic!(), /* unreachable */
}
}
}

View File

@ -20,9 +20,9 @@
use std::{
env::args,
fs::File,
io::{ Error, BufWriter, Read, Write, stderr, stdin, stdout },
io::{ stdin, stdout, stderr, BufWriter, Read, Write },
os::fd::{ AsRawFd, FromRawFd },
process::{ ExitCode, exit},
process::{ exit, ExitCode },
};
extern crate getopt;
@ -33,39 +33,13 @@ use getopt::GetOpt;
use strerror::StrError;
use sysexits::{ EX_IOERR, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")]
use openbsd::{
Promises,
UnveilPerms,
pledge,
unveil,
};
use ArgMode::*;
enum ArgMode { In, Out }
fn err(argv0: &String, e: Error, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {} [-aetu] [-i input] [-o output]", argv0);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("cpath rpath stdio unveil wpath");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
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 */
@ -88,12 +62,13 @@ fn main() -> ExitCode {
mode = Some(In); /* latest argument == -i */
},
Ok("o") => { /* add output */
let output = opt.arg().unwrap();
let output = opt.arg().unwrap();
outs.push(output);
mode = Some(Out); /* latest argument == -o */
},
Err(_) | Ok(_) => {
return usage(&argv[0]);
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
},
};
@ -111,32 +86,9 @@ fn main() -> ExitCode {
Out => outs.push(arg.to_string()),
};
}
}
#[cfg(target_os="openbsd")] {
for input in &ins {
let perms = UnveilPerms::new(['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
for output in &outs {
let perms = UnveilPerms::new(['c', 'w']);
if let Err(e) = unveil(Some(&output), Some(perms)) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
if ins.is_empty() && outs.is_empty() && argv.len() > optind {
return usage(&argv[0]);
} else {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
}
/* use stdin if no inputs are specified */
@ -156,8 +108,8 @@ fn main() -> ExitCode {
match File::open(file) {
Ok(f) => f,
Err(e) => {
let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR.into());
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
},
}
}).collect::<Vec<_>>();
@ -183,8 +135,8 @@ fn main() -> ExitCode {
match options {
Ok(f) => return f,
Err(e) => {
let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR.into());
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
},
};
}).collect::<Vec<_>>();
@ -208,19 +160,21 @@ fn main() -> ExitCode {
for file in inputs {
for byte in file.bytes().map(|b| {
b.unwrap_or_else(|e| {
let _ = err(&argv[0], e, None);
exit(EX_IOERR.into());
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
})
}) {
for out in &mut outputs {
if let Err(e) = out.write(&[byte]) {
return err(&argv[0], e, Some(EX_IOERR));
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() {
return err(&argv[0], e, Some(EX_IOERR));
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_IOERR as u8);
}
}
}

View File

@ -19,8 +19,8 @@
#include <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin,
* stdout, EOF */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */
#include <unistd.h> /* NULL, getopt(3), pledge(2), unveil(2) */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_USAGE */
#include <unistd.h> /* getopt(3) */
char *program_name = "npc";
@ -43,13 +43,6 @@ int main(int argc, char *argv[]) {
char showend = 0; /* print a dollar sign before each newline */
char showtab = 0; /* prints tab characters in caret notation */
#ifdef __OpenBSD__
if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL)) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}
#endif
if (argc > 0) {
program_name = argv[0];

View File

@ -1,147 +0,0 @@
/*
* 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/.
*/
#include <signal.h> /* sigaction(2), signal(2), struct sigaction, SIGINT */
#include <stdbool.h> /* bool */
#include <stdio.h> /* fprintf(3), fgetc(3), perror(3), fputc(3), stderr, stdin,
* stdout, EOF, NULL */
#include <stdlib.h> /* exit(3), EXIT_FAILURE */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_USAGE */
#include <termios.h> /* tcgetattr(3), tcsetattr(3), struct termios, ECHO */
#include <unistd.h> /* getopt(3), isatty(3), pledge(2), unveil(2),
* STDIN_FILENO */
char *program_name = "peek";
/* Restores terminal echo; otherwise when a user ^Cs the terminal would
* continue to not display typed text. If sig isn't zero, this will terminate
* the program. */
static void
restore_echo(int sig) {
static struct termios t;
/* Failure isn't reported because this is the termination routine anyway;
* errors will be obvious. */
if (tcgetattr(STDIN_FILENO, &t) == 0) {
t.c_lflag |= ECHO;
(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);
}
if (sig != 0) { exit(EXIT_FAILURE); } /* Terminated by signal. */
return;
}
static int
ioerr(char *argv0) {
perror(argv0);
restore_echo(0);
return EX_IOERR;
}
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-i]\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]){
bool is_term; /* Is stdin a terminal? */
bool must_be_term = 1; /* Must it be? */
#ifdef __OpenBSD__
if (pledge("stdio tty unveil", "") != 0 || unveil(NULL, NULL) != 0) {
/* This isn't fatal; these return values could be cast to void just as
* easily. */
(void)perror(argv[0] == NULL ? argv[0] : program_name);
}
#endif
is_term = isatty(STDIN_FILENO);
if (argc > 0) { /* option parsing */
int c;
program_name = argv[0];
while ((c = getopt(argc, argv, "i")) != -1) {
switch (c) {
case 'i': must_be_term = 0; break;
default: return usage(argv[0]);
}
}
if (argc > optind) { return usage(argv[0]); }
}
if (!is_term && must_be_term) {
(void)fprintf(
stderr,
"%s: Must be run in a terminal (specify -i to skip this check)\n",
argv[0]
);
return EX_USAGE;
}
if (is_term) {
{ /* Install signal handler */
/* There isn't a difference in functionality between the signal(2)
* and sigaction(2) methods. sigaction(2) is vastly preferred for
* portability but some older systems only have signal(2). */
/* Errors aren't terminating because the worst that happens is some
* terminal phooeyness if things go awry. */
#if defined _POSIX_C_SOURCE
struct sigaction act = { 0 };
act.sa_handler = restore_echo;
if (sigaction(SIGINT, &act, NULL) != 0) { perror(program_name); }
#else
if (signal(SIGINT, restore_echo) == SIG_ERR) {
perror(program_name);
}
#endif
}
{ /* Banish terminal echo */
/* This terminates when it fails because it's the whole point of
* the program. */
struct termios t;
if (tcgetattr(STDIN_FILENO, &t) != 0) {
return ioerr(program_name);
}
t.c_lflag ^= ECHO;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t) != 0) {
return ioerr(program_name);
}
}
}
{ /* Input loop */
int c;
while ((c = fgetc(stdin)) != EOF) {
if (fputc(c, stdout) == EOF) { return ioerr(program_name); }
}
}
if (is_term) { restore_echo(0); }
return EX_OK;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 20242025 Emma Tebibyte <emma@tebibyte.media>
* 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
@ -46,21 +46,15 @@ use std::{
collections::VecDeque,
env::args,
fmt::{ self, Display, Formatter },
io::{ Error, Write, stdin, stdout },
io::stdin,
process::ExitCode,
};
use CalcType::*;
extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
use sysexits::EX_DATAERR;
#[derive(Clone, PartialEq, PartialOrd, Debug)]
/* enum CalcType is a type containing operations used in the calculator */
@ -120,46 +114,12 @@ impl Display for CalcType {
#[derive(Debug, Clone)]
struct EvaluationError {
message: String,
code: u8,
code: i32,
}
impl StrError for EvaluationError {
fn strerror(&self) -> String {
self.message.clone()
}
}
fn err<T: StrError>(argv0: &String, e: &T, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn operate(
mut stack: VecDeque<f64>,
op: CalcType,
) -> Result<VecDeque<f64>, EvaluationError> {
let vals = (stack.pop_back(), stack.pop_back());
if let (Some(x), Some(y)) = vals {
match op {
Add => stack.push_back(y + x),
Subtract => stack.push_back(y - x),
Multiply => stack.push_back(y * x),
Divide => stack.push_back(y / x),
Power => stack.push_back(y.powf(x)),
Floor => stack.push_back((y / x).floor()),
Modulo => stack.push_back(y % x),
_ => {},
};
} else {
return Err(EvaluationError {
message: format!("{}: unexpected operation", op),
code: EX_DATAERR,
})
}
Ok(stack)
}
/* Im no math nerd but I want the highest possible approximation of 0.9
* repeating and it seems this can give it to me */
const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0;
fn eval(
input: &str,
@ -179,7 +139,6 @@ fn eval(
.rev()
.map(|t| CalcType::from(t))
.collect();
let mut ops: VecDeque<CalcType> = VecDeque::new();
while let Some(n) = toks.pop_back() {
@ -187,15 +146,36 @@ fn eval(
Val(v) => stack.push_back(v),
Invalid(i) => {
return Err(EvaluationError {
message: format!("{}: invalid token", i),
message: format!("{}: Invalid token", i),
code: EX_DATAERR,
})
},
op => {
ops.push_back(op.clone());
oper = true;
oper = true; /* this is an operation */
return operate(stack, op).map(|s| (s, oper));
let vals = (
stack.pop_back(),
stack.pop_back(),
);
if let (Some(x), Some(y)) = vals {
match op {
Add => stack.push_back(y + x),
Subtract => stack.push_back(y - x),
Multiply => stack.push_back(y * x),
Divide => stack.push_back(y / x),
Power => stack.push_back(y.powf(x)),
Floor => stack.push_back((y / x).floor()),
Modulo => stack.push_back(y % x),
_ => {},
};
} else {
return Err(EvaluationError {
message: format!("{}: Unexpected operation", op),
code: EX_DATAERR,
})
}
},
};
}
@ -204,46 +184,42 @@ fn eval(
}
/* Round a float to the given precision level */
fn round_precise(value: &f64) -> f64 {
/* Set floating-point precision for correcting rounding errors based on
* machine epsilon */
let precision = (-f64::EPSILON.log10()).floor() as i32;
let multiplier = 10_f64.powi(precision);
fn round_precise(value: &f64, precision: usize) -> f64 {
let multiplier = 10_f64.powi(precision as i32);
(value * multiplier).round() / multiplier
}
/* print the stack and let the caller know if evaluation should continue */
fn unstack(stack: VecDeque<f64>, op: bool) -> Result<bool, Error> {
if let Some(val) = stack.iter().last() {
if !op { return Ok(true); }
let out = round_precise(val).to_string() + &'\n'.to_string();
return stdout().write_all(out.as_bytes()).map(|_| true);
} else {
return Ok(false);
}
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], &e, Some(EX_OSERR));
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], &e, Some(EX_OSERR));
}
}
let mut stack = VecDeque::new();
let mut buf = String::new();
/* Set floating-point precision for correcting rounding errors based on
* machine epsilon */
let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize;
if argv.get(1).is_none() { /* read from stdin */
while let Ok(_) = stdin().read_line(&mut buf) {
match eval(&buf.trim(), stack) {
Ok(s) => {
buf.clear();
stack = s.0.clone();
if argv.get(1).is_some() { /* read expressions from argv */
let val = match stack.iter().last() {
Some(v) => v,
None => break,
};
if s.1 == false { continue; }
println!("{}", round_precise(val, precision).to_string());
},
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(err.code as u8);
},
};
}
} else { /* read from argv */
/* join argv into an owned String joined by spaces minus argv[0] */
let input = argv
.iter()
@ -254,44 +230,20 @@ fn main() -> ExitCode {
match eval(&input, stack) {
Ok(s) => {
/* we can ignore the return value of unstack() because we are
* not continually evaluating from stdin */
if let Err(e) = unstack(s.0.clone(), s.1.clone()) {
return err(&argv[0], &e, Some(EX_IOERR));
}
stack = s.0.clone();
return ExitCode::SUCCESS;
},
Err(e) => {
return err(&argv[0], &e, Some(e.code));
},
};
}
/* else, read from stdin */
loop { /* take input until EOF */
if let Err(e) = stdin().read_line(&mut buf) {
return err(&argv[0], &e, Some(EX_IOERR));
}
match eval(&buf.trim(), stack) {
Ok(s) => {
buf.clear();
stack = s.0.clone(); /* out with the old, in with the new */
match unstack(s.0, s.1) {
Ok(b) if b => continue,
Ok(_) => break,
Err(e) => {
return err(&argv[0], &e, Some(EX_IOERR))
},
let val = match stack.iter().last() {
Some(v) => v,
None => return ExitCode::SUCCESS,
};
println!("{}", round_precise(val, precision).to_string())
},
Err(e) => {
return err(&argv[0], &e, Some(e.code));
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(err.code 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);
}

94
src/scrut.c Normal file
View File

@ -0,0 +1,94 @@
/*
* 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
* 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 <stdio.h> /* fprintf(3), stderr, NULL */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <string.h> /* memset(3), strchr(3) */
#include <sysexits.h> /* EX_USAGE */
#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 */
char *program_name = "scrut";
#define OPTS "bcdefgkprsuwxLS"
static char *opts = OPTS;
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-" OPTS "] file...\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
char sel[(sizeof opts) / (sizeof *opts)];
if (argc < 2) { return usage(argv[0] == NULL ? program_name : argv[0]); }
{ /* option parsing */
char *p;
memset(sel, '\0', sizeof sel);
for (int c; (c = getopt(argc, argv, opts)) != -1;) {
if ((p = strchr(opts, c)) == NULL) { return usage(argv[0]); }
else { sel[p - opts] = c; }
}
/* straighten out selections; permute out nulls */
p = sel;
for (size_t i = 0; i < (sizeof sel) / (sizeof *sel); ++i) {
if (sel[i] != '\0') {
*p = sel[i];
if (&sel[i] != p++) { sel[i] = '\0'; }
}
}
}
if (optind == argc) { return usage(argv[0]); }
for (argv += optind ; *argv != NULL; ++argv) {
struct stat buf;
if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) {
return EXIT_FAILURE; /* doesn't exist or isn't stattable */
}
for (size_t i = 0; sel[i] != '\0'; ++i) {
if (
(sel[i] == 'b' && !S_ISBLK(buf.st_mode))
|| (sel[i] == 'c' && !S_ISCHR(buf.st_mode))
|| (sel[i] == 'd' && !S_ISDIR(buf.st_mode))
|| (sel[i] == 'e' && 0)
|| (sel[i] == 'f' && !S_ISREG(buf.st_mode))
|| (sel[i] == 'g' && !(buf.st_mode & S_ISGID))
|| (sel[i] == 'k' && !(buf.st_mode & S_ISVTX))
|| (sel[i] == 'p' && !S_ISFIFO(buf.st_mode))
|| (sel[i] == 'r' && access(*argv, R_OK) != 0)
|| (sel[i] == 'u' && !(buf.st_mode & S_ISUID))
|| (sel[i] == 'w' && access(*argv, W_OK) != 0)
|| (sel[i] == 'x' && access(*argv, X_OK) != 0)
|| (sel[i] == 'L' && !S_ISLNK(buf.st_mode))
|| (sel[i] == 'S' && !S_ISSOCK(buf.st_mode))
) { return EXIT_FAILURE; }
}
}
return EXIT_SUCCESS;
}

View File

@ -19,14 +19,10 @@
#include <ctype.h>
#include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3), perror(3) */
#include <stdio.h> /* fprintf(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), unveil(2) */
#endif
#include <sysexits.h> /* EX_USAGE */
char *program_name = "str";
@ -59,16 +55,8 @@ usage(char *argv0) {
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 unveil", "") == -1 || unveil(NULL, NULL) == -1) {
perror(program_name);
return EX_OSERR;
}
#endif
if (argc < 3) { return usage(program_name); }
if (argc < 3) { return usage(argv[0] == NULL ? program_name : argv[0]); }
for ( /* iterate ctypes */
ctype = 0;

View File

@ -16,25 +16,13 @@
* 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), perror(3), stderr */
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2), unveil(2) */
#endif
#include <stdio.h> /* fprintf(3), stderr */
#include <sysexits.h> /* EX_OK, EX_USAGE */
char *program_name = "strcmp";
int main(int argc, char *argv[]) {
unsigned int i;
#ifdef __OpenBSD__
if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL) == -1) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}
#endif
int i;
if (argc < 3) {
(void)fprintf(

View File

@ -32,33 +32,23 @@ use getopt::GetOpt;
use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE };
use strerror::StrError;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
fn err(argv0: &String, e: Error, code: u8) -> ExitCode {
fn oserr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code)
ExitCode::from(EX_OSERR as u8)
}
fn usage(s: &String) -> ExitCode {
fn ioerr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_IOERR as u8)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-w word_size]", s);
ExitCode::from(EX_USAGE)
ExitCode::from(EX_USAGE as u8)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e, EX_OSERR);
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e, EX_OSERR);
}
}
let mut buf: Vec<u8> = Vec::new(); // holds the sequence getting swabbed
let mut input = stdin();
let mut output = stdout().lock();
@ -86,22 +76,22 @@ fn main() -> ExitCode {
loop {
match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK), // read nothing; bye
Ok(0) => break ExitCode::from(EX_OK as u8), // read nothing; bye
Ok(v) if v == wordsize => { // read full block; swab
let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) {
break err(&argv[0], e, EX_IOERR);
break ioerr(&argv[0], e);
}
},
Ok(v) => { // partial read; partially write
if let Err(e) = output.write(&buf[..v]) {
break err(&argv[0], e, EX_IOERR);
break ioerr(&argv[0], e);
}
},
Err(e) => break err(&argv[0], e, EX_OSERR)
Err(e) => break oserr(&argv[0], e)
}
}
}

View File

@ -1,18 +1,9 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: CC0
*
* This work is marked with CC0 1.0. To view a copy of this license, visit
* <http://creativecommons.org/publicdomain/zero/1.0>.
*/
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */
#endif
int main(void) {
#ifdef __OpenBSD__
(void)pledge("stdio", "");
#endif
}
int main() {}

View File

@ -1,25 +0,0 @@
Tests
=====
The Harakit testing suite verifies the functionality of Harakit utilities
and checks for regressions and other issues relating to compliance to our
standards of practice.
Tests inherit the environment set by the top-level Makefile, which sets the BIN
variable to the build/bin directory at the root of the project; therefore, each
binary is located at $(BIN)/tool for idiomatic access.
Each test contains a set of PHONY targets which are prefixed with the name of
the tool being tested and an underscore. The first target is tests, which
depends on all the other targets in the test file. These test files are each
included in the top Makefile, so they can be called from the root of the
repository. This also means that BIN can be set manually so that tests can be
run using make(1) inside of the tests directory:
$ make -f dj.mk BIN=../build/bin dj_tests
--
Copyright © 20242025 Emma Tebibyte <emma@tebibyte.media>
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit
<http://creativecommons.org/licenses/by-sa/4.0/>.

View File

@ -1,48 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
/dev/full:
/dev/null:
.PHONY: dj_tests
dj_tests: dj_help dj_full dj_null # dj_skip_stdin
.PHONY: dj_full
# Linux has a /dev/full pseudodevice useful for testing errors.
dj_full: $(BIN)/dj /dev/full
case "$$(uname)" in \
Linux) \
! $(BIN)/dj -Hi /dev/zero -o /dev/full 2>&1 \
| tee /dev/stderr \
| xargs -I out test '1+0 > 0+0; 1024 > 0' = out \
;; \
esac
.PHONY: dj_help
dj_help: $(BIN)/dj
! $(BIN)/dj -h
.PHONY: dj_null
# Read nothing from /dev/null, write nothing to /dev/null.
dj_null: $(BIN)/dj /dev/null
$(BIN)/dj -Hi /dev/null -o /dev/null 2>&1 \
| tee /dev/stderr \
| xargs -I out test '0+0 > 0+0; 0 > 0' = out
# This test currently fails. This is probably due to dj(1) being stale relative
# to the main harakit branch. TODO: Reassess once the testing branch is merged.
# .PHONY: dj_skip_stdin
# # Test skipping stdin.
# dj_skip_stdin: $(BIN)/dj
# # Pipe 1024B of '\0' into dj(1); skip the first 24B; expect 1000B written.
# dd count=1 bs=1024 </dev/zero 2>/dev/null \
# | $(BIN)/dj -H -s 24 -o /dev/null 2>&1 \
# | tee /dev/stderr \
# | xargs -I out test '1+0 > 1+0; 1024 > 1000' = out

View File

@ -1,18 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: false_tests
false_tests: false_test false_help
.PHONY: false
false_test: $(BIN)/false
! $(BIN)/false
.PHONY: false_help
false_help: $(BIN)/false
! $(BIN)/false -h

View File

@ -1,46 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: fileis_tests
fileis_tests: fileis_help fileis_options
.PHONY: fileis_help
fileis_help: $(BIN)/fileis
! $(BIN)/fileis -h
.PHONY: fileis_options
# fileis tests file attributes, but files of a certain attribute aren't
# guaranteed to be present on a system. This test checks all of the files in
# harakit and, if test(1p) says a file matches a certain attribute, then checks
# fileis.
# opts are space-delimited (for command splitting), sel is not
fileis_options: $(BIN)/fileis
set -e; \
opts="b c d e f g k p r s u w x L S"; \
sel=; \
find . -name .git -prune -o -print \
| while read -r f; do \
for opt in $$opts; \
do if ! printf "%s\n" $$sel | grep $$opt >/dev/null; then \
if test -$$opt "$$f"; then \
if ! $(BIN)/fileis -$$opt "$$f"; \
then printf "[!!] fileis -%s failed on %s.\n" \
$$opt "$$f"; \
fi; \
sel="$$sel$$opt"; \
printf "[OK] Tested fileis -%s using %s\n" \
$$opt "$$f"; \
fi; \
fi; \
done; \
if printf "%s\n" "$$opts" | sed 's/ //g' | xargs test "$$sel" =; \
then break; \
fi; \
done

View File

@ -1,32 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: fop_tests
fop_tests: fop_functionality fop_delimiter fop_help fop_fail
.PHONY: fop_help
fop_help: $(BIN)/fop
! $(BIN)/fop -h
.PHONY: fop_delimiter
fop_delimiter: $(BIN)/fop
test "$$(printf 'test1 test1 test1\n' | $(BIN)/fop -d' ' 2 sed 's/1/4/g')" \
= 'test1 test1 test4'
test "$$(printf 'meowsetwoofsetribbit\n' \
| $(BIN)/fop -d 'set' 1 sed 's/woof/meow/g')" = 'meowsetmeowsetribbit'
.PHONY: fop_fail
fop_fail: $(BIN)/fop
! printf 'test\n' | $(BIN)/fop 1 cat
! printf 'test\n' | $(BIN)/fop 'test' cat
! printf 'test\n' | $(BIN)/fop -d'test' cat
! $(BIN)/fop
.PHONY: fop_functionality
fop_functionality: $(BIN)/fop
test "$$(printf 'test1\036test1\036test1\n' | $(BIN)/fop 1 sed 's/1/4/g')" \
= "$$(printf 'test1\036test4\036test1\n')"

View File

@ -1,32 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: hru_tests
hru_tests: hru_help hru_functionality hru_negative hru_regressions
.PHONY: hru_help
hru_help: $(BIN)/hru
! $(BIN)/hru -h
.PHONY: hru_functionality
hru_functionality: $(BIN)/hru
test "$$(printf '1234\n' | $(BIN)/hru)" = '1.2 kB'
test "$$(printf '0\n' | $(BIN)/hru)" = '0 B'
.PHONY: hru_negative
hru_negative: $(BIN)/hru
! printf '%s\n' '-1' | $(BIN)/hru
.PHONY: hru_regressions
hru_regressions: $(BIN)/hru
n=1; \
while true; \
do \
printf '%s\n' "$$n" | $(BIN)/hru || break; \
n="$$(($$n * 10))"; \
done; \
printf 'Max float: %s\n' "$$n"

View File

@ -1,67 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: intcmp_tests
intcmp_tests: intcmp_help intcmp_none intcmp_e intcmp_g intcmp_l intcmp_combined
.PHONY: intcmp_help
intcmp_help: $(BIN)/intcmp
! $(BIN)/intcmp -h
# These test that integer comparisons are working as they should. For the sake
# of readability (to facilitate faster skimming) these recipes follow a
# columned format:
# $binary -flags d d d d # op
# For flag meanings reference intcmp(1) (though they are somewhat self
# explanatory). d here refers to a decimal number; a mixture of 1s, 2s, and 3s
# (a particularly lovely number) arranged to demonstrate easily the operation
# under scrutiny. The commented op is the operation that is true for the given
# numbers. For example:
# $(BIN)/intcmp -e 3 3 3 3 # ==
# op here is ==; 3 == 3 == 3 == 3. The flag being used is -e, to test for
# equality, so this test should succeed.
# ! $(BIN)/intcmp -l 3 2 1 # >
# op here is >; 3 > 2 > 1. The flag being used is -l, to test for each integer
# being less than the next, so intcmp should fail - hence the ! at the start of
# the invocation. If this test failed, intcmp(1) would be confusing -l for -g,
# so that would be a good place to start looking for bugs.
.PHONY: intcmp_none
intcmp_none: $(BIN)/intcmp
! $(BIN)/intcmp 1 2
.PHONY: intcmp_e
intcmp_e: $(BIN)/intcmp
$(BIN)/intcmp -e 3 3 3 # ==
! $(BIN)/intcmp -e 1 2 3 # <
! $(BIN)/intcmp -e 3 2 1 # >
.PHONY: intcmp_g
intcmp_g: $(BIN)/intcmp
$(BIN)/intcmp -g 3 2 1 # >
! $(BIN)/intcmp -g 3 3 3 # ==
! $(BIN)/intcmp -g 1 2 3 # <
$(BIN)/intcmp -ge 3 3 1 # >=
! $(BIN)/intcmp -ge 1 2 3 # <
.PHONY: intcmp_l
intcmp_l: $(BIN)/intcmp
$(BIN)/intcmp -l 1 2 3 # <
! $(BIN)/intcmp -l 3 3 3 # ==
! $(BIN)/intcmp -l 3 2 1 # >
$(BIN)/intcmp -le 1 3 3 # <=
! $(BIN)/intcmp -le 3 2 1 # >
.PHONY: intcmp_combined
intcmp_combined: $(BIN)/intcmp
$(BIN)/intcmp -gl 1 2 3 # <
$(BIN)/intcmp -gl 3 2 1 # >
$(BIN)/intcmp -gl 1 3 1 # !=
! $(BIN)/intcmp -gl 3 3 3 # ==
$(BIN)/intcmp -egl 3 1 1 3 # >, ==, <
! $(BIN)/intcmp -egl foo # huh?

View File

@ -1,40 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# 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_remaining_options
.PHONY: mm_none
mm_none: $(BIN)/mm
test "$$(printf 'meow\n' | $(BIN)/mm)" = meow
.PHONY: mm_args
# mm(1) will error if positional arguments are given without -i or -o
mm_args: $(BIN)/mm
! $(BIN)/mm argument
.PHONY: mm_help
mm_help: $(BIN)/mm
! $(BIN)/mm -h
.PHONY: mm_stderr
# 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
.PHONY: mm_remaining_options
# check to make sure mm -i with trailing arguments interprets -o as one
mm_remaining_options:
! $(BIN)/mm -i README COPYING -o - 2>&1 | cut -d: -f2 \
| xargs test " -o" =

View File

@ -1,94 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 20242025 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: npc_tests
npc_tests: npc_help npc_args npc_ascii
.PHONY: npc_help
npc_help: $(BIN)/npc
! $(BIN)/npc -h
.PHONY: npc_args
# arg parsing
npc_args:
$(BIN)/npc -e </dev/null
$(BIN)/npc -t </dev/null
$(BIN)/npc -et </dev/null
! $(BIN)/npc -et 5 </dev/null
.PHONY: npc_ascii
# Test 0x00 to 0x7f in input; in other words, the full 7b ASCII range.
npc_ascii: npc_ascii_controls npc_ascii_uppers # npc_ascii_symbols \
# npc_ascii_lowers
.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" =
# ASCII 0x0a (otherwise the head|tail sequence won't work) to 0x1f
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); print }' \
| $(BIN)/npc \
| head -n 2 \
| tail -n 1 \
| xargs -I out test "^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_"
# This test is broken on Linux and will need closer inspection along with GNU
# xargs(1).
# .PHONY: npc_ascii_symbols
# # ASCII 0x1f to 0x3f (^_ and symbols)
# npc_ascii_symbols:
# # shell quoting olympics
# c="$(awk 'BEGIN{ for (i = 31; i < 64; ++i) printf("%c", i); print }')"
#
# printf '%s\n' "$c" | $(BIN)/npc \
# | sed -e s"/\'/\\\'/g" -e 's/"/\\"/g' \
# | tr -d '\n' \
# | xargs -I out test "^_ !\"#$$%&\'()*+,-./0123456789:;<=>?" = out
.PHONY: npc_ascii_uppers
# ASCII 0x40 to 0x5f (uppercases)
npc_ascii_uppers:
awk 'BEGIN{ for (i = 64; i < 96; ++i) printf("%c", i); print }' \
| $(BIN)/npc \
| sed 's/\\/\\\\/' \
| xargs -I out test @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ = out
# This test is broken and will need closer inspection along with the npc(1)
# source.
# .PHONY: npc_ascii_lowers
# # ASCII 0x60 to 0x7f (lowercases)
# npc_ascii_lowers:
# awk 'BEGIN{ for (i = 96; i < 128; ++i) printf("%c", i); print }' \
# | $(BIN)/npc \
# | xargs -I out test "\`abcdefghijklmnopqrstuvwxyz{|}~^?" = out

View File

@ -1,24 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
# Testing peek is hard as it requires visual confirmation that text isn't being
# echoed. These tests don't go that far but are a start, and have already
# caught a bug in -i behavior.
.PHONY: peek_tests
peek_tests: peek_help peek_stdio
.PHONY: peek_help
peek_help: $(BIN)/peek
! $(BIN)/peek -h
.PHONY: peek_stdio
# Test peek -i
peek_stdio: $(BIN)/peek
printf 'Test.\n' \
| $(BIN)/peek -i \
| xargs test 'Test.' =

24
tests/posix-compat.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
set -e
if ! ls Makefile >/dev/null 2>&1
then
printf '%s: Run this script in the root of the project.\n' "$0" 1>&2
exit 1
fi
printf "Starting POSIX compatibility testing.\n"
for utility in tests/posix/*; do
printf '%s: %s: Testing utility.\n' "$0" "$utility"
"$utility"
printf '\n'
done

View File

@ -1,48 +0,0 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: rpn_tests
rpn_tests: rpn_help rpn_add rpn_sub rpn_mul rpn_div rpn_mod rpn_flr rpn_stdin
.PHONY: rpn_help
rpn_help: $(BIN)/rpn
! $(BIN)/rpn -h
.PHONY: rpn_add
rpn_add: $(BIN)/rpn
test "$$($(BIN)/rpn 1 2 +)" -eq 3
test "$$($(BIN)/rpn 0.2 0.1 +)" = 0.3
.PHONY: rpn_sub
rpn_sub: $(BIN)/rpn
test "$$($(BIN)/rpn 23 5 -)" -eq 18
test "$$($(BIN)/rpn 0.3 0.1 -)" = 0.2
.PHONY: rpn_mul
rpn_mul: $(BIN)/rpn
test "$$($(BIN)/rpn 1.2 3 '*')" = 3.6
test "$$($(BIN)/rpn 0 3 '*')" -eq 0
.PHONY: rpn_div
rpn_div: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 /)" = 2.4
test "$$($(BIN)/rpn 3 0 /)" = inf
.PHONY: rpn_mod
rpn_mod: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 %)" -eq 2
test "$$($(BIN)/rpn 9 4 %)" -eq 1
.PHONY: rpn_flr
rpn_flr: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 //)" -eq 2
test "$$($(BIN)/rpn 9 4 //)" -eq 2
# done last because all operations have been tested
.PHONY: rpn_stdin
rpn_stdin: $(BIN)/rpn
test "$$(printf '1\n2\n+\n3\n-\n' | $(BIN)/rpn | tail -n1)" -eq 0

View File

@ -1,20 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: str_tests
str_tests: str_help str_isalpha
.PHONY: str_help
str_help: $(BIN)/str
! $(BIN)/str -h
.PHONY: str_isalpha
str_isalpha: $(BIN)/str
$(BIN)/str isalpha c
! $(BIN)/str isalpha 3

View File

@ -1,31 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
NAME = strcmp
TARGET = $(NAME)_tests
BINARY = $(BIN)/$(NAME)
.PHONY: strcmp_tests
strcmp_tests: strcmp_equals strcmp_help strcmp_nocmp strcmp_unequals
.PHONY: strcmp_equals
strcmp_equals: $(BIN)/strcmp
$(BIN)/strcmp equals equals
$(BIN)/strcmp - -
.PHONY: strcmp_help
strcmp_help: $(BIN)/strcmp
! $(BIN)/strcmp -h
.PHONY: strcmp_nocmp
strcmp_nocmp: $(BIN)/strcmp
! $(BIN)/strcmp nocmp
.PHONY: strcmp_unequals
strcmp_unequals: $(BIN)/strcmp
! $(BIN)/strcmp unequals equals

View File

@ -1,22 +0,0 @@
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: swab_tests
swab_tests: swab_help swab_examples
.PHONY: swab_help
swab_help: $(BIN)/swab
! $(BIN)/swab -h
.PHONY: swab_examples
# These are the examples present in the man page.
swab_examples: $(BIN)/swab
printf 'hello world!\n' \
| $(BIN)/swab \
| xargs -I out test 'ehll oowlr!d' = out

View File

@ -1,19 +0,0 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: true_tests
true_tests: true_test
.PHONY: true_help
true_help: $(BIN)/true
$(BIN)/true -h
.PHONY: true_test
true_test: $(BIN)/true
$(BIN)/true