1
0
Fork 0

Compare commits

...

135 Commits
main ... main

Author SHA1 Message Date
dtb 69510d76af
Merge branch 'main' into dj 2024-06-29 19:23:03 -06:00
dtb 6b28a12b73
dj.1: last minute changes 2024-06-29 19:14:08 -06:00
Emma Tebibyte 881df1bb18
Merge branch 'usage-text' 2024-06-29 17:20:19 -06:00
Emma Tebibyte 40984453e3
Makefile: better leverage targets for sysexits build 2024-06-29 08:01:33 -06:00
Emma Tebibyte cb88ed9809
Merge branch 'getopt-bindings' 2024-06-29 07:51:01 -06:00
Emma Tebibyte cf5136d247
Makefile, swab(1): fixes swab build 2024-06-29 07:49:47 -06:00
Emma Tebibyte 17455baeab
intcmp(1), npc(1): removes vestigial option 2024-06-29 06:38:55 -06:00
dtb 6bd19c072d
swab(1): fix some silly mistakes 2024-06-29 06:02:39 -06:00
dtb 3b5ddede98
Merge branch 'main' into dj 2024-06-29 05:56:11 -06:00
dtb 67b60e20cc
dj.1: Man page fixes 2024-06-29 05:55:29 -06:00
Emma Tebibyte 07a12ba81c
docs, src: fixing man page and usage text readability 2024-06-29 05:28:23 -06:00
Emma Tebibyte e341c38cd6
docs, src: updates usage text for utilities 2024-06-29 05:18:20 -06:00
Emma Tebibyte 50bbee10a9
libgetopt.rs(3): fixes typecasting for ARM devices 2024-06-28 10:22:20 -06:00
dtb d07bb7da41
swab(1): untested move to new getopt bindings 2024-06-28 08:33:31 -06:00
Emma Tebibyte 2f87ad948f
strerror.rs(3), getopt.rs(3): renames 2024-06-27 14:04:31 -06:00
dtb 3a66022c6d
dj(1): more refactor (get rid of the c scratch variable, use scoping) 2024-06-26 15:28:02 -06:00
dtb 2cfae0e8d7
dj(1): refactor (remove Io_setdefaults and other stuff) 2024-06-26 15:15:37 -06:00
dtb fb74e7bef0
dj(1), dj.1: Remove -A (use -a "\0") (see #101) 2024-06-26 13:45:36 -06:00
dtb e65f6b650d
dj(1): more refactor (get rid of ep pun) 2024-06-26 13:41:24 -06:00
dtb b70b356ce5
dj(1): remove Io_buffree 2024-06-26 12:45:50 -06:00
dtb 66f5498232
dj(1): refactor Io_fdseek 2024-06-26 12:40:36 -06:00
dtb 45a880455d
dj(1): refactor to build again and to get rid of globals 2024-06-26 12:22:33 -06:00
dtb 95f7992e0f
dj(1): fix usage text to be consistent with man page 2024-06-26 11:39:34 -06:00
dtb d3f5246242
dj(1), dj.1: remove the unnecessary -d and -q 2024-06-26 11:36:52 -06:00
Emma Tebibyte 0fc9a6b533
Makefile, docs: programmatically generate version for docs (i got tired of doing it myself) 2024-06-23 23:47:29 -06:00
Emma Tebibyte 8b400d8a62
Makefile, docs: rename 2024-06-23 23:34:23 -06:00
Emma Tebibyte 6e4aeb7be7
fop(1): bring getopt(3) usage up-to-date 2024-06-23 01:00:48 -06:00
Emma Tebibyte 965d1bb76e
Merge branch 'main' into getopt-bindings 2024-06-23 00:57:20 -06:00
Emma Tebibyte e1bf49c75a
getopt.rs(3): adds comments & documentation 2024-06-22 22:30:30 -06:00
Emma Tebibyte 8f990ba515
getopt.rs(3): adds testing 2024-06-22 22:18:13 -06:00
Emma Tebibyte 4b3333d8d3
fop(1): record separator worky now? 2024-06-21 03:26:42 -06:00
Emma Tebibyte 578d947561
Merge branch 'README-clarity' 2024-06-19 23:30:03 -06:00
Emma Tebibyte 2e91338101
Merge branch 'makefile-posix-2024' 2024-06-19 23:29:42 -06:00
Emma Tebibyte 72f57ba08b
Makefile: adds octal disclaimer 2024-06-19 23:29:22 -06:00
Emma Tebibyte 35f49a699f
fop(1): fixes record separator, again 2024-06-19 15:26:46 -06:00
Emma Tebibyte e1ac40e7ee
docs: updates version number 2024-06-19 15:03:06 -06:00
Emma Tebibyte 125b4c8930
README: updated for clarity 2024-06-19 02:52:58 -06:00
Emma Tebibyte f553cff096
Makefile: removes unneeded comment 2024-06-18 16:33:22 -06:00
Emma Tebibyte d201f9228c
Makefile: updates to use new POSIX 2024 standard features! 2024-06-18 16:32:20 -06:00
Emma Tebibyte 6814111ad1
dj.1: fixes many clunky sentences 2024-06-17 23:37:13 -06:00
Emma Tebibyte e38dcc09b1
intcmp.1: bold 2024-06-17 23:27:19 -06:00
Emma Tebibyte 59eee27979
strcmp.1: or 2024-06-17 23:21:44 -06:00
Emma Tebibyte bf10689606
swab.1: bold 2024-06-17 23:20:29 -06:00
Emma Tebibyte 266ee20d5c
fop.1: removes unnecessary stdin section and adds AUTHOR 2024-06-17 23:18:45 -06:00
Emma Tebibyte 1b299f8ee1
rpn.1: fixes clunkiness 2024-06-17 23:16:25 -06:00
Emma Tebibyte 53d5a1db73
hru.1: italics and removes clunky sentences 2024-06-17 23:10:01 -06:00
Emma Tebibyte ee9d42d0d4
str.1: cleanup 2024-06-17 23:02:13 -06:00
Emma Tebibyte 15d5761cd7
mm.1: fixes clunky sentence 2024-06-17 22:57:08 -06:00
Emma Tebibyte 376feb9ae9
npc.1: fixes minor details 2024-06-17 22:56:17 -06:00
Emma Tebibyte 314254b32f
docs: hotfix 2024-06-06 13:32:54 -06:00
Emma Tebibyte 40da8135f1
getopt.rs(3): relicense to AGPLv3 2024-06-06 01:03:01 -06:00
Emma Tebibyte 51421b2128
dj.1, strcmp.1, swab.1: remove nonexistent roff macros 2024-06-06 00:14:55 -06:00
Emma Tebibyte 28823206bd
Merge branch 'docs' (closes #81) 2024-06-05 23:22:57 -06:00
Emma Tebibyte 4d27a4976c
swab.1: -f Ignores the SIGINT signal 2024-06-05 23:17:21 -06:00
Emma Tebibyte 8ce7890124
fop.1: removes Unicode representation of RS; hru.1: removes erroneous line break; mm.1: formatting & grammar; npc.1: dollar sign, not currency sign; rpn.1: syntax error; scrut.1: move description to DESCRIPTION; str.1: comma; swab.1: wording & escape codes; true.1: formatting 2024-06-05 23:07:15 -06:00
Emma Tebibyte 526185c2c3
swab.1: typo 2024-06-05 22:35:50 -06:00
Emma Tebibyte ad6f9d565b
dj.1: formats sysexits.h(3) reference 2024-06-05 20:39:37 -06:00
Emma Tebibyte f3f3b7f9aa
Merge branch 'coc' (closes #100) 2024-06-05 17:35:16 -06:00
Emma Tebibyte d22d90b6ec
Merge branch 'fop-delim' (closes #106) 2024-06-05 17:28:51 -06:00
Emma Tebibyte 516d10e21d
CONDUCT: wording 2024-06-05 17:27:13 -06:00
Emma Tebibyte 3392d64e44
fop(1): literal record separator 2024-06-05 17:25:22 -06:00
Emma Tebibyte 11b1b424a0
dj.1: more clarification 2024-06-05 17:18:21 -06:00
Emma Tebibyte 3c0725f137
dj.1: adds DESCRIPTION and removes seek/skip confusion 2024-06-05 17:15:42 -06:00
Emma Tebibyte 02daca87dc
fop(1): updated to latest optind api 2024-06-05 11:55:16 -06:00
Emma Tebibyte e862b7fec6
getopt.rs(3): safe api around optind reading and setting 2024-06-05 11:54:55 -06:00
Emma Tebibyte 05cde15105
fop(1): updated to use new optind api 2024-06-04 19:15:55 -06:00
Emma Tebibyte 35ddc729fd
getopt.rs(3): makes optind part of the Opt struct 2024-06-04 19:15:22 -06:00
Emma Tebibyte 46c4be5c59
CONDUCT: no llm contributions 2024-06-04 16:23:27 -06:00
Emma Tebibyte f4cad598c4
docs: formatting 2024-06-04 16:11:33 -06:00
Emma Tebibyte b7283d54fe
getopt.rs(3): formatting & organization 2024-06-04 15:07:11 -06:00
Emma Tebibyte a5a9c91cb6
getopt.rs(3): optind support 2024-06-04 15:04:01 -06:00
Emma Tebibyte 896b251434
hru.1: fixes formatting 2024-06-03 23:52:28 -06:00
Emma Tebibyte fec61fee1b
swab.1: fixes formatting 2024-06-03 23:47:14 -06:00
Emma Tebibyte beff2d98c6
mm.1: fixed manpage reference formatting 2024-06-03 23:45:26 -06:00
Emma Tebibyte e7ba74013b
dj.1: fixes man page reference formatting 2024-06-03 23:21:00 -06:00
Emma Tebibyte 1211524dee
Merge branch 'README-updates' 2024-06-03 23:08:02 -06:00
Emma Tebibyte c32c554e03
docs: removed unnecessary comments 2024-06-03 23:07:19 -06:00
Emma Tebibyte d6fb08019e
CONDUCT: initial commit 2024-06-02 23:14:40 -06:00
Emma Tebibyte 642774bccf
src: removed erroneous file 2024-06-02 19:21:56 -06:00
Emma Tebibyte 24b235e7c1
README: updated to make current 2024-06-02 19:15:23 -06:00
Emma Tebibyte 2dd6c0ded7
fop(1): adds the ability to use any string as a delimiter 2024-06-02 19:05:30 -06:00
Emma Tebibyte 70cbc52c93
docs: updates to use man(7) macros to fix formatting 2024-06-02 18:47:14 -06:00
Emma Tebibyte b7f52902b6
dj.1: grammar & formatting 2024-05-24 10:21:06 -06:00
Emma Tebibyte 922ee71283
dj.1: fixes standard input section grammar 2024-05-24 10:14:53 -06:00
Emma Tebibyte 122a10257e
dj.1: fixed option description locations 2024-05-24 10:13:59 -06:00
Emma Tebibyte 0660db3d02
dj.1: -c: better description 2024-05-24 10:13:17 -06:00
Emma Tebibyte b4173a2327
dj.1: -o: better description 2024-05-24 10:06:15 -06:00
Emma Tebibyte 2137b0bf43
dj.1: -i: better description 2024-05-24 10:03:52 -06:00
Emma Tebibyte d87f4b20a3
dj.1: -A: better description 2024-05-24 09:56:44 -06:00
Emma Tebibyte c53f3fa617
dj.1: remove mention of KiB 2024-05-24 09:53:54 -06:00
Emma Tebibyte 432b19818e
made dj options no longer alphabetized 2024-04-28 20:42:44 -06:00
Emma Tebibyte 9093b06166
Merge branch 'manpage-install' 2024-04-27 12:58:50 -06:00
dtb 6132c9bf47
Merge branch 'main' into scrut-sysexits 2024-04-26 19:37:34 -06:00
dtb 919b171400
scrut(1): conditionally include non-POSIX sysexits.h 2024-04-26 19:35:33 -06:00
dtb 8e38db92c7
scrut(1): update copyright header 2024-04-26 19:33:45 -06:00
dtb adc9dbded5
scrut(1): changed return status 1 to EXIT_FAILURE 2024-04-26 19:30:33 -06:00
Emma Tebibyte 488351da39
scrut(1): changed return status 0 to EXIT_SUCCESS 2024-04-25 16:46:05 -06:00
Emma Tebibyte d8b54fdbf5
Makefile: fixes erroneous whitespace 2024-04-25 12:43:31 -06:00
Emma Tebibyte f6aac60aee
Makefile: fixed manpage install location 2024-04-25 12:40:20 -06:00
Emma Tebibyte 187d9486b7
dj.1: debug output clarification 2024-04-18 08:44:44 -06:00
Emma Tebibyte b41af1b578
dj.1: -c: grammar 2024-04-18 08:41:55 -06:00
Emma Tebibyte ed284b9949
dj.1: -a: More specific wording 2024-04-18 08:40:29 -06:00
Emma Tebibyte 3cdade71e2
dj.1: -S whether or not the input is a stream is irrelevant 2024-04-18 08:38:48 -06:00
Emma Tebibyte df16707b0e
intcmp.1: -e permits adjacent integers to be equal to each other 2024-04-18 08:36:01 -06:00
Emma Tebibyte 78eacd660a
getopt.rs(3): better Opt return 2024-04-13 23:42:11 -06:00
Emma Tebibyte d1b77d652b
getopt.rs(3): returns last parsed option 2024-04-13 17:11:04 -06:00
Emma Tebibyte 8c255e61fc
COPYING.GPL: initial commit 2024-04-12 00:12:37 -06:00
Emma Tebibyte 95927ba8c5
getopt.rs(3): formatting 2024-04-11 23:51:52 -06:00
Emma Tebibyte ad92fe27d4
fop(1): switch to getopt.rs(3) 2024-04-11 23:37:04 -06:00
Emma Tebibyte bb2c63bfaf
getopt.rs(3): fixed pointer shenanigans 2024-04-11 20:33:42 -06:00
Emma Tebibyte 88b0d55440
getopt.rs(3): refactor to remove as much as possible from unsafe 2024-04-11 20:21:01 -06:00
Emma Tebibyte 0164b681c0
Makefile: remove unneeded test 2024-04-01 20:41:07 -06:00
Emma Tebibyte 320a70bc56
getopt.rs(3): initial commit 2024-04-01 20:39:10 -06:00
dtb 61382c34d9
mm(1): error out when given positional arguments 2024-03-31 22:54:03 -06:00
Emma Tebibyte 13ee16173e
fop.1: initial commit 2024-03-29 16:47:00 -06:00
Emma Tebibyte 5db09a5ca1
mm.1: cat(1p) and tee(1p) provide similar functionality 2024-03-29 16:30:06 -06:00
Emma Tebibyte ce5a4dc4bd
mm.1: removed STANDARD INPUT section 2024-03-29 16:29:27 -06:00
Emma Tebibyte abc599148d
mm.1: subsequent outputs are opened for appending 2024-03-29 16:26:41 -06:00
Emma Tebibyte 63c8ff8093
intcmp.1: compares integers to each other 2024-03-29 16:22:56 -06:00
Emma Tebibyte 9ea57a27b7
dj.1: stdin by default 2024-03-29 16:21:02 -06:00
Emma Tebibyte 4e33f945ae
dj.1: null bytes 2024-03-29 16:17:48 -06:00
Emma Tebibyte 70b0c2f924
dj.1: fixed -d description 2024-03-29 16:14:27 -06:00
Emma Tebibyte 603d8ee1d8
str.1: remove extraneous former implementation information 2024-03-27 00:17:33 -06:00
Emma Tebibyte cdd8e79b01
str.1: strings are not tested against each other 2024-03-27 00:16:51 -06:00
Emma Tebibyte d3bfc7b1f5
npc.1: ASCII bytes 2024-03-27 00:16:15 -06:00
Emma Tebibyte 3cb37d830a
mm.1: wording, consistency with dj.1 2024-03-27 00:14:34 -06:00
Emma Tebibyte 6158a39a4a
dj.1: consistency with mm.1 2024-03-27 00:14:02 -06:00
Emma Tebibyte a2188dc674
dj.1: fix -H description 2024-03-27 00:09:17 -06:00
Emma Tebibyte 49e2022e52
dj.1: -d, -i, -o, fixed descriptions 2024-03-27 00:08:43 -06:00
Emma Tebibyte bb43533a37
dj.1: -A and -a: fix descriptions 2024-03-26 23:58:00 -06:00
Emma Tebibyte f565f0530b
dj.1: dd(1p) is not a disk utility 2024-03-26 23:53:10 -06:00
Emma Tebibyte a1902df503
strcmp.1: Unicode is a proper noun 2024-03-26 23:50:16 -06:00
Emma Tebibyte 63a0c683f9
docs: remove unnecessary references to the name of each program 2024-03-26 19:22:30 -06:00
Emma Tebibyte cf76fa94e6
mm.1: updated man page 2024-03-26 18:44:05 -06:00
Emma Tebibyte a6fd1108c6
docs: fixed formatting of many manpages 2024-03-26 18:26:51 -06:00
36 changed files with 1189 additions and 1806 deletions

83
CONDUCT Normal file
View File

@ -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 Bhikkhus 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] <https://www.dhammatalks.org/books/#/books/TenPerfections/Section0001.html>
[1] <https://www.fsf.org/news/publication-of-the-fsf-funded-white-papers-on-questions-around-copilot>

View File

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

View File

@ -8,15 +8,16 @@
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.POSIX:
# The octal escape \043 is utilized twice in this file as make(1p) will
# interpret a hash in a rule as an inline comment.
# if using BSD make(1), remove these pragmas because they break it
.PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
.PRAGMA: command_comment # breaks without this?
.POSIX:
DESTDIR ?= dist
PREFIX ?= /usr/local
MANDIR != [ $(PREFIX) = / ] && printf '/usr/share/man\n' \
|| printf '/share/man\n'
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | sed 's/ /\n/g' \
| sed -n 's/sysexits\.h//p' || printf 'include\n'
@ -28,12 +29,12 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
CFLAGS += -I$(SYSEXITS)
.PHONY: all
all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
all: 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:
@ -42,36 +43,47 @@ clean:
dist: all
mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1
cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp docs/*.1 $(DESTDIR)/$(PREFIX)/share/man/man1
cp build/docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
.PHONY: install
install: dist
cp -r $(DESTDIR)/* /
.PHONY: test
test: build
test: build /tmp/getopt
/tmp/getopt
tests/posix-compat.sh
$(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt
/tmp/getopt: src/libgetopt.rs
$(RUSTC) --test -o /tmp/getopt src/libgetopt.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/getopt-rs/lib.rs
build/o/libgetopt.rlib: build src/libgetopt.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \
-o $@ src/getopt-rs/lib.rs
-o $@ src/libgetopt.rs
build/o/libstrerror.rlib: build src/strerror.rs
build/o/libstrerror.rlib: build src/libstrerror.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \
src/strerror.rs
src/libstrerror.rs
build/o/libsysexits.rlib: build $(SYSEXITS)sysexits.h
# bandage solution until bindgen(1) gets stdin support
printf '#define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h \
> build/include/sysexits.h
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
build/bin/dj: src/dj.c build
@ -102,7 +114,6 @@ 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
@ -130,10 +141,8 @@ build/bin/strcmp: src/strcmp.c build
.PHONY: swab
swab: build/bin/swab
build/bin/swab: src/swab.rs build build/o/libsysexits.rlib
$(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/swab.rs
build/bin/swab: src/swab.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/swab.rs
.PHONY: true
true: build/bin/true

27
README
View File

@ -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 dont
make anything better.
their flaws. This project originated from frustrations with the way certain
tools work and how other projects that extend POSIX dont 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

249
docs/dj.1
View File

@ -1,160 +1,153 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH dj 1
.\"
.TH DJ 1 2024-06-29 "Harakit X.X.X"
.SH NAME
dj \(en disk jockey
.\"
.SH SYNOPSIS
dj
.RB ( -AdHnq )
.RB ( -a
.RB [ byte ])
.RB ( -c
.RB [ count ])
.RB [ -Hn ]
.RB [ -a\ byte ]
.RB [ -c\ count ]
.RB ( -i
.R [
.B input file
.R ])
.RB ( -b
.R [
.B input block size
.R ])
.RB ( -s
.R [
.B input offset
.R ])
.RB [ -i\ file ]
.RB [ -b\ block_size ]
.RB [ -s\ offset ]
.RB ( -o
.R [
.B output file
.R ])
.RB ( -B
.R [
.B output block size
.R ])
.RB ( -S
.R [
.B output offset
.R ])
.RB [ -o\ file ]
.RB [ -B\ block_size ]
.RB [ -S\ offset ]
.\"
.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\ \fIfile\fP
Takes a file path as an argument and opens it for use as an input.
.IP \fB-b\fP\ \fIblock_size\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 index of the byte at which reading will
commence; \(lqskips\(rq that number of bytes. 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\ \fIblock_size\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
.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.
but for the output buffer.
.IP \fB-S\fP
Takes a numeric argument as the index of the byte at which writing will
commence; \(lqseeks\(rq that number of bytes. If the standard output is used,
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. If the option argument is empty, the
null byte is used.
.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-H\fP
Prints diagnostic messages in a human-readable manner as described in the
DIAGNOSTICS section.
.IP \fB-n\fP
Retries failed reads once before exiting.
.\"
.SH STANDARD INPUT
The standard input shall be used as an input if no inputs are specified or if
input file is \(lq-\(rq.
.\"
.SH STANDARD OUTPUT
The standard output shall be used as an output if no inputs are specified or if
the output file 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
.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.
On a partial or empty read, 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
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
option is 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
<https://gnu.org/licenses/agpl.html>.
.\"
.SH SEE ALSO
dd(1)
.BR dd (1p)

View File

@ -1,35 +1,35 @@
.\" Copyright (c) 2022, 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH 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 <emma@tebibyte.media>.
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
<http://creativecommons.org/publicdomain/zero/1.0>.
.\"
.SH SEE ALSO
true(1p)
.BR true (1p)

62
docs/fop.1 Normal file
View File

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

View File

@ -2,56 +2,68 @@
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH 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 projects 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 GNUs 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 <emma@tebibyte.media>.
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
<https://gnu.org/licenses/agpl.html>.
.\"
.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

View File

@ -1,78 +1,103 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH intcmp 1
.\"
.TH INTCMP 1 2024-06-06 "Harakit X.X.X"
.SH NAME
intcmp \(en compare integers
.\"
.SH SYNOPSIS
intcmp
.RB ( -eghl )
.RB [ integer ]
.RB [ integer... ]
.RB [ -egl ]\ integer\ 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.
Strcmps 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 <trinity@trinity.moe>.
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
<https://gnu.org/licenses/gpl.html>.
.\"
.SH SEE ALSO
strcmp(1), scrut(1), str(1), test(1p)
.BR scrut (1),
.BR strcmp (1),
.BR str (1),
.BR test (1p)

101
docs/mm.1
View File

@ -2,75 +2,70 @@
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH mm 1
.\"
.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 ])
.RB [ -aenu ]
.RB [ -i\ input ]
.RB [ -o\ output ]
.\"
.SH DESCRIPTION
Mm catenates input files and writes them to the start of each output file.
Catenate input files and write them to the start of each output file or stream.
.\"
.SH OPTIONS
Mm, upon receiving the
.B -a
option, will open subsequent outputs for appending rather than updating.
.PP
The
.B -i
option opens a path as an input. Without any inputs specified mm will use
standard input. Standard input itself can be specified by giving the path '-'.
.PP
The
.B -o
option opens a path as an output. Without any outputs specified mm will use
standard output. Standard output itself can be specified by giving the
path '-'. Standard error itself can be specified with the
.B -e
option.
.PP
The
.B -u
option ensures neither input or output will be buffered.
.PP
The
.B -n
option tells mm to ignore SIGINT signals.
.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\ \fIinput\fP
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
no inputs are specified, the standard input shall be used.
.IP \fB-o\fP\ \fIoutput\fP
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
no outputs are specified, the standard output shall be used.
.IP \fB-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 can no longer be written mm prints a diagnostic message, ceases
writing to that particular output, and if there are more outputs specified,
continues, eventually exiting unsuccessfully.
.PP
On error mm prints a diagnostic message and exits with the appropriate
sysexits.h(3) status.
If an output cannot be written to, an error occurs; however, exiting will be
deferred until writing to any other specified outputs completes.
.SH BUGS
Mm does not truncate existing files, which may lead to unexpected results.
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
Mm was modeled after the cat and tee utilities specified in POSIX.
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 (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.\"
.SH SEE ALSO
cat(1p), dd(1), dj(1), tee(1p)
.BR cat (1p),
.BR dd (1),
.BR dj (1),
.BR tee (1p)

View File

@ -1,68 +1,74 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH 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 <trinity@trinity.moe>.
Written by DTB
.MT trinity@trinity.moe
.ME .
.\"
.SH COPYRIGHT
Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.\"
.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

View File

@ -3,68 +3,84 @@
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH 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 <emma@tebibyte.media>.
Written by Emma Tebibyte
.MT emma@tebibyte.media
.ME .
.\"
.SH COPYRIGHT
Copyright (c) 2024 Emma Tebibyte. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.\"
.SH SEE ALSO
bc(1p), dc(1), rpn(7), IEEE 754
.BR bc (1p),
.BR dc (1),
.BR rpn (7),
.I IEEE 754

View File

@ -1,93 +1,86 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH 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 [ file... ]
.RB [ -LSbcdefgkprsuwx ]
.B 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 <trinity@trinity.moe>.
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
<https://gnu.org/licenses/agpl.html>.
.\"
.SH SEE ALSO
access(3p), lstat(3p), test(1p)
.BR access (3p),
.BR lstat (3p),
.BR test (1p)

View File

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

View File

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

View File

@ -1,71 +1,77 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH swab 1
.\"
.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 ])
.RB [ -f ]
.RB [ -w\ word_size ]
.\"
.SH DESCRIPTION
.SH USAGE
Swab swaps the latter and former halves of a block of bytes.
Swap the latter and former halves of a block of bytes.
.\"
.SH OPTIONS
.IP \fB-f\fP
Ignore SIGINT signal.
.IP \fB-w\fP\ \fIword_size\fP
Configures the word size; that is, the size in bytes of the block size on which
to operate. The default word size is 2. The word size must be cleanly divisible
by 2, otherwise the block of bytes being processed can\(cqt be halved.
.\"
.SH EXAMPLES
The following sh(1p) line:
The following
.BR sh (1p)
line:
.R printf 'hello world!\n' | swab
.RS
printf 'hello world!\(rsn' | swab
.RE
Produces the following output:
.R ehll oowlr!d
.SH OPTIONS
The
.B -f
option ignores system call interruptions.
.PP
The
.B -w
option configures the word size; that is, the size in bytes of the block size
on which to operate. By default the word size is 2. The word size must be
cleanly divisible by 2, otherwise the block of bytes being processed can't be
halved.
.RS
ehll oowlr!d
.RE
.\"
.SH DIAGNOSTICS
If an error is encountered in input, output, or invocation, a diagnostic
message will be written to standard error and swab will exit with the
appropriate status from sysexits.h(3).
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
Swab was modeled after the
.R conv=swab
functionality specified in the POSIX dd utility but additionally allows the
word size to be configured.
.PP
Swab is useful for fixing the endianness of binary files produced on other
machines.
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 (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.\"
.SH SEE ALSO
dd(1p)
.BR dd (1p)

View File

@ -1,35 +1,36 @@
.\" Copyright (c) 2022, 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH 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 <emma@tebibyte.media>.
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
<http://creativecommons.org/publicdomain/zero/1.0>.
.\"
.SH SEE ALSO
false(1p)
.BR false (1p),
.BR true (1p)

283
src/dj.c
View File

@ -16,7 +16,6 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <ctype.h> /* isupper(3), tolower(3) */
#include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */
#include <stdio.h> /* fprintf(3), stderr */
@ -25,8 +24,12 @@
#include <sysexits.h> /* EX_OK, EX_USAGE */
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
#include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH,
S_IWUSR */
extern int errno;
char *program_name = "dj";
/* dj uses two structures that respectively correspond to the reading and
* writing ends of its jockeyed "pipe". User-configurable members are noted
* with their relevant options. */
@ -41,56 +44,17 @@ struct Io{
int prec; /* partial records processed */
int rec; /* records processed */
long seek; /* bytes to seek/skip (will be 0 after skippage) (-sS) */
} ep[2]; /* "engineered pipe"; also "extended play", for the deejay */
};
/* Additionally, the following global variables are used to store user options.
*/
/* To be assigned to main:fmt_output and used with output(). */
static char *fmt_asv = "%d\037%d\036%d\037%d\035%d\036%d\034";
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
/* (-a) */ static int align; /* Only the lower 8b are used but align is
* negative if no alignment is being done. */
/* (-c) */ static int count; /* 0 if dj(1) runs until no more reads are
* possible. */
/* ASCII field separator delimited statistics */
static char *fmt_asv = "%d\037%d\036%d\037%d\035%d\036%d\034";
/* human-readable statistics */
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
/* pointer to chosen formatting */
/* (-H) */ static char *fmt_output; /* fmt_asv (default) or fmt_human (-H) */
/* (-dq) */ static char debug; /*
* -d increments dj -qq | 0 - no diagnostic output whatsoever
* -q decrements dj -q | 1 - typical output without
* | notifications on partial reads or
* | writes
* dj | 2 - typical output (default)
* dj -d | 3 - verbose status messages */
/* (-n) */ static char noerror; /* 0 - exits on partial reads or writes
* (default)
* 1 - retries on partial reads/writes
* (-n) */
/* Non-configurable defaults. */
#define bs_default 1024 /* GNU dd(1) default; twice POSIX but a neat 2^10 */
static char *program_name = "<no argv[0]>";
static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>";
static int read_flags = O_RDONLY; /* These flags are consistent with Busybox */
static int write_flags = O_WRONLY | O_CREAT; /* dd(1). */
/* Macro to set defaults for user-configurable options. */
#define setdefaults do{ \
align = -1; \
count = 0; \
debug = 2; \
fmt_output = fmt_asv; \
noerror = 0; \
ep[0].fl = read_flags; \
Io_setdefaults(&ep[0]); \
ep[1].fl = write_flags; \
Io_setdefaults(&ep[1]); }while(0)
static int read_flags = O_RDONLY; /* Consistent with Busybox dd(1). */
static int write_flags = O_WRONLY | O_CREAT;
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
@ -104,8 +68,8 @@ static int write_flags = O_WRONLY | O_CREAT; /* dd(1). */
* particular io[2] used in main. Error conditions are not checked because this
* is only used when the program is about to terminate (hence its name). */
#define terminate(io) do{ \
Io_buffree(&(io)[0]); \
Io_buffree(&(io)[1]); \
free((io[0]).buf); \
free((io[1]).buf); \
Io_fdclose(&(io)[0]); \
Io_fdclose(&(io)[1]); }while(0)
@ -116,15 +80,6 @@ Io_bufalloc(struct Io *io){
return (io->buf = malloc(io->bs * (sizeof *io->buf)));
}
/* Frees *io's buffer. Returns io. */
static struct Io *
Io_buffree(struct Io *io){
free(io->buf);
return io;
}
/* Fills the unused portion of io's buffer with padding, updating io->bufuse.
* Returns io. */
static struct Io *
@ -193,18 +148,18 @@ Io_fdopen(struct Io *io, char *fn){
return fd;
}
/* Seeks io->seek bytes through *io's file descriptor, (counter-intuitively)
* returning -1 if successful and a sysexits.h exit code if an unrecoverable
* error occurred. io->buf will be cleared of useful bytes and io->seek will
* be set to zero to indicate the seek occurred. */
static int
/* Seeks io->seek bytes through *io's file descriptor, subtracting the number
* of sought bytes from io->seek. This procedure leaves garbage in io->buf. */
static void
Io_fdseek(struct Io *io){
int (*op)(int, void *, size_t);
if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1)
return -1;
if(io->seek != 0
|| (!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1))
return;
if(io->fl == write_flags)
memset(io->buf, '\0', io->bs);
/* 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),
@ -223,12 +178,11 @@ Io_fdseek(struct Io *io){
/* 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;
return -1;
return;
}
/* Reads io->bs bytes from *io's file descriptor into io->buf, storing the
@ -242,23 +196,6 @@ Io_read(struct Io *io){
return io;
}
/* Sets the variables in a struct *io to the defaults. Identifies the read/
* write ends of the "pipe" by checking io->fl. Returns io. */
static struct Io *
Io_setdefaults(struct Io *io){
io->bs = bs_default;
io->buf = NULL;
io->bytes = 0;
io->fd = (io->fl == read_flags) ? STDIN_FILENO : STDOUT_FILENO;
io->fn = (io->fl == read_flags) ? stdin_name : stdout_name;
io->prec = 0;
io->rec = 0;
io->seek = 0;
return io;
}
/* Writes io->bufuse units from io->buf to io->fd, permuting any unwritten
* bytes to the start of io->buf and updating io->bufuse. If io->bufuse doesn't
* change, errno will probably be set. Returns io. */
@ -284,14 +221,13 @@ oserr(char *s){
}
/* Prints statistics regarding the use of dj, particularly partially and
* completely read and written records, accessing debug, ep, and fmt_output. */
* completely read and written records. */
static void
output(void){
output(struct Io io[2], char *fmt){
if(debug >= 1)
fprintf(stderr, fmt_output,
ep[0].rec, ep[0].prec, ep[1].rec, ep[1].prec,
ep[0].bytes, ep[1].bytes);
fprintf(stderr, fmt,
io[0].rec, io[0].prec, io[1].rec, io[1].prec,
io[0].bytes, io[1].bytes);
return;
}
@ -311,142 +247,147 @@ parse(char *s){
}
static int
usage(void){
usage(char *s){
fprintf(stderr, "Usage: %s (-AdfHqQ) (-a [byte]) (-c [count])\n"
"\t(-i [input file]) (-b [input block size]) (-s [input offset])\n"
"\t(-o [output file]) (-B [output block size]) (-S [output offset])\n",
fprintf(stderr, "Usage: %s [-Hn] [-a byte] [-c count]\n"
"\t[-i file] [-b block_size] [-s offset]\n"
"\t[-o file] [-B block_size] [-S offset]\n",
program_name);
return EX_USAGE;
}
int main(int argc, char *argv[]){
int c;
int i;
int align; /* low 8b used, negative if no alignment is being done */
int count; /* 0 if dj(1) runs until no more reads are possible */
char *fmt_output; /* == fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io being modified */
struct Io io[2];
char noerror; /* 0=exits (default) 1=retries on partial reads or writes */
setdefaults;
/* Set defaults. */
align = -1;
count = 0;
fmt_output = fmt_asv;
noerror = 0;
for(i = 0; i < 2; ++i){
io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bytes = 0;
io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i ? stdin_name : stdout_name;
io[i].fl = i ? read_flags : write_flags;
io[i].prec = 0;
io[i].rec = 0;
io[i].seek = 0;
}
if(argc > 0){
int c;
program_name = argv[0];
while((c = getopt(argc, argv, "a:Ab:B:c:di:hHnqs:S:o:")) != -1)
while((c = getopt(argc, argv, "a:b:B:c:i:hHns:S:o:")) != -1)
switch(c){
case 'i': case 'o':
i = (c == 'o');
case 'i': case 'o': i = (c == 'o');
if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */
ep[i].fd = (i == 0) ? STDIN_FILENO : STDOUT_FILENO;
ep[i].fn = (i == 0) ? stdin_name : stdout_name;
io[i].fd = i ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i ? stdin_name : stdout_name;
break;
}else if(Io_fdopen(&ep[i], optarg) != -1)
}else if(Io_fdopen(&io[i], optarg) != -1)
break;
terminate(ep);
terminate(io);
return oserr(optarg);
case 'A': align = '\0'; break;
case 'd': ++debug; break;
case 'n': noerror = 1; break;
case 'H': fmt_output = fmt_human; break;
case 'q': --debug; break;
case 'a':
if(optarg[0] != '\0' && optarg[1] == '\0'){
if(optarg[0] == '\0' || optarg[1] == '\0'){
align = optarg[0];
break;
}
/* FALLTHROUGH */
case 'c': case 'b': case 's': case 'B': case 'S':
case 'c': case 'b': case 's': case 'B': case 'S': /* numbers */
if(c == 'c' && (count = parse(optarg)) >= 0)
break;
i = isupper(c);
c = tolower(c);
if((c == 'b' && (ep[i].bs = parse(optarg)) > 0)
|| (c == 's' && (ep[i].seek = parse(optarg)) >= 0))
i = (c >= 'A' && c <= 'Z'); /* uppercase changes output */
c &= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */
if((c == 'b' && (io[i].bs = parse(optarg)) > 0)
|| (c == 's' && (io[i].seek = parse(optarg)) >= 0))
break;
/* FALLTHROUGH */
default:
terminate(ep);
return usage();
terminate(io);
return usage(program_name);
}
}
if(debug >= 3)
fprintf(stderr,
"argv0=%s\n"
"in=%s\tibs=%d\tskip=%ld\talign=%hhx\tcount=%d\n"
"out=%s\tobs=%d\tseek=%ld\tdebug=%2d\tnoerror=%d\n",
program_name,
ep[0].fn, ep[0].bs, ep[0].seek, align, count,
ep[1].fn, ep[1].bs, ep[1].seek, debug, noerror);
if(argc > optind){
terminate(ep);
return usage();
terminate(io);
return usage(program_name);
}
for(i = 0; i <= 1; ++i){
if(Io_bufalloc(&ep[i]) == NULL){
for(i = 0; i < 2; ++i){
if(Io_bufalloc(&io[i]) == NULL){
fprintf(stderr, "%s: Failed to allocate %d bytes\n",
program_name, ep[i].bs);
terminate(ep);
program_name, io[i].bs);
terminate(io);
return EX_OSERR;
}else if(ep[i].seek > 0)
switch(Io_fdseek(&ep[i])){
case EX_OK:
output();
terminate(ep);
return EX_OK;
}else if(io[i].seek > 0)
Io_fdseek(&io[i]);
if(io[i].seek > 0){
terminate(io);
return oserr(io[i].fn);
}
}
do{ /* read */
Io_read(&ep[0]);
if(!noerror && ep[0].bufuse == 0)
Io_read(&ep[0]); /* second chance */
if(ep[0].bufuse == 0) /* that's all she wrote */
Io_read(&io[0]);
if(!noerror && io[0].bufuse == 0)
Io_read(&io[0]); /* second chance */
if(io[0].bufuse == 0) /* that's all she wrote */
break;
else if(ep[0].bufuse < ep[0].bs){
++ep[0].prec;
if(debug >= 2){
fprintf(stderr, "%s: Partial read:\n\t", program_name);
output();
}
else if(io[0].bufuse < io[0].bs){
++io[0].prec;
fprintf(stderr, "%s: Partial read:\n\t", program_name);
output(io, fmt_output);
if(!noerror)
count = 1;
if(align >= 0)
Io_bufrpad(&ep[0], align);
Io_bufrpad(&io[0], align);
}else
++ep[0].rec;
++io[0].rec;
/* write */
do{ if(ep[1].bs > ep[0].bs){ /* io[1].bs > io[0].bs */
Io_bufxapp(&ep[1], &ep[0]);
if(ep[0].bs + ep[1].bufuse <= ep[1].bs && count != 1)
do{
int t;
if(io[1].bs > io[0].bs){
Io_bufxapp(&io[1], &io[0]);
if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1)
continue; /* we could write more */
}else
Io_bufxfer(&ep[1], &ep[0], MIN(ep[0].bufuse, ep[1].bs));
Io_bufxfer(&io[1], &io[0], MIN(io[0].bufuse, io[1].bs));
c = ep[1].bufuse;
Io_write(&ep[1]);
if(!noerror && ep[1].bufuse == c)
Io_write(&ep[1]); /* second chance */
if(c == ep[1].bufuse){ /* no more love */
t = io[1].bufuse;
Io_write(&io[1]);
if(!noerror && io[1].bufuse == t)
Io_write(&io[1]); /* second chance */
if(t == io[1].bufuse){ /* no more love */
count = 1;
break;
}else if(c > ep[1].bufuse && ep[1].bufuse > 0){
ep[1].prec += 1;
if(debug >= 2){
fprintf(stderr, "%s: Partial write:\n\t", program_name);
output();
}
}else if(t > io[1].bufuse && io[1].bufuse > 0){
io[1].prec += 1;
fprintf(stderr, "%s: Partial write:\n\t", program_name);
output(io, fmt_output);
if(!noerror)
count = 1;
}else if(ep[1].bufuse == 0 && c < ep[1].bs)
++ep[1].prec;
}else if(io[1].bufuse == 0 && t < io[1].bs)
++io[1].prec;
else
++ep[1].rec;
}while(ep[0].bufuse > 0);
++io[1].rec;
}while(io[0].bufuse > 0);
}while(count == 0 || --count > 0);
output();
terminate(ep);
output(io, fmt_output);
terminate(io);
return EX_OK;
}

View File

@ -26,33 +26,39 @@ 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::<Vec<String>>();
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::<Vec<char>>();
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);
});
@ -63,7 +69,7 @@ fn main() {
let mut buf = String::new();
let _ = stdin().read_to_string(&mut buf);
let mut fields = buf.split(d).collect::<Vec<&str>>();
let mut fields = buf.split(&d).collect::<Vec<&str>>();
let opts = argv
.iter()

View File

@ -1,95 +0,0 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*
* 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
}
}

