diff --git a/CONDUCT b/CONDUCT new file mode 100644 index 0000000..4dd97a9 --- /dev/null +++ b/CONDUCT @@ -0,0 +1,83 @@ +Code of Conduct + +This Code of Conduct is derived from the 10 Pāramitās of Theravadin Buddhism. +You can read more about them in Ṭhānissaro Bhikkhu’s Ten Perfections: A Study +Guide [0]. + +1. Generosity (Dāna) + +Give contributions freely and willingly under the terms of the GNU Affero +General Public License, version 3 or later, or a compatible license. + +2. Ethics (Sīla) + +Do not use nonfree code or uncredited code in contributions. Do not contribute +code of dubious origins, such as code generated by large language models or +unlicensed snippets found online [1]. Do not take credit for others’ +contributions. Make sure to utilize the copyright header and license notice on +source files to credit yourself and others for their work. + +3. Renunciation (Nekkhamma) + +Stay committed to the principles of simplicity and interoperability embodied by +the project. Keep your personal will and desire out of the project, for it can +only prove harmful to its success. + +4. Wisdom (Pañña) + +Look to established sources for standards, best practices, and important +implementation details when setting new precedence. Follow the existing +precedence where it applies. + +5. Energy (Viriya) + +Focus on the currently-open, currently-assigned, and currently-in-progress +issues, pull requests, and other endeavors in order to keep yourself and others +from being overwhelmed with responsibility, either from your zeal or your +negligence. + +If you notice an issue, open an issue as soon as you can. If you see a neglected +branch, open a pull request or comment on an existing one, if applicable. Be +diligent in your commitment to making this project work. + +6. Patience (Khanti) + +Be patient with maintainers and other contributors. We all have our own lives +going on and may need significant time to get to things. + +7. Truthfulness (Sacca) + +Communicate honestly and openly. Do not embellish facts to get your way. Make +sure to let maintainers know about any issues along the way and keep ample +communication channels open. + +8. Determination (Adhiṭṭhāna) + +Stay focused on long-term objectives and cultivate attainment to that +achievement by utilizing to the fullest extent possible the tools available to +you for managing the workload. + +9. Loving-Kindness (Mettā) + +Treat everyone with respect, even if they treat you poorly. This does not mean +you have to put up with abuse, but make sure to respond with kindness and with +love in your heart. Support and uplift maintainers and other contributors with +your words and actions. + +Do not use angry or hateful language toward contributors, such as demeaning +phrases and slurs. Make sure that if you do not know the pronouns of a +contributor to ask for them and, in the meantime, use gender-neutral they/them +or equivalent pronouns. + +10. Equanimity (Upekkhā) + +Keep a balanced perspective on all suggestions and contributions and make +judgements not from a place of ego and personal preference but on their +usefulness and suitability to the project. Make sure to keep an eye on the +bigger picture as implementing individual features may seem intuitive at first +but scale poorly in practical use. Keep a level head about your own work: it is +not shameful to make a mistake in this vein, and fixing it usually leads to +more insight. + +[0] +[1] diff --git a/CONTRIBUTING b/CONTRIBUTING index b7ad7a8..267918b 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -1,3 +1,5 @@ +Make sure to read our code of conduct in the CONDUCT file. + 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 diff --git a/Makefile b/Makefile index 03f68fd..91d69a1 100644 --- a/Makefile +++ b/Makefile @@ -1,62 +1,88 @@ # Copyright (c) 2023–2024 Emma Tebibyte # Copyright (c) 2023–2024 DTB # Copyright (c) 2023 Sasha Koshka +# Copyright (c) 2024 Aaditya Aryal # 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. +# The octal escape \043 is utilized twice in this file as make(1p) will +# interpret a hash in a rule as an inline comment. + .POSIX: -.PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1) -.PRAGMA: command_comment # breaks without this? -PREFIX=/usr/local +DESTDIR ?= dist +PREFIX ?= /usr/local -CC=cc -MAKE=make -RUSTC=rustc +MANDIR != [ $(PREFIX) = / ] && printf '/usr/share/man\n' \ + || printf '/share/man\n' +SYSEXITS != printf '\043include \n' | cpp -M - | sed 's/ /\n/g' \ + | sed -n 's/sysexits\.h//p' || printf 'include\n' + +CC ?= cc +RUSTC ?= rustc +RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \ + --extern sysexits=build/o/libsysexits.rlib \ + --extern strerror=build/o/libstrerror.rlib +CFLAGS += -I$(SYSEXITS) .PHONY: all -all: dj false fop hru intcmp rpn scrut str strcmp true +all: docs dj false fop hru intcmp mm npc rpn scrut str strcmp swab true +# keep build/include until bindgen(1) has stdin support +# https://github.com/rust-lang/rust-bindgen/issues/2703 build: - # keep build/include until bindgen(1) has stdin support - # https://github.com/rust-lang/rust-bindgen/issues/2703 - mkdir -p build/bin build/include build/lib build/o build/test + mkdir -p build/bin build/docs build/include build/lib build/o build/test .PHONY: clean clean: - rm -rf build/ dist/ + rm -rf build dist dist: all - mkdir -p dist/bin dist/share/man/man1 - cp build/bin/* dist/bin/ - cp docs/*.1 dist/share/man/man1/ + mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1 + cp build/bin/* $(DESTDIR)/$(PREFIX)/bin + cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1 .PHONY: install install: dist - mkdir -p $(PREFIX) - cp -r dist/* $(PREFIX)/ + cp -r $(DESTDIR)/* / .PHONY: test -test: all +test: all build /tmp/getopt tests/test.sh - $(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt + /tmp/getopt -build/o/libsysexits.rlib: build - # bandage solution until bindgen(1) gets stdin support - printf '#define EXIT_FAILURE 1\n' | cat - include/sysexits.h \ - > build/include/sysexits.h - bindgen --default-macro-constant-type signed --use-core --formatter=none \ - "$$(printf '#include \n' \ - | cpp -M -idirafter "build/include" - \ - | sed 's/ /\n/g' | grep sysexits.h)" \ - | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o build/o/libsysexits.rlib - +/tmp/getopt: src/libgetopt.rs + $(RUSTC) --test -o /tmp/getopt src/libgetopt.rs -build/o/libgetopt.rlib: src/getopt-rs/lib.rs +.PHONY: docs +docs: docs/ build + for file in docs/*; do original="$$(sed -n '/^\.TH/p' <"$$file")"; \ + title="$$(printf '%s\n' "$$original" | sed \ + "s/X\.X\.X/$$(git describe --tags --long | cut -d'-' -f1)/g")"; \ + sed "s/$$original/$$title/g" <"$$file" >"build/$$file"; done + +.PHONY: rustlibs +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 \ - -o build/o/libgetopt.rlib src/getopt-rs/lib.rs + -o $@ src/libgetopt.rs + +build/o/libstrerror.rlib: build src/libstrerror.rs + $(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \ + src/libstrerror.rs + +build/o/libsysexits.rlib: build/include/sysexits.h + 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 +build/include/sysexits.h: build $(SYSEXITS)sysexits.h + printf '\043define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h > $@ .PHONY: dj dj: build/bin/dj @@ -70,29 +96,33 @@ build/bin/false: src/false.c build .PHONY: fop fop: build/bin/fop -build/bin/fop: src/fop.rs build build/o/libgetopt.rlib build/o/libsysexits.rlib - $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ - --extern sysexits=build/o/libsysexits.rlib \ - -o $@ src/fop.rs +build/bin/fop: src/fop.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/fop.rs .PHONY: hru hru: build/bin/hru -build/bin/hru: src/hru.rs build build/o/libgetopt.rlib build/o/libsysexits.rlib - $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ - --extern sysexits=build/o/libsysexits.rlib \ - -o $@ src/hru.rs +build/bin/hru: src/hru.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/hru.rs .PHONY: intcmp intcmp: build/bin/intcmp build/bin/intcmp: src/intcmp.c build $(CC) $(CFLAGS) -o $@ src/intcmp.c +.PHONY: mm +mm: build/bin/mm +build/bin/mm: src/mm.c build + $(CC) $(CFLAGS) -o $@ src/mm.c + +.PHONY: npc +npc: build/bin/npc +build/bin/npc: src/npc.c build + $(CC) $(CFLAGAS) -o $@ src/npc.c + .PHONY: rpn rpn: build/bin/rpn -build/bin/rpn: src/rpn.rs build build/o/libsysexits.rlib - $(RUSTC) $(RUSTFLAGS) \ - --extern sysexits=build/o/libsysexits.rlib \ - -o $@ src/rpn.rs +build/bin/rpn: src/rpn.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/rpn.rs .PHONY: scrut scrut: build/bin/scrut @@ -109,6 +139,11 @@ strcmp: build/bin/strcmp build/bin/strcmp: src/strcmp.c build $(CC) $(CFLAGS) -o $@ src/strcmp.c +.PHONY: swab +swab: build/bin/swab +build/bin/swab: src/swab.rs build rustlibs + $(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/swab.rs + .PHONY: true true: build/bin/true build/bin/true: src/true.c build diff --git a/README b/README index accfc7e..fdb05fb 100644 --- a/README +++ b/README @@ -1,28 +1,29 @@ “Seek not to walk the path of the masters; seek what they sought.” – Matsuo Basho -The Bonsai core utilities are the result of the careful examination of the -current state of POSIX and Unix utilies. The Unix Philosophy, “do one thing and -do it well” is its core but these tools do not cling to the names of 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 era of the original Unix tools has been long and fruitful, but they have -their flaws. The new, non-POSIX era of this project started with frustration -with the way certain tools work and how other projects that extend POSIX don’t -make anything better. +their flaws. This project originated from frustrations with the way certain +tools work and how other projects that extend POSIX don’t make anything better. 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 -Bonsai core utilities will intentionally discourage use of the shell for -purposes beyond its scope. +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 -The coreutils require a POSIX-compliant environment to compile, including a C -compiler and preprocessor (cc(1) and cpp(1) by default) with the -idirafter -flag, a Rust compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant -make(1) utility. +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 +compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant make(1) +utility. To build and install: @@ -38,7 +39,7 @@ To test the utilities: $ make test -To remove all untracked files: +To remove all build and distributable files: $ make clean diff --git a/docs/dj.1 b/docs/dj.1 index 1e286f5..7031ccf 100644 --- a/docs/dj.1 +++ b/docs/dj.1 @@ -1,14 +1,13 @@ .\" Copyright (c) 2024 DTB +.\" Copyright (c) 2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH dj 1 - +.\" +.TH DJ 1 2024-06-17 "Harakit X.X.X" .SH NAME - dj \(en disk jockey - +.\" .SH SYNOPSIS dj @@ -19,142 +18,172 @@ dj .RB [ count ]) .RB ( -i -.R [ -.B input file -.R ]) +[\fBinput file\fP]) .RB ( -b -.R [ -.B input block size -.R ]) +[\fBinput block size\fP]) .RB ( -s -.R [ -.B input offset -.R ]) +[\fBinput offset\fP]) .RB ( -o -.R [ -.B output file -.R ]) +[\fBoutput file\fP]) .RB ( -B -.R [ -.B output block size -.R ]) +[\fBoutput block size\fP]) .RB ( -S -.R [ -.B output offset -.R ]) +[\fBoutput offset\fP]) +.\" +.SH DESCRIPTION -.SH USAGE +Perform precise read and write operations on files. This utility is useful for +reading and writing binary data to and from disks. -The -.B -i -option takes a path as an argument to open and use in place of standard input. -The -.B -o -option does the same in place of standard output. Dj does not truncate output -files and instead writes over the bytes in the existing file. -.PP -The +This manual page uses the terms \(lqskip\(rq and \(lqseek\(rq to refer to moving +to a specified byte by index in the input and output of the program +respectively. This language is inherited from the +.BR dd (1p) +utility and used here to decrease ambiguity. + +When seeking or skipping to a byte, writing or reading starts at the byte +immediately subsequent to the specified byte. +.\" +.SH OPTIONS + +.IP \fB-i\fP +Takes a file path as an argument and opens it for use as an input. +.IP \fB-b\fP +Takes a numeric argument as the size in bytes of the input buffer, the default +being 1024. +.IP \fB-s\fP +Takes a numeric argument as the number of bytes to skip into the input +before starting to read. If the standard input is used, bytes read to this point +are discarded. +.IP \fB-o\fP +Takes a file path as an argument and opens it for use as an output. +.IP \fB-B\fP +Does the same as .B -b -option takes a numeric argument as the size in bytes of the input buffer and -the -.B -B -option does the same for the output buffer, the default for both being 1024 -bytes, or one kibibyte (KiB). -.PP -The -.B -s -option takes a numeric argument as the number of bytes to skip into the input -before starting to read, and the -.B -S -option skips a number of bytes through the output before starting to write from -the input. If the input is a stream the bytes are read and discarded. If the -output is a stream, nul characters are printed. -.PP -The +but for the output buffer. +.IP \fB-S\fP +Seeks a number of bytes through the output before starting to write from +the input. If the output is a stream, null characters are printed. +.IP \fB-a\fP +Accepts a single literal byte with which the input buffer is padded in the event +of an incomplete read from the input file. +.IP \fB-A\fP +Specifying this option pads the input buffer with null bytes in the event of an +incomplete read. This is equivalent to specifying .B -a -option takes one argument of one byte in length and pads the input buffer with -that byte in the event that a read doesn't fill the input buffer, and the -.B -A -option takes no arguments and pads with nuls. -The -.B -c -option specifies an amount of reads to make, and if 0 (the default) dj will -continue reading until a partial or empty read. -.PP -On a partial or empty read, dj prints a diagnostic message (unless the -.B -q -option is specified) and exits (unless the -.B -n -option is specified, in which case only two consecutive empty reads will cause -dj to exit). -At exit, usage statistics are printed unless the option -.B -q -is specified a second time. The -.B -H -option will make these diagnostics human-readable. +with a null byte instead of a character. +.IP \fB-c\fP +Specifies a number of reads to make. The default is 0, in which case the +input is read until a partial or empty read is made. +.IP \fB-d\fP +Prints invocation information before program execution as described in the +DIAGNOSTICS section. Each invocation increments the debug level of the +program. +.IP \fB-H\fP +Prints diagnostics messages in a human-readable manner as described in the +DIAGNOSTICS section. +.IP \fB-n\fP +Retries failed reads once before exiting. +.IP \fB-q\fP +Suppresses error messages which print when a read or write is partial or +empty. Each invocation decrements the debug level of the program. +.\" +.SH STANDARD INPUT +The standard input shall be used as an input if no inputs are specified or if +one or more of the input files is \(lq-\(rq. +.\" +.SH STANDARD OUTPUT +The standard output shall be used as an output if no inputs are specified or if +one or more of the input files is \(lq-\(rq. +.\" .SH DIAGNOSTICS -The -.B -d -option prints all information, user-specified or otherwise, before program -execution. -.PP -When dj exits, by default statistics are printed for input and output to -standard error in the following format: -.PP -.R {records read} {ASCII unit separator} {partial records read} -.R {ASCII record separator} {records written} {ASCII unit separator} -.R {partial records written} {ASCII group separator} {bytes read} -.R {ASCII record separator} {bytes written} {ASCII file separator} -.PP -If the -.B -H -option is specified dj instead uses this following format: -.PP -.R {records read} '+' {partial records read} '>' {records written} -.R '+' {partial records written} ';' {bytes read} '>' {bytes written} -.R {ASCII line feed} -.PP -The +On a partial or empty read, unless the .B -q -option suppresses error messages which print when a read or write is partial or -empty and when used twice suppresses diagnostic output entirely. -.PP -In non-recoverable errors that don't pertain to dj's read-write cycle, a -diagnostic message is printed and dj exits with the appropriate sysexits(3) -status. +option is specified, a diagnostic message is printed. Then, the program exits +unless the +.B -n +option is specified. +By default, statistics are printed for input and output to the standard error in +the following format: + +.RS +{records read} {ASCII unit separator} {partial records read} +{ASCII record separator} {records written} {ASCII unit separator} +{partial records written} {ASCII group separator} {bytes read} +{ASCII record separator} {bytes written} {ASCII file separator} +.RE + +This format for diagnostic output is designed to be machine-parseable for +convenience. For a more human-readable format, the +.B -H +option may be specified. In this event, the following format is used instead: + +.RS +{records read} '+' {partial records read} '>' {records written} +'+' {partial records written} ';' {bytes read} '>' {bytes written} +{ASCII line feed} +.RE + +If the +.B -d +option is specified, debug information will be printed at the beginning of +execution. This output contains information regarding how the program was +invoked. The following example is the result of running the program with +.B -d +as the only argument: + +.RS +argv0=dj +in= ibs=1024 skip=0 align=ff count=0 +out= obs=1024 seek=0 debug= 3 noerror=0 +.RE + +In non-recoverable errors that don\(cqt pertain to the read-write cycle, a +diagnostic message is printed and the program exits with the appropriate +.BR sysexits.h (3) +status. +.\" .SH BUGS If .B -n -is specified along with a specified count, actual byte output may be lower than -expected (the product of the count multiplied by the input block size). If the +is specified along with the +.B -c +option and a count, actual byte output is the product of the count and the input +block size and therefore may be lower than expected. If the .B -a or .B -A -options are used this could make data written nonsensical. -.PP +options are specified, this could make written data nonsensical. +.\" +.SH CAVEATS + +Existing files are not truncated on ouput and are instead overwritten. + Many lowercase options have capitalized variants and vice-versa which can be confusing. Capitalized options tend to affect output or are more intense versions of lowercase options. - +.\" .SH RATIONALE -Dj was modeled after the dd utility specified in POSIX but adds additional -features: typical option formatting, allowing seeks to be specified in bytes -rather than in blocks, allowing arbitrary bytes as padding, and printing in a -format that's easy to parse for machines. It also neglects character -conversion, which may be dd's original intent but is irrelevant to its modern -use. - +This program was based on the +.BR dd (1p) +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 +character conversion and adds typical option formatting, allowing seeks to be +specified in bytes rather than in blocks, allowing arbitrary bytes as padding, +and printing in a format that\(cqs easy for machines to parse. +.\" .SH COPYRIGHT -Copyright (C) 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -dd(1) +.BR dd (1p) diff --git a/docs/false.1 b/docs/false.1 index 6883802..b940909 100644 --- a/docs/false.1 +++ b/docs/false.1 @@ -1,35 +1,35 @@ .\" Copyright (c) 2022, 2024 DTB -.\" Copyright (c) 2023 Emma Tebibyte +.\" Copyright (c) 2023–2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH FALSE 1 - +.\" +.TH FALSE 1 2024-06-06 "Harakit X.X.X" .SH NAME - false \(en do nothing, unsuccessfully - +.\" .SH DESCRIPTION -False does nothing regardless of operands or standard input. -False will always return an exit code of 1. - +Do nothing regardless of operands or standard input. An exit code of 1 will +always be returned. +.\" .SH RATIONALE -False exists for the construction of control flow and loops based on a failure. - -False functions as described in POSIX.1-2017. - +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. +.\" .SH AUTHOR -Written by Emma Tebibyte . - +Written by Emma Tebibyte +.MT emma@tebibyte.media +.ME . +.\" .SH COPYRIGHT This work is marked with CC0 1.0. To see a copy of this license, visit . - +.\" .SH SEE ALSO - -true(1p) +.BR true (1p) diff --git a/docs/fop.1 b/docs/fop.1 new file mode 100644 index 0000000..d777c68 --- /dev/null +++ b/docs/fop.1 @@ -0,0 +1,62 @@ +.\" Copyright (c) 2024 DTB +.\" Copyright (c) 2024 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . +.\" +.TH FOP 1 2024-06-17 "Harakit X.X.X" +.SH NAME +fop \(en field operator +.\" +.SH SYNOPSIS + +fop +.RB ( -d ) +.RB [ delimiter ] +.RB index +.RB program... +.\" +.SH DESCRIPTION + +Performs operations on specified fields in data read from the standard input. +.\" +.SH OPTIONS + +.IP \fB-d\fP +Sets a delimiter by which the input data will be split into fields. The default +is an ASCII record separator. +.\" +.SH CAVEATS + +Field indices are zero-indexed, which may be unexpected behavior for some users. +.\" +.SH RATIONALE + +With the assumption that tools will output data separated with ASCII field +separators, there is a need for the ability to modify select fields in this data +easily and quickly. + +The idea for this utility originated in the fact that the GNU +.BR ls (1) +utility contains a +.B -h +option which enables human-readable units in file size outputs. This +functionality was broken out into +.BR hru (1), +but there was no easy way to modify the field in the ouput of +.BR ls (1p) +without creating a new tool. +.\" +.SH AUTHOR + +Written by Emma Tebibyte +.MT emma@tebibyte.media +.ME . +.\" +.SH COPYRIGHT + +Copyright \(co 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later +. +.\" +.SH SEE ALSO +.BR sed (1p) diff --git a/docs/hru.1 b/docs/hru.1 index c75cb73..6929d51 100644 --- a/docs/hru.1 +++ b/docs/hru.1 @@ -2,56 +2,68 @@ .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH rpn 1 - +.\" +.TH HRU 1 2024-06-17 "Harakit X.X.X" .SH NAME - hru \(en human readable units - +.\" .SH SYNOPSIS hru - +.\" .SH DESCRIPTION -Hru reads byte counts in the form of whole numbers from the standard input and -writes to the standard output the same number converted one of the units of data -defined by the International System of Units. +Convert counts to higher units. + +Byte counts will be read in the form of whole numbers from the standard input +and be written to the standard output the same number converted to a higher unit +of data as defined by the \fIInternational System of Units\fP. The program will convert the byte count to the highest unit possible where the value is greater than one. - +.\" .SH DIAGNOSTICS -If encountering non-integer characters in the standard input, hru will exit with -the appropriate error code as defined by sysexits.h(3) and print an error -message. - +If encountering non-integer characters in the standard input, the program will +exit with the appropriate error code as defined by +.BR sysexits.h (3) +and print an error message. +.\" .SH RATIONALE -The GNU project’s ls(1) implementation contains a human-readable option (-h) -that, when specified, makes the tool print size information in a format more -immediately readable. This functionality is useful not only in the context of -ls(1) so the decision was made to split it into a new tool. The original -functionality in GNU’s ls(1) can be emulated with fop(1) combined with this -program. - +The GNU project\(cqs +.BR ls (1) +implementation contains a human-readable option (\fB-h\fP) that, when specified, +makes the tool print size information in a format more immediately +readable. This functionality is useful not only in this context, so the decision +was made to split it into a new tool. The original functionality from GNU\(cqs +.BR ls (1) +can be emulated with +.BR fop (1) +combined with this program. +.\" .SH STANDARDS -Hru follows the standard unit prefixes as specified by the Bureau International -des Poids et Mesures (BIPM) in the ninth edition of The International System of -Units (SI). - +The standard unit prefixes as specified by the +.I Bureau International des Poids et Mesures +.RI ( BIPM ) +in the ninth edition of +.I The International System of Units +.RI ( SI ) +are utilized for the ouput of conversions. +.\" .SH AUTHOR -Written by Emma Tebibyte . - +Written by Emma Tebibyte +.MT emma@tebibyte.media +.ME . +.\" .SH COPYRIGHT -Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later +Copyright \(co 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -GNU ls(1), The International System of Units (SI) 9th Edition +GNU +.BR ls (1), +.I The International System of Units (SI) 9th Edition diff --git a/docs/intcmp.1 b/docs/intcmp.1 index f370755..034a4fd 100644 --- a/docs/intcmp.1 +++ b/docs/intcmp.1 @@ -1,78 +1,105 @@ .\" Copyright (c) 2023–2024 DTB -.\" Copyright (c) 2023 Emma Tebibyte +.\" Copyright (c) 2023–2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH intcmp 1 - +.\" +.TH INTCMP 1 2024-06-06 "Harakit X.X.X" .SH NAME - intcmp \(en compare integers - +.\" .SH SYNOPSIS intcmp -.RB ( -eghl ) +.RB ( -egl ) .RB [ integer ] .RB [ integer... ] - .SH DESCRIPTION +Compare integers to each other. +.\" +.SH OPTIONS -Intcmp compares integers. - -.SH USAGE - -The -e option permits given integers to be equal to each other. If combined -with -g or -l, only adjacent integers in the argument sequence can be equal. -.PP -The -g option permits a given integer to be greater than the following integer. -.PP -The -l option permits a given integer to be less than the following integer. -.PP -It may help to think of the -e, -g, and -l options as equivalent to the -infix algebraic “=”, “>”, and “<” operators respectively, with each option -putting its symbol between every given integer. For example, -.R intcmp -l 1 2 3 -is equivalent to evaluating "1 < 2 < 3". +.IP \fB-e\fP +Permits given integers to be equal to each other. +.IP \fB-g\fP +Permits a given integer to be greater than the following integer. +.IP \fB-l\fP +Permits a given integer to be less than the following integer. +.\" +.SH EXAMPLES +It may help to think of the +.BR -e , +.BR -g , +and +.B -l +options as equivalent to the infix algebraic \(lq=\(rq, \(lq>\(rq, and \(lq<\(rq +operators respectively, with each option putting its symbol between every given +integer. The following example is equivalent to evaluating \(lq1 < 2 < 3\(rq: +\" +.RS +intcmp -l 1 2 3 +.RE +.\" .SH DIAGNOSTICS -Intcmp exits 0 for a valid expression and 1 for an invalid expression. -.PP -Intcmp prints a debug message and exits with the appropriate sysexits(3) error -code in the event of an error. +The program will exit with a successfully for a valid expression and with an +error code of 1 for an invalid expression. +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 BUGS -There are multiple ways to express compound comparisons; “less than or equal -to” can be -le or -el, for example. -.PP -The inequality comparison is -gl or -lg for “less than or greater than”; this -is elegant but unintuitive. -.PP --egl, "equal to or less than or greater than", exits 0 no matter what for valid -program usage and may be abused to function as an integer validator. -Use str(1) instead. +.BR -egl , +\(lqequal to or less than or greater than\(rq, always exits successfully for +valid program usage and may be abused to function as an integer validator. Use +.BR str (1) +instead. +.\" +.SH CAVEATS +There are multiple ways to express compound comparisons; \(lqless than or equal +to\(rq can be +.B -le +or +.BR -el , +for example. + +The inequality comparison is +.B -gl +.B or +.B -lg +for \(lqless than or greater than\(rq; +this is elegant but unintuitive. +.\" .SH RATIONALE The traditional tool for integer comparisons in POSIX and other Unix shells has -been test(1). This tool also handles string comparisons and file scrutiny. -These parts of its functionality have been broken out into multiple utilities. - -Strcmp’s functionality may be performed on a POSIX-compliant system with -test(1p). +been +.BR test (1). +This tool also handles string 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 POSIX-compliant system +with +.BR test (1p). +.\" .SH AUTHOR -Written by DTB . - +Written by DTB +.MT trinity@trinity.moe +.ME . +.\" .SH COPYRIGHT - -Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +.\" +Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -strcmp(1), scrut(1), str(1), test(1p) +.BR scrut (1), +.BR strcmp (1), +.BR str (1), +.BR test (1p) diff --git a/docs/mm.1 b/docs/mm.1 new file mode 100644 index 0000000..2ff9f44 --- /dev/null +++ b/docs/mm.1 @@ -0,0 +1,73 @@ +.\" Copyright (c) 2024 DTB +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . +.\" +.TH MM 1 2024-06-17 "Harakit X.X.X" +.SH NAME +mm \(en middleman +.\" +.SH SYNOPSIS + +mm +.RB ( -aenu ) +.RB ( -i +.RB [ input ]) +.RB ( -o +.RB [ output ]) +.\" +.SH DESCRIPTION + +Catenate input files and write them to the start of each output file or stream. +.\" +.SH OPTIONS + +.IP \fB-a\fP +Opens subsequent outputs for appending rather than updating. +.IP \fB-e\fP +Use the standard error as an output. +.IP \fB-i\fP +Opens a path as an input. If one or more of the input files is \(lq-\(rq or if +no inputs are specified, the standard input shall be used. +.IP \fB-o\fP +Opens a path as an output. If one or more of the output files is \(lq-\(rq or if +no outputs are specified, the standard output shall be used. +.IP \fB-u\fP +Ensures neither input or output will be buffered. +.IP \fB-n\fP +Causes SIGINT signals to be ignored. +.\" +.SH DIAGNOSTICS + +If an output cannot be written to, an error occurs; however, exiting will be +deferred until writing to any other specified outputs completes. + +When an error is encountered, a diagnostic message is printed and the program +exits with the appropriate +.BR sysexits.h (3) +status. +.\" +.SH CAVEATS + +Existing files are not truncated on ouput and are instead overwritten. +.\" +.SH RATIONALE + +The +.BR cat (1p) +and +.BR tee (1p) +programs specified in POSIX together provide similar functionality. The +separation of the two sets of functionality into separate APIs seemed +unncessary. +.\" +.SH COPYRIGHT + +Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +. +.\" +.SH SEE ALSO +.BR cat (1p), +.BR dd (1), +.BR dj (1), +.BR tee (1p) diff --git a/docs/npc.1 b/docs/npc.1 index 7f66c3d..3e7af39 100644 --- a/docs/npc.1 +++ b/docs/npc.1 @@ -1,68 +1,74 @@ .\" Copyright (c) 2023–2024 DTB -.\" Copyright (c) 2023 Emma Tebibyte +.\" Copyright (c) 2023–2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH npc 1 - +.\" +.TH NPC 1 2024-06-17 "Harakit X.X.X" .SH NAME - npc \(en show non-printing characters - +.\" .SH SYNOPSIS npc -.RB ( -eht ) - +.RB ( -et ) +.\" .SH DESCRIPTION -Npc reads from standard input and writes to standard output, replacing non- -printing characters with printable equivalents. Control characters print as a -carat ('^') followed by the character '@' through '_' corresponding to the -character replaced (e.g. control-X becomes "^X"). The delete character (0x7F) -becomes "^?". Characters with the high bit set (>127) are printed as "M-" +Print normally non-printing characters. + +The program reads from standard input and writes to standard output, replacing +non-printing characters with printable equivalents. Control characters print as +a carat ('^') followed by the character '@' through '_' corresponding to the +character replaced (e.g. control-X becomes '^X'). The delete character (0x7F) +becomes '^?'. Characters with the high bit set (>127) are printed as 'M-' followed by the graphical representation for the same character without the high bit set. -.PP -The -.B -e -option prints a currency sign ('$') before each line ending. -.PP -The -.B -t -option prints tab characters as "^I" rather than a literal horizontal tab. +.\" +.SH OPTIONS +.IP \fB-e\fP +Prints a dollar sign ('$') before each newline. +.IP \fB-t\fP +Prints tab characters as '^I' rather than a literal horizontal tab. +.\" .SH DIAGNOSTICS -Npc prints a debug message and exits with the appropriate sysexits(3) error -code in the event of an error, otherwise it exits successfully. - +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 BUGS -Npc operates in single-byte chunks regardless of intended encoding. - +The program operates in single-byte chunks regardless of intended encoding. +.\" .SH RATIONALE POSIX currently lacks a way to display non-printing characters in the terminal -using a standard tool. A popular extension to cat(1p), the -v option, is the -bandage solution GNU and other software suites use. - -This functionality should be a separate tool because its usefulness extends -beyond that of cat(1p). +using a standard tool. A popular extension to +.BR cat (1p), +the +.B -v +option, is the bandage solution GNU and other software suites use. +This functionality is included in a separate tool because its usefulness extends +beyond that of +.BR cat (1p). +.\" .SH AUTHOR -Written by DTB . - +Written by DTB +.MT trinity@trinity.moe +.ME . +.\" .SH COPYRIGHT Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -cat(1p), cat-v(1) - +.BR cat (1p), +.BR cat-v (1), .I UNIX Style, or cat -v Considered Harmful by Rob Pike diff --git a/docs/rpn.1 b/docs/rpn.1 index 2197fbe..8c8cd84 100644 --- a/docs/rpn.1 +++ b/docs/rpn.1 @@ -3,68 +3,84 @@ .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH rpn 1 - +.\" +.TH RPN 1 2024-06-17 "Harakit X.X.X" .SH NAME - rpn \(en reverse polish notation evaluation - +.\" .SH SYNOPSIS rpn -.RB [numbers...]\ [operators...] - +.RB [ numbers... ] +.RB [ operators... ] +.\" .SH DESCRIPTION -Rpn evaluates reverse polish notation expressions either read from the standard -input or parsed from provided arguments. See the STANDARD INPUT section. +Evaluate reverse polish notation. -Upon evaluation, rpn will print the resulting number on the stack to the -standard output. Any further specified numbers will be placed at the end of the +The program evaluates reverse polish notation expressions read either from the +standard input or parsed from provided arguments. See the STANDARD INPUT +section. + +Upon evaluation, the resulting number on the stack will be printed to the +standard output. Any further numbers specified will be placed at the end of the stack. -For information on for reverse polish notation syntax, see rpn(7). - +For information on for reverse polish notation syntax, see +.BR rpn (7). +.\" .SH STANDARD INPUT -If arguments are passed to rpn, it interprets them as an expression to be -evaluated. Otherwise, it reads whitespace-delimited numbers and operations from +If arguments are specified, they are interpreted as an expression to be +evaluated. Otherwise, whitespace-delimited numbers and operations are read from the standard input. - +.\" .SH DIAGNOSTICS -If encountering a syntax error, rpn will exit with the appropriate error code -as defined by sysexits.h(3) and print an error message. - +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; however, in the event of a syntax error, the program will print an +error message and continue accepting input. +.\" .SH CAVEATS Due to precision constraints and the way floats are represented in accordance -with the IEEE Standard for Floating Point Arithmetic (IEEE 754), floating-point -arithmetic has rounding errors. This is somewhat curbed by using the -machine epsilon as provided by the Rust standard library to which to round -numbers. Because of this, variation is expected in the number of decimal places -rpn can handle based on the platform and hardware of any given machine. - +with the +.I IEEE Standard for Floating Point Arithmetic +(\fIIEEE 754\fP), floating-point arithmetic has rounding errors. This is +somewhat curbed by using the machine epsilon as provided by the Rust standard +library to which numbers are rounded. Because of this, variation is expected in +the number of decimal places the program can handle based on the platform and +hardware of any given machine. +.\" .SH RATIONALE -An infix notation calculation utility, bc(1p), 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 dc(1) -pre-dates the standardized bc(1p), the latter originally being a preprocessor -for the former, and was included in UNIX v2 onward. While it implements reverse -polish notation, it still suffers from being unable to accept an expression as -an argument. - +An infix notation calculation utility, +.BR bc (1p), +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) +pre-dates the standardized +.BR bc (1p), +the latter originally being a preprocessor for the former, and was included in +Second Edition UNIX and onward. While it implements reverse polish notation, it +still suffers from being unable to accept an expression as an argument. +.\" .SH AUTHOR -Written by Emma Tebibyte . - +Written by Emma Tebibyte +.MT emma@tebibyte.media +.ME . +.\" .SH COPYRIGHT Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -bc(1p), dc(1), rpn(7), IEEE 754 +.BR bc (1p), +.BR dc (1), +.BR rpn (7), +.I IEEE 754 diff --git a/docs/scrut.1 b/docs/scrut.1 index 7a5107a..56383b8 100644 --- a/docs/scrut.1 +++ b/docs/scrut.1 @@ -1,93 +1,86 @@ .\" Copyright (c) 2024 DTB +.\" Copyright (c) 2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH scrut 1 - +.\" +.TH SCRUT 1 2024-06-06 "Harakit X.X.X" .SH NAME - scrut \(en scrutinize file properties - .SH SYNOPSIS scrut -.RB ( -bcdefgkprsuwxLS ) +.RB ( -LSbcdefgkprsuwx ) .RB [ file... ] - +.\" .SH DESCRIPTION -Scrut determines if given files comply with the opted requirements. - +Determine if files comply with requirements. If the given files comply with the +specified requirements, the program will exit successfully. Otherwise, it exits +unsuccessfully. +.\" .SH OPTIONS -.B -b -requires the given files to exist and be block special files. -.PP -.B -c -requires the given files to exist and be character special files. -.PP -.B -d -requires the given files to exist and be directories. -.PP -.B -e -requires the given files to exist, and is redundant to any other option. -.PP -.B -e -requires the given files to exist and be regular files. -.PP -.B -g -requires the given files to exist and have their set group ID flags set. -.PP -.B -k -requires the given files to exist and have their sticky bit set. -.PP -.B -p -requires the given files to exist and be named pipes. -.PP -.B -r -requires the given files to exist and be readable. -.PP -.B -u -requires the given files to exist and have their set user ID flags set. -.PP -.B -w -requires the given files to exist and be writable. -.PP -.B -x -requires the given files to exist and be executable. -.PP -.B -L -requires the given files to exist and be symbolic links. -.PP -.B -S -requires the given files to exist and be sockets. +.IP \fB-L\fB +Requires the given files to exist and be symbolic links. +.IP \fB-S\fP +Requires the given files to exist and be sockets. +.IP \fB-b\fP +Requires the given files to exist and be block special files. +.IP \fB-c\fP +Requires the given files to exist and be character special files. +.IP \fB-d\fP +Requires the given files to exist and be directories. +.IP \fB-e\fP +Requires the given files to exist, and is redundant to any other option. +.IP \fB-f\fP +Requires the given files to exist and be regular files. +.IP \fB-g\fP +Requires the given files to exist and have their set group ID flags set. +.IP \fB-k\fP +Requires the given files to exist and have their sticky bit set. +.IP \fB-p\fP +Requires the given files to exist and be named pipes. +.IP \fB-r\fP +Requires the given files to exist and be readable. +.IP \fB-u\fP +Requires the given files to exist and have their set user ID flags set. +.IP \fB-w\fP +Requires the given files to exist and be writable. +.IP \fB-x\fP +Requires the given files to exist and be executable. +.\" +.SH DIAGNOSTICS -.SH EXIT STATUS +When invoked incorrectly, a debug message will be printed and the program will +exit with the appropriate +.BR sysexits.h (3) +error code. +.\" +.SH RATIONALE -Scrut prints a debug message and exits unsuccessfully with the appropriate -sysexits.h(3) error code if invoked incorrectly. Scrut exits successfully if -the given files comply with their requirements and unsuccessfully otherwise. - -.SH STANDARDS - -Scrut is nearly compatible with POSIX's test utility though it is narrower in -scope. Notably, the +The +.BR test (1p) +utility contains functionality that was broken out into separate programs. Thus, +the scope of this program is narrower than it. Notably, the .B -h option is now invalid and therefore shows usage information instead of being an alias to the modern .B -L option. - +.\" .SH AUTHOR -Written by DTB . - +Written by DTB +.MT trinity@trinity.moe +.ME . +.\" .SH COPYRIGHT -Copyright © 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -access(3p), lstat(3p), test(1p) +.BR access (3p), +.BR lstat (3p), +.BR test (1p) diff --git a/docs/str.1 b/docs/str.1 index ecf71ee..22ffea1 100644 --- a/docs/str.1 +++ b/docs/str.1 @@ -1,58 +1,60 @@ .\" Copyright (c) 2023–2024 DTB -.\" Copyright (c) 2023 Emma Tebibyte +.\" Copyright (c) 2023–2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH STR 1 - +.\" +.TH STR 1 2024-06-17 "Harakit X.X.X" .SH NAME - -str \(en test the character types of string arguments - +str \(en test string arguments +.\" .SH SYNOPSIS str .RB [ type ] .RB [ string... ] - +.\" .SH DESCRIPTION -Str tests each character in an arbitrary quantity of string arguments against -the function of the same name within ctype(3). +Test the character types of string arguments. +The tests in this program are equivalent to the functions with the same names in +.BR ctype.h (0p) +and are the methods by which string arguments are tested. +.\" .SH DIAGNOSTICS -Str exits successfully if all tests pass and unsuccessfully if a test failed. -.PP -Str will exit unsuccessfully if a string is empty, as none of its contents -passed the test. -.PP -Str will print a message to standard error and exit unsuccessfully if used -improperly. +If all tests pass, the program will exit successfully. If any of the tests fail, +the program will exit unsuccessfully with an error code of 1. -.SH DEPRECATED FEATURES +When invoked incorrectly, a debug message will be printed and the program will +exit with the appropriate +.BR sysexits.h (3) +error code. +.\" +.SH CAVEATS -Str used to have an "isvalue" type as an extension to ctype(3). This was -removed in favor of using strcmp(1) to compare strings against the empty string -(''). +None of an empty string\(cqs contents pass any of the tests, so the program will +exit unsuccessfully if one is specified. -.SH BUGS - -There's no way of knowing which argument failed the test without re-testing +There\(cqs no way of knowing which argument failed the test without re-testing arguments individually. -.PP -If a character in a string isn't valid ASCII str will exit unsuccessfully. +If a character in a string isn\(cqt valid ASCII, the program will exit +unsuccessfully. +.\" .SH AUTHOR -Written by DTB . - +Written by DTB +.MT trinity@trinity.moe +.ME . +.\" .SH COPYRIGHT -Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -ctype(3p), strcmp(1), ascii(7) +.BR ctype (3p), +.BR strcmp(1), +.BR ascii(7) diff --git a/docs/strcmp.1 b/docs/strcmp.1 index 14c0d0d..0ad21b2 100644 --- a/docs/strcmp.1 +++ b/docs/strcmp.1 @@ -1,62 +1,76 @@ .\" Copyright (c) 2023–2024 DTB -.\" Copyright (c) 2023 Emma Tebibyte +.\" Copyright (c) 2023–2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH STRCMP 1 - +.\" +.TH STRCMP 1 2024-06-17 "Harakit X.X.X" .SH NAME - strcmp \(en compare strings - +.\" .SH SYNOPSIS strcmp .RM [ string ] .RB [ strings... ] - +.\" .SH DESCRIPTION -Strcmp checks whether the given strings are the same. -Strcmp exits successfully if the strings are identical. Otherwise, strcmp exits -with the value 1 if an earlier string has a greater byte value than a later -string (e.g. -.R strcmp b a -) -and 255 if an earlier string has a lesser byte value (e.g. -.R strcmp a b -). - +Check whether string arguments are the same. +.\" .SH DIAGNOSTICS -Strcmp will print an error message and exit unsuccessfully with a status -described in sysexits(3) if used incorrectly (given less than two operands). +The program will exit successfully if the strings are identical. Otherwise, it +will exit with an error code of 1 if a string passed has a lesser byte value +than one of the prior strings: -.SH UNICODE +.RS +strcmp b a +.RE -Strcmp will exit unsuccessfully if the given strings are not identical; -Unicode strings may need to be normalized if the intent is to check visual -similarity and not byte similarity. +or with an error code of 255 if it has a greater byte value than one of the +prior strings: +.RS +strcmp a b +.RE + +When invoked incorrectly, a debug message will be printed and the program will +exit with the appropriate +.BR sysexits.h (3) +error code. +.\" +.SH CAVEATS + +The program will exit unsuccessfully if the given strings are not identical; +therefore, Unicode strings may need to be normalized if the intent is to check +visual similarity and not byte similarity. +.\" .SH RATIONALE The traditional tool for string comparisons in POSIX and other Unix shells has -been test(1). This tool also handles integer comparisons and file scrutiny. -These parts of its functionality have been broken out into multiple utilities. - -Strcmp’s functionality may be performed on a POSIX-compliant system with -test(1p). +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 POSIX-compliant system +with +.BR test (1p). +.\" .SH AUTHOR -Written by DTB . - +Written by DTB +.MT trinity@trinity.moe +.ME . +.\" .SH COPYRIGHT -Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later +Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later . - +.\" .SH SEE ALSO - -strcmp(3), intcmp(1), scrut(1), test(1p) +.BR strcmp (3), +.BR intcmp (1), +.BR scrut (1), +.BR test (1p) diff --git a/docs/swab.1 b/docs/swab.1 new file mode 100644 index 0000000..72f0f19 --- /dev/null +++ b/docs/swab.1 @@ -0,0 +1,81 @@ +.\" Copyright (c) 2024 DTB +.\" Copyright (c) 2024 Emma Tebibyte +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . +.\" +.TH SWAB 1 2024-06-17 "Harakit X.X.X" +.SH NAME +swab \(en swap bytes +.\" +.SH SYNOPSIS + +swab +.RB ( -f ) +.RB ( -w +.R [ +.B word size +.R ]) +.\" +.SH DESCRIPTION + +Swap the latter and former halves of a block of bytes. +.\" +.SH OPTIONS + +.IP \fB-f\fP +Ignore SIGINT signal. +.IP \fB-w\fP +Configures the word size; that is, the size in bytes of the block size +on which to operate. The default word size is 2. The word size must be +cleanly divisible by 2, otherwise the block of bytes being processed can\(cqt be +halved. +.\" +.SH EXAMPLES + +The following +.BR sh (1p) +line: + +.RS +printf 'hello world!\(rsn' | swab +.RE + +Produces the following output: + +.RS +ehll oowlr!d +.RE +.\" +.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 + +This program was modeled and named after the +.B conv=swab +functionality specified +in the +.BR dd (1p) +utility. It additionally allows the word size to be configured. + +This functionality is useful for fixing the endianness of binary files produced +on other machines. +.\" +.SH AUTHOR + +Written by DTB +.MT trinity@trinity.moe +.ME . +.\" +.SH COPYRIGHT + +Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +. +.\" +.SH SEE ALSO +.BR dd (1p) diff --git a/docs/true.1 b/docs/true.1 index 3c292d8..97af65b 100644 --- a/docs/true.1 +++ b/docs/true.1 @@ -1,35 +1,36 @@ .\" Copyright (c) 2022, 2024 DTB -.\" Copyright (c) 2023 Emma Tebibyte +.\" Copyright (c) 2023–2024 Emma Tebibyte .\" .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . - -.TH TRUE 1 - +.\" +.TH TRUE 1 2024-06-06 "Harakit X.X.X" .SH NAME - true \(en do nothing, successfully - +.\" .SH DESCRIPTION -True does nothing regardless of operands or standard input. -True will always return an exit code of 0. - +Do nothing regardless of operands or standard input. An exit code of 0 will +always be returned. +.\" .SH RATIONALE -True exists for the construction of control flow and loops based on a success. - -True functions as described in POSIX.1-2017. - +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. +.\" .SH AUTHOR -Written by Emma Tebibyte . - +Written by Emma Tebibyte +.MT emma@tebibyte.media +.ME . +.\" .SH COPYRIGHT This work is marked with CC0 1.0. To see a copy of this license, visit . - +.\" .SH SEE ALSO - -false(1p) +.BR false (1p), +.BR true (1p) diff --git a/src/dj.c b/src/dj.c index d5bd59c..8a6732c 100644 --- a/src/dj.c +++ b/src/dj.c @@ -203,28 +203,28 @@ Io_fdseek(struct Io *io){ if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1) return -1; - else if(io->fl == write_flags){ - memset(io->buf, '\0', io->bs); - /* This is a dirty trick; rather than testing conditions and operating - * likewise, because the parameters to read or write are going to be - * the same either way, just use a function pointer to keep track of - * the intended operation. */ - op = (int (*)(int, void *, size_t))&write; - /* Function pointer casts are risky; this works because the difference - * is in the second parameter and only that write(2) makes the buffer - * const whereas read(2) does not. To avoid even the slightest - * undefined behavior comment out the cast, just be ready for a - * -Wincompatible-function-pointer-types if your compiler notices it. - */ - }else - op = &read; - /* We're going to cheat and use bufuse as the retval for write(2), which is - * fine because it'll be zeroed as this function returns anyway. */ - do{ if( (io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek))) == 0) - /* second chance */ - io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek)); - }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); + /* repeated code to get the condition out of the loop */ + if(io->fl == write_flags){ + memset(io->buf, '\0', io->bs); + /* We're going to cheat and use bufuse as the retval for write(2), + * which is fine because it'll be zeroed as this function returns + * anyway. */ + do{ + if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek))) + == 0) + /* second chance */ + io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)); + }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); + }else if(io->fl == read_flags){ + do{ + if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek))) + == 0) + /* second chance */ + io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)); + }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); + }else + return EX_SOFTWARE; io->bufuse = 0; diff --git a/src/fop.rs b/src/fop.rs index c9a767e..91c8a72 100644 --- a/src/fop.rs +++ b/src/fop.rs @@ -22,46 +22,54 @@ use std::{ process::{ Command, exit, Stdio }, }; -extern crate sysexits; extern crate getopt; +extern crate strerror; +extern crate sysexits; -use getopt::{ Opt, Parser }; +use getopt::GetOpt; +use strerror::StrError; use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE }; fn main() { let argv = args().collect::>(); - let mut d = '␞'; - let mut arg_parser = Parser::new(&argv, "d:"); + let mut d = '\u{1E}'.to_string(); + let mut index_arg = 0; - while let Some(opt) = arg_parser.next() { - match opt { - Ok(Opt('d', Some(arg))) => { - let arg_char = arg.chars().collect::>(); - if arg_char.len() > 1 { - eprintln!("{}: {}: Not a character.", argv[0], arg); - exit(EX_USAGE); - } else { d = arg_char[0]; } + let usage = format!( + "Usage: {} [-d delimiter] index command [args...]", + argv[0], + ); + + while let Some(opt) = argv.getopt("d:") { + match opt.opt() { + Ok(_) => { + /* unwrap because Err(OptError::MissingArg) will be returned if + * opt.arg() is None */ + d = opt.arg().unwrap(); + index_arg = opt.ind(); }, - _ => {}, + Err(_) => { + eprintln!("{}", usage); + exit(EX_USAGE); + } }; } - let index_arg = arg_parser.index(); - let command_arg = arg_parser.index() + 1; + let command_arg = index_arg as usize + 1; argv.get(command_arg).unwrap_or_else(|| { - eprintln!("Usage: {} [-d delimiter] index command [args...]", argv[0]); + eprintln!("{}", usage); exit(EX_USAGE); }); let index = argv[index_arg].parse::().unwrap_or_else(|e| { - eprintln!("{}: {}: {}.", argv[0], argv[1], e); + eprintln!("{}: {}: {}", argv[0], argv[1], e); exit(EX_DATAERR); }); let mut buf = String::new(); let _ = stdin().read_to_string(&mut buf); - let mut fields = buf.split(d).collect::>(); + let mut fields = buf.split(&d).collect::>(); let opts = argv .iter() @@ -75,13 +83,13 @@ fn main() { .stdout(Stdio::piped()) .spawn() .unwrap_or_else( |e| { - eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e); + eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); exit(EX_UNAVAILABLE); }); let field = fields.get(index).unwrap_or_else(|| { eprintln!( - "{}: {}: No such index in input.", + "{}: {}: No such index in input", argv[0], index.to_string(), ); @@ -94,7 +102,7 @@ fn main() { } let output = spawned.wait_with_output().unwrap_or_else(|e| { - eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e); + eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); exit(EX_IOERR); }); @@ -103,7 +111,7 @@ fn main() { if replace.pop() != Some(b'\n') { replace = output.stdout; } let new_field = String::from_utf8(replace).unwrap_or_else(|e| { - eprintln!("{}: {}: {}.", argv[0], argv[command_arg], e); + eprintln!("{}: {}: {}", argv[0], argv[command_arg], e); exit(EX_IOERR); }); @@ -111,8 +119,8 @@ fn main() { stdout().write_all( fields.join(&d.to_string()).as_bytes() - ).unwrap_or_else(|e|{ - eprintln!("{}: {}.", argv[0], e); + ).unwrap_or_else(|e| { + eprintln!("{}: {}", argv[0], e.strerror()); exit(EX_IOERR); }); } diff --git a/src/getopt-rs/error.rs b/src/getopt-rs/error.rs deleted file mode 100644 index 322af02..0000000 --- a/src/getopt-rs/error.rs +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * 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/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::{ error, fmt }; - -use crate::ErrorKind::{ self, * }; - -/// A basic error type for [`Parser`](struct.Parser.html) -#[derive(Debug, Eq, PartialEq)] -pub struct Error { - culprit: char, - kind: ErrorKind, -} - -impl Error { - /// Creates a new error using a known kind and the character that caused the - /// issue. - pub fn new(kind: ErrorKind, culprit: char) -> Self { - Self { culprit, kind } - } - - /// Returns the [`ErrorKind`](enum.ErrorKind.html) for this error. - pub fn kind(self) -> ErrorKind { - self.kind - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.kind { - MissingArgument => write!( - f, - "option requires an argument -- {:?}", - self.culprit, - ), - UnknownOption => write!(f, "unknown option -- {:?}", self.culprit), - } - } -} - -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - None - } -} diff --git a/src/getopt-rs/errorkind.rs b/src/getopt-rs/errorkind.rs deleted file mode 100644 index 5475d8e..0000000 --- a/src/getopt-rs/errorkind.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * 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/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/// What kinds of errors [`Parser`](struct.Parser.html) can return. -#[derive(Debug, Eq, PartialEq)] -pub enum ErrorKind { - /// An argument was not found for an option that was expecting one. - MissingArgument, - /// An unknown option character was encountered. - UnknownOption, -} diff --git a/src/getopt-rs/lib.rs b/src/getopt-rs/lib.rs deleted file mode 100644 index 62f0e0d..0000000 --- a/src/getopt-rs/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * 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/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -//! # getopt -//! -//! `getopt` provides a minimal, (essentially) POSIX-compliant option parser. - -pub use crate::{ - error::Error, - errorkind::ErrorKind, - opt::Opt, - parser::Parser, - result::Result -}; - -mod error; -mod errorkind; -mod opt; -mod parser; -mod result; -#[cfg(test)] -mod tests; diff --git a/src/getopt-rs/opt.rs b/src/getopt-rs/opt.rs deleted file mode 100644 index 05b51e6..0000000 --- a/src/getopt-rs/opt.rs +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * 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/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::fmt; - -/// A single option. -/// -/// For `Opt(x, y)`: -/// - `x` is the character representing the option. -/// - `y` is `Some` string, or `None` if no argument was expected. -/// -/// # Example -/// -/// ``` -/// # fn main() -> Result<(), Box> { -/// use getopt::Opt; -/// -/// // args = ["program", "-abc", "foo"]; -/// # let args: Vec = vec!["program", "-abc", "foo"] -/// # .into_iter() -/// # .map(String::from) -/// # .collect(); -/// let optstring = "ab:c"; -/// let mut opts = getopt::Parser::new(&args, optstring); -/// -/// assert_eq!(Opt('a', None), opts.next().transpose()?.unwrap()); -/// assert_eq!(Opt('b', Some("c".to_string())), opts.next().transpose()?.unwrap()); -/// assert_eq!(None, opts.next().transpose()?); -/// # Ok(()) -/// # } -/// ``` -#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Opt(pub char, pub Option); - -impl fmt::Display for Opt { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Opt({:?}, {:?})", self.0, self.1) - } -} diff --git a/src/getopt-rs/parser.rs b/src/getopt-rs/parser.rs deleted file mode 100644 index 6f06cc3..0000000 --- a/src/getopt-rs/parser.rs +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * 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/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::collections::HashMap; - -use crate::{ error::Error, errorkind::ErrorKind, opt::Opt, result::Result }; - -/// The core of the `getopt` crate. -/// -/// `Parser` is implemented as an iterator over the options present in the given -/// argument vector. -/// -/// The method [`next`](#method.next) does the heavy lifting. -/// -/// # Examples -/// -/// ## Simplified usage: -/// ``` -/// # fn main() -> Result<(), Box> { -/// use getopt::Opt; -/// -/// // args = ["program", "-abc", "foo"]; -/// # let args: Vec = vec!["program", "-abc", "foo"] -/// # .into_iter() -/// # .map(String::from) -/// # .collect(); -/// let mut opts = getopt::Parser::new(&args, "ab:c"); -/// -/// assert_eq!(Some(Opt('a', None)), opts.next().transpose()?); -/// assert_eq!(1, opts.index()); -/// assert_eq!(Some(Opt('b', Some("c".to_string()))), opts.next().transpose()?); -/// assert_eq!(2, opts.index()); -/// assert_eq!(None, opts.next()); -/// assert_eq!(2, opts.index()); -/// assert_eq!("foo", args[opts.index()]); -/// # Ok(()) -/// # } -/// ``` -/// -/// ## A more idiomatic example: -/// ``` -/// # fn main() -> Result<(), Box> { -/// use getopt::Opt; -/// -/// // args = ["program", "-abc", "-d", "foo", "-e", "bar"]; -/// # let mut args: Vec = vec!["program", "-abc", "-d", "foo", "-e", "bar"] -/// # .into_iter() -/// # .map(String::from) -/// # .collect(); -/// let mut opts = getopt::Parser::new(&args, "ab:cd:e"); -/// -/// let mut a_flag = false; -/// let mut b_flag = String::new(); -/// let mut c_flag = false; -/// let mut d_flag = String::new(); -/// let mut e_flag = false; -/// -/// loop { -/// match opts.next().transpose()? { -/// None => break, -/// Some(opt) => match opt { -/// Opt('a', None) => a_flag = true, -/// Opt('b', Some(arg)) => b_flag = arg.clone(), -/// Opt('c', None) => c_flag = true, -/// Opt('d', Some(arg)) => d_flag = arg.clone(), -/// Opt('e', None) => e_flag = true, -/// _ => unreachable!(), -/// }, -/// } -/// } -/// -/// let new_args = args.split_off(opts.index()); -/// -/// assert_eq!(true, a_flag); -/// assert_eq!("c", b_flag); -/// assert_eq!(false, c_flag); -/// assert_eq!("foo", d_flag); -/// assert_eq!(true, e_flag); -/// -/// assert_eq!(1, new_args.len()); -/// assert_eq!("bar", new_args.first().unwrap()); -/// # Ok(()) -/// # } -/// ``` -#[derive(Debug, Eq, PartialEq)] -pub struct Parser { - opts: HashMap, - args: Vec>, - index: usize, - point: usize, -} - -impl Parser { - /// Create a new `Parser`, which will process the arguments in `args` - /// according to the options specified in `optstring`. - /// - /// For compatibility with - /// [`std::env::args`](https://doc.rust-lang.org/std/env/fn.args.html), - /// valid options are expected to begin at the second element of `args`, and - /// `index` is - /// initialised to `1`. - /// If `args` is structured differently, be sure to call - /// [`set_index`](#method.set_index) before the first invocation of - /// [`next`](#method.next). - /// - /// `optstring` is a string of recognised option characters; if a character - /// is followed by a colon (`:`), that option takes an argument. - /// - /// # Note: - /// Transforming the OS-specific argument strings into a vector of `String`s - /// is the sole responsibility of the calling program, as it involves some - /// level of potential information loss (which this crate does not presume - /// to handle unilaterally) and error handling (which would complicate the - /// interface). - pub fn new(args: &[String], optstring: &str) -> Self { - let optstring: Vec = optstring.chars().collect(); - let mut opts = HashMap::new(); - let mut i = 0; - let len = optstring.len(); - - while i < len { - let j = i + 1; - - if j < len && optstring[j] == ':' { - opts.insert(optstring[i], true); - i += 1; - } else { - opts.insert(optstring[i], false); - } - i += 1; - } - - Self { - opts, - // "explode" the args into a vector of character vectors, to allow - // indexing - args: args.iter().map(|e| e.chars().collect()).collect(), - index: 1, - point: 0, - } - } - - /// Return the current `index` of the parser. - /// - /// `args[index]` will always point to the the next element of `args`; when - /// the parser is - /// finished with an element, it will increment `index`. - /// - /// After the last option has been parsed (and [`next`](#method.next) is - /// returning `None`), - /// `index` will point to the first non-option argument. - pub fn index(&self) -> usize { - self.index - } - - // `point` must be reset to 0 whenever `index` is changed - - /// Modify the current `index` of the parser. - pub fn set_index(&mut self, value: usize) { - self.index = value; - self.point = 0; - } - - /// Increment the current `index` of the parser. - /// - /// This use case is common enough to warrant its own optimised method. - pub fn incr_index(&mut self) { - self.index += 1; - self.point = 0; - } -} - -impl Iterator for Parser { - type Item = Result; - - /// Returns the next option, if any. - /// - /// Returns an [`Error`](struct.Error.html) if an unexpected option is - /// encountered or if an - /// expected argument is not found. - /// - /// Parsing stops at the first non-hyphenated argument; or at the first - /// argument matching "-"; - /// or after the first argument matching "--". - /// - /// When no more options are available, `next` returns `None`. - /// - /// # Examples - /// - /// ## "-" - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "-", "-a"]; - /// # let args: Vec = vec!["program", "-", "-a"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a"); - /// - /// assert_eq!(None, opts.next()); - /// assert_eq!("-", args[opts.index()]); - /// ``` - /// - /// ## "--" - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "--", "-a"]; - /// # let args: Vec = vec!["program", "--", "-a"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a"); - /// - /// assert_eq!(None, opts.next()); - /// assert_eq!("-a", args[opts.index()]); - /// ``` - /// - /// ## Unexpected option: - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "-b"]; - /// # let args: Vec = vec!["program", "-b"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a"); - /// - /// assert_eq!( - /// "unknown option -- 'b'".to_string(), - /// opts.next().unwrap().unwrap_err().to_string() - /// ); - /// ``` - /// - /// ## Missing argument: - /// ``` - /// use getopt::Parser; - /// - /// // args = ["program", "-a"]; - /// # let args: Vec = vec!["program", "-a"] - /// # .into_iter() - /// # .map(String::from) - /// # .collect(); - /// let mut opts = Parser::new(&args, "a:"); - /// - /// assert_eq!( - /// "option requires an argument -- 'a'".to_string(), - /// opts.next().unwrap().unwrap_err().to_string() - /// ); - /// ``` - fn next(&mut self) -> Option> { - if self.point == 0 { - /* - * Rationale excerpts below taken verbatim from "The Open Group Base - * Specifications Issue 7, 2018 edition", IEEE Std 1003.1-2017 - * (Revision of IEEE Std 1003.1-2008). - * Copyright © 2001-2018 IEEE and The Open Group. - */ - - /* - * If, when getopt() is called: - * argv[optind] is a null pointer - * *argv[optind] is not the character '-' - * argv[optind] points to the string "-" - * getopt() shall return -1 without changing optind. - */ - if self.index >= self.args.len() - || self.args[self.index].is_empty() - || self.args[self.index][0] != '-' - || self.args[self.index].len() == 1 - { - return None; - } - - /* - * If: - * argv[optind] points to the string "--" - * getopt() shall return -1 after incrementing index. - */ - if self.args[self.index][1] == '-' && self.args[self.index].len() == 2 { - self.incr_index(); - return None; - } - - // move past the starting '-' - self.point += 1; - } - - let opt = self.args[self.index][self.point]; - self.point += 1; - - match self.opts.get(&opt) { - None => { - if self.point >= self.args[self.index].len() { - self.incr_index(); - } - Some(Err(Error::new(ErrorKind::UnknownOption, opt))) - } - Some(false) => { - if self.point >= self.args[self.index].len() { - self.incr_index(); - } - - Some(Ok(Opt(opt, None))) - } - Some(true) => { - let arg: String = if self.point >= self.args[self.index].len() { - self.incr_index(); - if self.index >= self.args.len() { - return Some(Err(Error::new( - ErrorKind::MissingArgument, - opt, - ))); - } - self.args[self.index].iter().collect() - } else { - self.args[self.index] - .clone() - .split_off(self.point) - .iter() - .collect() - }; - - self.incr_index(); - - Some(Ok(Opt(opt, Some(arg)))) - } - } - } -} diff --git a/src/getopt-rs/result.rs b/src/getopt-rs/result.rs deleted file mode 100644 index 015a402..0000000 --- a/src/getopt-rs/result.rs +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * 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/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use std::result; - -use crate::error::Error; - -/// A specialized `Result` type for use with [`Parser`](struct.Parser.html) -pub type Result = result::Result; diff --git a/src/getopt-rs/tests.rs b/src/getopt-rs/tests.rs deleted file mode 100644 index c53d517..0000000 --- a/src/getopt-rs/tests.rs +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2023 Emma Tebibyte - * 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/. - * - * This file incorporates work covered by the following copyright and permission - * notice: - * The Clear BSD License - * - * Copyright © 2017-2023 David Wildasin - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted (subject to the limitations in the disclaimer - * below) provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions, and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED - * BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -use crate::{Opt, Parser}; - -macro_rules! basic_test { - ($name:ident, $expect:expr, $next:expr, [$($arg:expr),+], $optstr:expr) => ( - #[test] - fn $name() -> Result<(), String> { - let expect: Option = $expect; - let args: Vec = vec![$($arg),+] - .into_iter() - .map(String::from) - .collect(); - let next: Option = $next; - let mut opts = Parser::new(&args, $optstr); - - match opts.next().transpose() { - Err(error) => { - return Err(format!("next() returned {:?}", error)) - }, - Ok(actual) => if actual != expect { - return Err( - format!("expected {:?}; got {:?}", expect, actual) - ) - }, - }; - - match next { - None => if opts.index() < args.len() { - return Err(format!( - "expected end of args; got {:?}", args[opts.index()] - )) - }, - Some(n) => if args[opts.index()] != n { - return Err(format!( - "next arg: expected {:?}; got {:?}", - n, - args[opts.index()] - )) - }, - }; - - Ok(()) - } - ) -} - -#[rustfmt::skip] basic_test!( - blank_arg, None, Some(String::new()), ["x", ""], "a" -); -#[rustfmt::skip] basic_test!( - double_dash, None, Some("-a".to_string()), ["x", "--", "-a", "foo"], "a" -); -#[rustfmt::skip] basic_test!(no_opts_1, None, None, ["x"], "a"); -#[rustfmt::skip] basic_test!( - no_opts_2, None, Some("foo".to_string()), ["x", "foo"], "a" -); -#[rustfmt::skip] basic_test!( - no_opts_3, None, Some("foo".to_string()), ["x", "foo", "-a"], "a" -); -#[rustfmt::skip] basic_test!( - single_dash, None, Some("-".to_string()), ["x", "-", "-a", "foo"], "a" -); -#[rustfmt::skip] basic_test!( - single_opt, - Some(Opt('a', None)), - Some("foo".to_string()), - ["x", "-a", "foo"], - "a" -); -#[rustfmt::skip] basic_test!( - single_optarg, - Some(Opt('a', Some("foo".to_string()))), - None, - ["x", "-a", "foo"], - "a:" -); - -macro_rules! error_test { - ($name:ident, $expect:expr, [$($arg:expr),+], $optstr:expr) => ( - #[test] - fn $name() -> Result<(), String> { - let expect: String = $expect.to_string(); - let args: Vec = vec![$($arg),+] - .into_iter() - .map(String::from) - .collect(); - let mut opts = Parser::new(&args, $optstr); - - match opts.next() { - None => { - return Err(format!( - "unexpected successful response: end of options" - )) - }, - Some(Err(actual)) => { - let actual = actual.to_string(); - - if actual != expect { - return Err( - format!("expected {:?}; got {:?}", expect, actual) - ); - } - }, - Some(Ok(opt)) => { - return Err( - format!("unexpected successful response: {:?}", opt) - ) - }, - }; - - Ok(()) - } - ) -} - -#[rustfmt::skip] error_test!( - bad_opt, - "unknown option -- 'b'", - ["x", "-b"], - "a" -); - -#[rustfmt::skip] error_test!( - missing_optarg, - "option requires an argument -- 'a'", - ["x", "-a"], - "a:" -); - -#[test] -fn multiple() -> Result<(), String> { - let args: Vec = vec!["x", "-abc", "-d", "foo", "-e", "bar"] - .into_iter() - .map(String::from) - .collect(); - let optstring = "ab:d:e".to_string(); - let mut opts = Parser::new(&args, &optstring); - - macro_rules! check_result { - ($expect:expr) => { - let expect: Option = $expect; - match opts.next().transpose() { - Err(error) => { - return Err(format!("next() returned {:?}", error)); - }, - Ok(actual) => { - if actual != expect { - return Err( - format!("expected {:?}; got {:?}", expect, actual) - ); - } - } - }; - }; - } - - check_result!(Some(Opt('a', None))); - check_result!(Some(Opt('b', Some("c".to_string())))); - check_result!(Some(Opt('d', Some("foo".to_string())))); - check_result!(Some(Opt('e', None))); - check_result!(None); - - Ok(()) -} - -#[test] -fn continue_after_error() { - let args: Vec = vec!["x", "-z", "-abc"] - .into_iter() - .map(String::from) - .collect(); - let optstring = "ab:d:e".to_string(); - for _opt in Parser::new(&args, &optstring) { - // do nothing, should not panic - } -} diff --git a/src/hru.rs b/src/hru.rs index 0e0b25d..b7937f7 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -23,8 +23,10 @@ use std::{ process::{ ExitCode, exit }, }; +extern crate strerror; extern crate sysexits; +use strerror::StrError; use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE }; const LIST: [(u32, &str); 10] = [ @@ -49,7 +51,7 @@ fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> { let c = match 10_u128.checked_pow(n) { Some(c) => c, None => { - return Err(format!("10^{}: Integer overflow.", n.to_string())); + return Err(format!("10^{}: Integer overflow", n.to_string())); }, }; @@ -79,7 +81,7 @@ fn main() -> ExitCode { f }, Err(err) => { - eprintln!("{}: {}.", argv[0], err); + eprintln!("{}: {}", argv[0], err); return ExitCode::from(EX_DATAERR as u8); }, }; @@ -87,7 +89,7 @@ fn main() -> ExitCode { let (number, prefix) = match convert(n) { Ok(x) => x, Err(err) => { - eprintln!("{}: {}.", argv[0], err); + eprintln!("{}: {}", argv[0], err); return ExitCode::from(EX_SOFTWARE as u8); }, }; @@ -98,7 +100,7 @@ fn main() -> ExitCode { stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes()) .unwrap_or_else(|e| { - eprintln!("{}: {}.", argv[0], e); + eprintln!("{}: {}", argv[0], e.strerror()); exit(EX_IOERR); }); } diff --git a/src/libgetopt.rs b/src/libgetopt.rs new file mode 100644 index 0000000..8064c81 --- /dev/null +++ b/src/libgetopt.rs @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2023–2024 Emma Tebibyte + * 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::{ c_int, c_char, CString, CStr }; + +/* binding to getopt(3p) */ +extern "C" { + static mut optarg: *mut c_char; + static mut _opterr: c_int; + static mut optind: c_int; + static mut optopt: c_int; + + fn getopt( + ___argc: c_int, + ___argv: *const *mut c_char, + __shortopts: *const c_char, + ) -> c_int; +} + +#[derive(Clone, Debug)] +pub enum OptError { + MissingArg(String), + UnknownOpt(String), +} + +#[derive(Clone, Debug)] +pub struct Opt { + arg: Option, /* option argument */ + ind: *mut i32, /* option index */ + opt: Result, /* option option */ +} + +impl Opt { + pub fn arg(&self) -> Option { self.arg.clone() } + + /* sets optarg if default is desired */ + pub fn arg_or(&self, default: impl std::fmt::Display) -> String { + default.to_string() + } + + /* makes matching the output of this method more bearable */ + pub fn opt(&self) -> Result<&str, OptError> { + self.opt.as_ref().map(|o| o.as_str()).map_err(OptError::clone) + } + + /* From getopt(3p): + * + * The variable optind is the index of the next element of the argv[] + * vector to be processed. It shall be initialized to 1 by the system, and + * getopt() shall update it when it finishes with each element of argv[]. + * If the application sets optind to zero before calling getopt(), the + * behavior is unspecified. When an element of argv[] contains multiple + * option characters, it is unspecified how getopt() determines which + * options have already been processed. */ + pub fn ind(&self) -> usize { unsafe { *self.ind as usize } } + + /* this is patently terrible and is only happening because I’m stubborn */ + pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } } +} + +/* function signature */ +pub trait GetOpt { + fn getopt(&self, optstring: &str) -> Option; +} + +impl GetOpt for Vec { + fn getopt(&self, optstring: &str) -> Option { + let c_strings: Vec<_> = self + .iter() + .cloned() + .map(|x| CString::new(x).unwrap().into_raw()) + .collect(); + + /* god knows what this does */ + let boxed = Box::into_raw(c_strings.into_boxed_slice()); + let argv = boxed as *const *mut c_char; + + /* operations are separated out so that everything lives long enough */ + let opts = CString::new(optstring).unwrap().into_raw(); + let len = self.len() as c_int; + + unsafe { + let ret = match getopt(len, argv, opts) { + /* From getopt(3p): + * + * The getopt() function shall return the next option character + * specified on the command line. + * + * A (':') shall be returned if getopt() detects a + * missing argument and the first character of optstring was a + * (':'). + * + * A ('?') shall be returned if getopt() + * encounters an option character not in optstring or detects a + * missing argument and the first character of optstring was not + * a (':'). + * + * Otherwise, getopt() shall return -1 when all command line + * options are parsed. */ + 58 => { /* ASCII ':' */ + Some(Opt { + arg: None, + ind: std::ptr::addr_of_mut!(optind), + /* error containing option */ + opt: Err(OptError::MissingArg(optopt.to_string())), + }) + }, + 63 => { /* ASCII '?' */ + Some(Opt { + arg: None, + ind: std::ptr::addr_of_mut!(optind), + /* error containing option */ + opt: Err(OptError::UnknownOpt(optopt.to_string())), + }) + }, + /* From getopt(3p): + * + * If, when getopt() is called: + * + * argv[optind] is a null pointer + * *argv[optind] is not the character - + * argv[optind] points to the string "-" + * + * getopt() shall return -1 without changing optind. If: + * + * argv[optind] points to the string "--" + * + * getopt() shall return -1 after incrementing optind. */ + -1 => return None, + opt => { + let arg: Option; + + if optarg.is_null() { arg = None; } + else { + arg = Some(CStr::from_ptr(optarg) + .to_string_lossy() + .into_owned()); + } + + Some(Opt { + arg, + ind: std::ptr::addr_of_mut!(optind), + /* I didn’t need to cast this before; I rewrote the + * pointer logic and now I do + * + * I don’t know why this is */ + opt: Ok((opt as u8 as char).to_string()), + }) + }, + }; + + /* delloc argv (something online said I should do this) */ + let _ = Box::from_raw(boxed); + return ret; + } + } +} + +/* tests (good) */ +#[cfg(test)] +mod tests { + use GetOpt; + + #[test] + fn testing() { + let argv: Vec = ["test", "-b", "-f", "arg", "-o", "arg"] + .iter() + .map(|s| s.to_string()) + .collect(); + + while let Some(opt) = argv.getopt(":abf:o:") { + match opt.opt() { + Ok("a") => assert_eq!(opt.ind(), 1), + Ok("b") => assert_eq!(opt.ind(), 2), + Ok("f") | Ok("o") => { + assert_eq!(opt.arg(), Some("arg".into())); + }, + _ => assert!(false), + }; + } + + if let Some(opt) = argv.getopt("abc:") { + opt.clone().set_ind(1); + assert_eq!(opt.ind(), 1); + } + } +} diff --git a/src/libstrerror.rs b/src/libstrerror.rs new file mode 100644 index 0000000..e306e7a --- /dev/null +++ b/src/libstrerror.rs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Emma Tebibyte + * 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. + */ + +use std::ffi::{ c_int, c_char, CStr }; + +pub trait StrError { fn strerror(&self) -> String; } + +impl StrError for std::io::Error { + /* wrapper function for use in Rust */ + fn strerror(&self) -> String { + /* Get the raw OS error. If it’s None, what the hell is going on‽ */ + let errno = self.raw_os_error().unwrap_or(0) as c_int; + + /* Get a CStr from the error message so that it’s referenced and then + * convert it to an owned value. If the string is not valid UTF-8, + * return that error instead. */ + match unsafe { CStr::from_ptr(strerror(errno)) }.to_str() { + Ok(s) => s.to_owned(), // yay!! :D + Err(e) => e.to_string(), // awww :( + } + } +} + +/* binding to strerror(3p) */ +extern "C" { fn strerror(errnum: c_int) -> *mut c_char; } diff --git a/src/mm.c b/src/mm.c new file mode 100644 index 0000000..dc337b7 --- /dev/null +++ b/src/mm.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2024 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +#include /* errno */ +#include /* signal(2), SIG_ERR, SIG_IGN, SIGINT */ +#include /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3), + * setvbuf(3), size_t, _IONBF, NULL */ +#include /* free(3), realloc(3) */ +#include /* strcmp(3), strerror(3) */ +#include /* getopt(3) */ +#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \ + || !defined EX_USAGE +# include +#endif +extern int errno; + +/* This structure is how open files are tracked. */ +struct Files{ + size_t a; /* allocation */ + size_t s; /* used size */ + char *mode; /* file opening mode */ + char **names; /* file names */ + FILE **files; /* file pointers */ +}; + +/* How much to grow the allocation when it's saturated. */ +#ifndef ALLOC_INCREMENT +# define ALLOC_INCREMENT 1 +#endif + +/* How much to grow the allocation at program start. */ +#ifndef ALLOC_INITIAL +# define ALLOC_INITIAL 10 +#endif + +/* pre-allocated strings */ +static char *program_name = ""; +static char *stdin_name = ""; +static char *stdout_name = ""; +static char *stderr_name = ""; +static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} }; +static char *wharsh = "wb"; + +/* Adds the open FILE pointer for the file at the path s to the files struct, + * returning the FILE if successful and NULL if not, allocating more memory in + * the files buffers as needed. */ +static FILE * +Files_append(struct Files *files, FILE *file, char *name){ + + if(file == NULL || (files->s == files->a + && ((files->files = realloc(files->files, + (files->a += (files->a == 0) + ? ALLOC_INITIAL + : ALLOC_INCREMENT) + * sizeof *(files->files))) == NULL + || (files->names = realloc(files->names, + files->a * sizeof *(files->names))) == NULL))) + return NULL; + + files->names[files->s] = name; + return files->files[files->s++] = file; +} + +/* Opens the file at the path p and puts it in the files struct, returning NULL + * if either the opening or the placement of the open FILE pointer fail. */ +#define Files_open(files, p) \ + Files_append((files), fopen((p), (files)->mode), (p)) + +/* Prints a diagnostic message based on errno and returns an exit status + * appropriate for an OS error. */ +static int +oserr(char *s, char *r){ + + fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno)); + + return EX_OSERR; +} + +/* Hijacks i and j from main and destructs the files[2] struct used by main by + * closing its files and freeing its files and names arrays, returning retval + * from main. */ +#define terminate \ + for(i = 0; i < 2; ++i){ \ + for(j = 0; j < files[i].s; ++j) \ + if(files[i].files[j] != stdin \ + && files[i].files[j] != stdout \ + && files[i].files[j] != stderr) \ + fclose(files[i].files[j]); \ + free(files[i].files); \ + free(files[i].names); \ + } \ + return retval + +/* Prints a usage text, in which s is the program being run (i.e. argv[0]), and + * returns an exit status appropriate for a usage error. */ +int usage(char *s){ + + fprintf(stderr, "Usage: %s (-aenu) (-i [input])... (-o [output])...\n", s); + + return EX_USAGE; +} + +int main(int argc, char *argv[]){ + int c; + struct Files files[2]; /* {read, write} */ + size_t i; + size_t j; + size_t k; /* loop index but also unbuffer status */ + int retval; + + /* Initializes the files structs with their default values, standard + * input and standard output. If an input or an output is specified + * these initial values will be overwritten, so to, say, use mm(1) + * equivalently to tee(1p), -o - will need to be specified before + * additional files to ensure standard output is still written. */ + for(i = 0; i < 2; ++i){ + files[i].a = 0; + files[i].s = 0; + files[i].mode = fmode[i]; + files[i].files = NULL; + files[i].names = NULL; + Files_append(&files[i], i == 0 ? stdin : stdout, + i == 0 ? stdin_name : stdout_name); + files[i].s = 0; + } + + k = 0; + + if(argc > 0) + program_name = argv[0]; + + if(argc > 1) + while((c = getopt(argc, argv, "aehi:no:u")) != -1) + switch(c){ + case 'a': /* "rb+" -> "ab" */ + files[1].mode[0] = 'a'; + files[1].mode[2] = '\0'; + break; + case 'e': + if(Files_append(&files[1], stderr, stderr_name) != NULL) + break; + retval = oserr(argv[0], "-e"); + terminate; + case 'i': + if((strcmp(optarg, "-") == 0 && Files_append(&files[0], + stdin, stdin_name) != NULL) + || Files_open(&files[0], optarg) != NULL) + break; + retval = oserr(argv[0], optarg); + terminate; + case 'o': + if((strcmp(optarg, "-") == 0 && Files_append(&files[1], + stdout, stdout_name) != NULL) + || Files_open(&files[1], optarg) != NULL) + break; + /* does not exist, so try to create it */ + if(errno == ENOENT){ + files[1].mode = wharsh; + if(Files_open(&files[1], optarg) != NULL){ + files[1].mode = fmode[1]; + break; + } + } + retval = oserr(argv[0], optarg); + terminate; + case 'n': + if(signal(SIGINT, SIG_IGN) != SIG_ERR) + break; + retval = oserr(argv[0], "-n"); + terminate; + case 'u': + k = 1; + break; + default: + retval = usage(argv[0]); + terminate; + } + + if(optind != argc){ + retval = usage(argv[0]); + terminate; + } + + files[0].s += files[0].s == 0; + files[1].s += files[1].s == 0; + + /* Unbuffer files. */ + if(k){ + for(i = 0; + i < files[0].s; + setvbuf(files[0].files[i++], NULL, _IONBF, 0)); + for(i = 0; + i < files[1].s; + setvbuf(files[1].files[i++], NULL, _IONBF, 0)); + } + + retval = EX_OK; + + /* Actual program loop. */ + for(i = 0; i < files[0].s; ++i) /* iterate ins */ + while((c = getc(files[0].files[i])) != EOF) /* iterate chars */ + for(j = 0; j < files[1].s; ++j) /* iterate outs */ + if(putc(c, files[1].files[j]) == EOF){ + /* notebook's full */ + retval = EX_IOERR; + fprintf(stderr, "%s: %s: %s\n", + program_name, files[1].names[j], strerror(errno)); + if(fclose(files[1].files[j]) == EOF) + fprintf(stderr, "%s: %s: %s\n", + program_name, files[1].names[j], strerror(errno)); + /* massage out the tense muscle */ + for(k = j--; k < files[1].s - 1; ++k){ + files[1].files[k] = files[1].files[k+1]; + files[1].names[k] = files[1].names[k+1]; + } + if(--files[1].s == 0) + terminate; + } + + terminate; +} diff --git a/src/rpn.rs b/src/rpn.rs index 0cb23b8..2bfbbf5 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -172,7 +172,7 @@ fn eval( }; } else { return Err(EvaluationError { - message: format!("{}: Unexpected operation.", op), + message: format!("{}: Unexpected operation", op), code: EX_DATAERR, }) } diff --git a/src/scrut.c b/src/scrut.c index e2494d3..c5b675f 100644 --- a/src/scrut.c +++ b/src/scrut.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 2023 DTB + * Copyright (c) 2023–2024 DTB + * Copyright (c) 2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under @@ -17,13 +18,15 @@ */ #include /* fprintf(3), stderr, NULL */ -#include /* EXIT_FAILURE */ +#include /* EXIT_FAILURE, EXIT_SUCCESS */ #include /* memset(3), strchr(3) */ +#ifndef EX_USAGE +# include +#endif #include /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */ #include /* lstat(3), stat struct, S_ISBLK, S_ISCHR, S_ISDIR, * S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK, * S_ISUID, S_ISVTX */ -#include static char args[] = "bcdefghkprsuwxLS"; static char ops[(sizeof args) / (sizeof *args)]; @@ -57,7 +60,7 @@ int main(int argc, char *argv[]){ argv += optind; do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) - return 1; /* doesn't exist or isn't stattable */ + return EXIT_FAILURE; /* doesn't exist or isn't stattable */ for(i = 0; ops[i] != '\0'; ++i) if(ops[i] == 'e') @@ -97,8 +100,8 @@ usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n", && !S_ISLNK(buf.st_mode)) || (ops[i] == 'S' && !S_ISSOCK(buf.st_mode))) - return 1; + return EXIT_FAILURE; }while(*++argv != NULL); - return 0; + return EXIT_SUCCESS; } diff --git a/src/swab.rs b/src/swab.rs new file mode 100644 index 0000000..d05b651 --- /dev/null +++ b/src/swab.rs @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 DTB + * 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, + io::{ stdin, stdout, Error, ErrorKind, Read, Write }, + process::ExitCode, + vec::Vec +}; + +extern crate getopt; +use getopt::GetOpt; + +extern crate sysexits; +use sysexits::{ EX_OK, EX_OSERR, EX_USAGE }; + +fn oserr(s: &str, e: Error) -> ExitCode { + eprintln!("{}: {}", s, e); + ExitCode::from(EX_OSERR as u8) +} + +fn usage(s: &str) -> ExitCode { + eprintln!("Usage: {} (-f) (-w [wordsize])", s); + ExitCode::from(EX_USAGE as u8) +} + +fn main() -> ExitCode { + let argv = args().collect::>(); + let mut buf: Vec = Vec::new(); + let mut input = stdin(); + let mut output = stdout().lock(); + + let mut force = false; + let mut wordsize: usize = 2; + + while let Some(opt) = argv.getopt("fw:") { + match opt.opt() { + Ok("f") => force = true, + Ok("w") => { + if let Some(arg) = opt.arg() { + match arg.parse::() { + Ok(w) if w % 2 == 0 => { wordsize = w; () }, + _ => { return usage(&argv[0]); }, + } + } + }, + _ => { return usage(&argv[0]); } + } + } + + buf.resize(wordsize, 0); + + loop { + match input.read(&mut buf) { + Ok(0) => break ExitCode::from(EX_OK as u8), + Ok(v) if v == wordsize => { + let (left, right) = buf.split_at(v/2); + if let Err(e) = output.write(&right) + .and_then(|_| output.write(&left)) { + break oserr(&argv[0], e) + } + }, + Ok(v) => { + if let Err(e) = output.write(&buf[..v]) { + break oserr(&argv[0], e) + } + }, + Err(e) if e.kind() == ErrorKind::Interrupted && force => continue, + Err(e) => break oserr(&argv[0], e) + } + } +}