View File

@ -1,61 +0,0 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*
* 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,
}

View File

@ -1,72 +0,0 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*
* 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;

View File

@ -1,89 +0,0 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*
* 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<dyn std::error::Error>> {
/// use getopt::Opt;
///
/// // args = ["program", "-abc", "foo"];
/// # let args: Vec<String> = 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<String>);
impl fmt::Display for Opt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Opt({:?}, {:?})", self.0, self.1)
}
}

View File

@ -1,382 +0,0 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*
* 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<dyn std::error::Error>> {
/// use getopt::Opt;
///
/// // args = ["program", "-abc", "foo"];
/// # let args: Vec<String> = 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<dyn std::error::Error>> {
/// use getopt::Opt;
///
/// // args = ["program", "-abc", "-d", "foo", "-e", "bar"];
/// # let mut args: Vec<String> = 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<char, bool>,
args: Vec<Vec<char>>,
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<char> = 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<Opt>;
/// 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<String> = 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<String> = 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<String> = 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<String> = 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<Result<Opt>> {
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))))
}
}
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*
* 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<T> = result::Result<T, Error>;

View File

@ -1,228 +0,0 @@
/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*
* 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<Opt> = $expect;
let args: Vec<String> = vec![$($arg),+]
.into_iter()
.map(String::from)
.collect();
let next: Option<String> = $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<String> = 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<String> = 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<Opt> = $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<String> = 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
}
}

View File

@ -52,7 +52,7 @@ int main(int argc, char *argv[]){
if(optind + 2 /* ref cmp */ > argc){
usage: fprintf(stderr,
"Usage: %s (-eghl) [integer] [integer...]\n",
"Usage: %s [-egl] integer integer...\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;
}

202
src/libgetopt.rs Normal file
View File

@ -0,0 +1,202 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::ffi::{ 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<String>, /* option argument */
ind: *mut i32, /* option index */
opt: Result<String, OptError>, /* option option */
}
impl Opt {
pub fn arg(&self) -> Option<String> { 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 Im stubborn */
pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } }
}
/* function signature */
pub trait GetOpt {
fn getopt(&self, optstring: &str) -> Option<Opt>;
}
impl GetOpt for Vec<String> {
fn getopt(&self, optstring: &str) -> Option<Opt> {
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 <colon> (':') shall be returned if getopt() detects a
* missing argument and the first character of optstring was a
* <colon> (':').
*
* A <question-mark> ('?') 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 <colon> (':').
*
* 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<String>;
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 didnt need to cast this before; I rewrote the
* pointer logic and now I do
*
* I dont 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<String> = ["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);
}
}
}

View File

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

View File

@ -39,7 +39,7 @@ int main(int argc, char *argv[]){
}
if(argc > optind){
usage: fprintf(stderr, "Usage: %s (-eht)\n", argv[0]);
usage: fprintf(stderr, "Usage: %s [-et]\n", argv[0]);
return EX_USAGE;
}

View File

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

View File

@ -56,7 +56,7 @@ int main(int argc, char *argv[]){
goto pass;
}
fprintf(stderr, "Usage: %s [type] [string...]\n",
fprintf(stderr, "Usage: %s type string...\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;

View File

@ -8,7 +8,7 @@ int main(int argc, char *argv[]){
int i;
if(argc < 3){
fprintf(stderr, "Usage: %s [string] [string...]\n",
fprintf(stderr, "Usage: %s string string...\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;
}

View File

@ -24,7 +24,7 @@ use std::{
};
extern crate getopt;
use getopt::{ Opt, Parser };
use getopt::GetOpt;
extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
@ -35,7 +35,7 @@ fn oserr(s: &str, e: Error) -> ExitCode {
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} (-f) (-w [wordsize])", s);
eprintln!("Usage: {} [-f] [-w word_size]", s);
ExitCode::from(EX_USAGE as u8)
}
@ -45,24 +45,21 @@ fn main() -> ExitCode {
let mut input = stdin();
let mut output = stdout().lock();
let mut opts = Parser::new(&argv, "fw:");
let mut force = false;
let mut wordsize: usize = 2;
loop {
match opts.next() {
None => break,
Some(opt) =>
match opt {
Ok(Opt('f', None)) => force = true,
Ok(Opt('w', Some(arg))) => {
match arg.parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); },
}
},
_ => { return usage(&argv[0]); }
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::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); },
}
}
},
_ => { return usage(&argv[0]); }
}
}

View File

@ -1,10 +0,0 @@
extern crate strerror;
use strerror::raw_message;
fn main() {
stdout.write_all(b"meow\n").unwrap_or_else(|e| {
eprintln!("{}", raw_message(e));
std::process::exit(1);
});
}