Compare commits

...

480 Commits
peek ... main

Author SHA1 Message Date
8eece4cf84
Makefile: removes deprecated variable 2025-03-28 19:38:51 -06:00
0e9127d417
tests: removed POSIX testing (fixes #163) 2025-02-24 23:28:52 -07:00
e6f747d635
Merge branch 'npc-broken-tests' 2025-02-24 23:19:47 -07:00
9b699d7298
Makefile, tests/bonsai/scrut.mk: cleanup 2025-02-24 23:17:10 -07:00
941f931f8b
Merge branch libfileis 2025-02-24 23:13:13 -07:00
f0eac562b3
tests: bonsai/npc.mk: fixed copyright header 2025-02-24 23:08:28 -07:00
138044e52f
Merge branch 'optimizations' 2025-02-24 23:04:10 -07:00
bd8bc0094d
rpn(1): fixes erroneous OpenBSD-gated import 2025-02-24 23:03:31 -07:00
99a99cdcb5
Merge branch 'style-c' 2025-02-24 22:59:50 -07:00
008c0624f6
tests: bonsai/npc.mk: removed test that is broken with GNU and BusyBox 2025-02-24 22:57:42 -07:00
982c67df13
intcmp(1), swab(1): consistent type annotations 2024-10-01 22:08:56 -06:00
07a2f1efc2
Merge branch 'man-fixes' 2024-09-11 15:42:58 -06:00
c4cd2563f9
scrut(1): fixes failing write test 2024-09-11 03:26:00 -06:00
8bdb72ece8
false(1): fixes pledge(2) invocation 2024-09-11 03:19:56 -06:00
ba73f50527
true(1): removes NULL from commented imports 2024-09-11 03:19:23 -06:00
579bb98874
true(1): fixes pledge(2) invocation 2024-09-11 03:18:01 -06:00
8f1e570b50
strcmp(1): fixes pledge(2) invocation 2024-09-11 03:15:58 -06:00
a43daf2cf2
str(1): fixes pledge(2) invocation 2024-09-11 03:15:13 -06:00
f25a499ca0
scrut(1): fixes pledge(2) invocation 2024-09-11 03:14:36 -06:00
e0b5467fb6
npc(1): fixes pledge invocation 2024-09-11 03:13:38 -06:00
7498b283ce
mm(1): updates to use new unveil api 2024-09-11 02:32:32 -06:00
0b3ed37c38
libopenbsd.rs(3): makes using statically-allocated arrays possible 2024-09-11 02:32:03 -06:00
800a097903
swab(1): uses default for promises 2024-09-10 17:14:02 -06:00
1ccdc65d30
rpn(1): uses default for promises 2024-09-10 17:13:20 -06:00
df0a236f81
mm(1): uses default for Promises 2024-09-10 17:12:14 -06:00
e385d873ec
intcmp(1): uses default value for Promises 2024-09-10 17:11:22 -06:00
73a75a32df
hru(1): uses default Promises value for child processes 2024-09-10 17:10:26 -06:00
b8e5901d97
libopenbsd.rs(3): adds default value for Promises 2024-09-10 17:09:46 -06:00
f4bd4de2e4
fileis(1): various changes to make the code more efficient and idiomatic 2024-09-10 02:42:55 -06:00
f66fdef9c3
STYLE: avoid unbounded loops 2024-09-10 02:05:01 -06:00
b56a66f980
STYLE: fixes default case in example 2024-09-10 01:59:59 -06:00
f6559b464a
STYLE: fix minor issues 2024-09-07 12:51:18 -06:00
accec33301
Makefile: fixes abuse of relative paths 2024-09-05 17:12:39 -06:00
444e682f02
Merge branch 'rdme' 2024-09-05 17:02:31 -06:00
790b12fb40
STYLE: changes numbering scheme to transcend headers, fixes minor errors 2024-09-05 15:55:32 -06:00
fde682a10e
Makefile: fixes manpage installation location 2024-09-05 12:57:55 -06:00
4cb5ea78d7
STYLE: improves clarity, focus, distribution of items, adds more gripes, includes usage text style 2024-09-05 12:44:14 -06:00
62ce288524
rpn(1): makes rounding more efficient 2024-09-04 22:12:44 -06:00
DTB
87c9e29932
tests: bonsai/peek.mk 2024-09-01 08:51:33 -06:00
DTB
1037e32e6e
peek(1): fix erroring on stdin/stdout redirection 2024-09-01 08:50:13 -06:00
DTB
5c70852890
peek.1: replace parens with square brackets 2024-09-01 08:50:12 -06:00
DTB
8ec18ccf75
peek(1): use stdbool.h 2024-09-01 08:50:12 -06:00
DTB
3bfdc62035
peek(1): better error on piping 2024-09-01 08:50:12 -06:00
DTB
7d174c1f7c
peek(1): warn on pledge(2) or unveil(2) errors 2024-09-01 08:50:12 -06:00
DTB
3f41110617
peek(1): pledge(2) and unveil(2) on OpenBSD 2024-09-01 08:50:11 -06:00
DTB
7a69e1d516
peek.1: correct references to use the 1p section, add reference to stty(1p) 2024-09-01 08:50:11 -06:00
DTB
64e943f64a
peek(1): fix ioerr typos 2024-09-01 08:50:11 -06:00
DTB
16dcd4da37
peek(1): more style fixes 2024-09-01 08:50:11 -06:00
DTB
d892fa1cac
peek(1): update style 2024-09-01 08:50:11 -06:00
DTB
3862a95151
peek.1: s/inputted/input/ 2024-09-01 08:48:26 -06:00
DTB
264ae2e82c
peek.1: fix version string 2024-09-01 08:48:26 -06:00
DTB
1634761593
peek.1: fix wording on -i 2024-09-01 08:48:26 -06:00
DTB
0b40360e7e
Makefile: all: add peek 2024-09-01 08:48:26 -06:00
590d98d7f7
peek.1: updates to be more consistent with current documentation 2024-09-01 08:48:26 -06:00
DTB
07aa9a9abc
peek(1): remove unused variables, clean up some typos 2024-09-01 08:48:25 -06:00
DTB
fce49bed85
peek(1): correct grammar 2024-09-01 08:46:56 -06:00
DTB
62b38a6765
peek(1): strip down to bare essentials, rewrite man page to match 2024-09-01 08:46:56 -06:00
DTB
23521bade5
peek(1): remove -p 2024-09-01 08:46:56 -06:00
DTB
3b774cab27
Import peek(1) from trinity/src 2024-09-01 08:46:52 -06:00
e889159619
CONTRIBUTING: made better 2024-08-30 21:22:53 -06:00
731e62ee7e
rpn(1): made evaluation function more readable 2024-08-28 18:24:49 -06:00
f50bfeea92
fop(1): fixes panic on no arguments 2024-08-28 18:05:56 -06:00
8d00fa0afd
fop(1): minor formatting change 2024-08-28 00:46:32 -06:00
9f520df82b
strcmp(1): adds null unveil 2024-08-28 00:31:02 -06:00
41982c2f73
str(1): adds null unveil 2024-08-28 00:30:45 -06:00
6166a3ca36
npc(1): adds null unveil 2024-08-28 00:26:26 -06:00
efedcd3ae4
true(1), false(1): adds null unveils 2024-08-28 00:24:35 -06:00
8fd5bdf5a6
swab(1): adds null unveil 2024-08-28 00:15:15 -06:00
b946469da5
rpn(1): adds null unveil 2024-08-28 00:13:21 -06:00
06384c72fb
intcmp(1): adds null unveil 2024-08-28 00:09:40 -06:00
9aeadd8f8f
hru(1): adds null unveil 2024-08-27 23:57:12 -06:00
5b666d6858
fop(1): adds null unveil 2024-08-27 23:54:39 -06:00
b875aa1058
rpn(1): more improvements 2024-08-24 19:00:01 -06:00
cc53dab035
rpn(1): handles errors when writing to stdout 2024-08-24 18:18:27 -06:00
232629f4d3
hru(1): makes more efficient 2024-08-24 17:55:00 -06:00
ff4ff825bd
swab(1): changes disparate error handling functions to one function 2024-08-24 17:26:26 -06:00
5eb71e90a3
tests/bonsai: rpn.mk: tests the standard input 2024-08-24 17:22:57 -06:00
a0138be79e
rpn(1): refactor to make code more efficient, readable, and maintainable 2024-08-24 17:22:02 -06:00
150fa22f35
fop(1): changes casts to calls to .into() 2024-08-24 15:57:55 -06:00
f104598164
mm(1): updates error-handling functions and uncovers issue with error reporting 2024-08-24 15:53:58 -06:00
722679247a
hru(1): uses new sysexits 2024-08-23 14:31:02 -06:00
4db4160019
swab(1): uses new sysexits 2024-08-23 14:23:11 -06:00
928dce0234
fop(1): uses new sysexits 2024-08-23 14:22:11 -06:00
1fffd3df52
Makefile: sets bindgen to output exit codes as u8 2024-08-23 14:21:43 -06:00
a14b8fb9d7
fop(1): adds functions for error handling 2024-08-23 13:35:30 -06:00
dd39aeff02
mm(1): adds ioerr(), usage(), and oser() functions 2024-08-22 00:22:05 -06:00
fbacfecce8
Merge branch 'linux-fix' (closes #158) 2024-08-21 22:43:29 -06:00
DTB
579ff65b67
tests: bonsai/npc.mk: drop redundant tab removal 2024-08-21 21:59:22 -06:00
DTB
334433536b
tests: bonsai/npc.mk: fix harrowing ordeal of a Linux error 2024-08-21 21:57:10 -06:00
e9058803d3
mm(1): fixes to pledge(2) now; tests: bonsai/mm.mk: adds test for regression 2024-08-17 14:58:56 -06:00
71d4d6ba05
tests: bonsai/dj.mk, bonsai/rpn.mk: fixes testing on linux 2024-08-17 01:57:16 -06:00
821f5d09e9
fop(1), hru(1), intcmp(1), mm(1), rpn(1), swab(1): fixes conditional compilation 2024-08-17 01:51:25 -06:00
a4a556a5b6
mm(1): fixes extra file arguments not being unveil(2)ed 2024-08-16 18:43:51 -06:00
DTB
e2c03842a3
intcmp(1): remove extra newline 2024-08-13 12:13:47 -06:00
DTB
0fd66bff38
false(1), true(1): complete main prototype 2024-08-13 12:04:42 -06:00
DTB
0c4923016e
Makefile: replace include/None.mk with /dev/null 2024-08-13 11:56:09 -06:00
e0c985f7ff
libopenbsd.rs(3): uses c_char instead of i8 for portability 2024-08-14 00:32:28 -06:00
d2f8fb8106
README: improvements, community links 2024-08-12 17:53:43 -06:00
DTB
4c81516742
strcmp(1): further error-proofing 2024-08-10 22:30:08 -06:00
DTB
98c4d94f6d
scrut(1): further error-proofing 2024-08-10 22:24:32 -06:00
DTB
da190f713c
npc(1): tweak OpenBSD functions 2024-08-10 22:18:15 -06:00
DTB
10b7f7706b
dj(1): tweak OpenBSD functions 2024-08-10 22:16:06 -06:00
DTB
b1a4a1a2b9
dj(1): use entirely-stdio error messages for OpenBSD functions 2024-08-10 22:03:59 -06:00
a693ced9d9
strcmp(1): fixes typo 2024-08-10 19:18:52 -06:00
1003c82d23
swab(1): uses pledge(2) 2024-08-10 19:16:26 -06:00
baa75a2619
strcmp(1): implements use of pledge(2) 2024-08-10 19:09:50 -06:00
d6d9c2088e
npc(1): uses perror(3) 2024-08-10 19:09:26 -06:00
ea2efdf5b9
str(1): perror(2) -> perror(3) 2024-08-10 19:08:15 -06:00
6c558654f3
str(1): adds reference to perror(3) in include 2024-08-10 19:01:44 -06:00
8699d04ccc
str(1): updates to use pledge(2) 2024-08-10 19:00:32 -06:00
18dfd20937
scrut(1): fixes buffer overflow on i386 systems 2024-08-10 18:57:36 -06:00
42010596de
scrut(1): adds support for pledge(2) and unveil(2) 2024-08-10 17:17:42 -06:00
0ddfa6e474
tests: bonsai/rpn.mk: fixes rpn_test target deps 2024-08-10 15:34:41 -06:00
326c8f77d1
rpn(1): adds support for pledge(2) 2024-08-10 15:34:14 -06:00
72ef8d00bc
npc(1): adds pledge(2) support 2024-08-10 15:22:07 -06:00
851f729ebd
tests: bonsai/mm.mk, bonsai/intcmp.mk: updates for regression testing 2024-08-10 14:24:33 -06:00
e253cdf79c
intcmp(1): fixes intcmp without options falling through to integer parsing 2024-08-10 14:23:53 -06:00
d89707a47c
mm(1): fixes mm without arguments not working 2024-08-10 14:20:38 -06:00
2a75b8f820
mm(1): removes leftover debug print 2024-08-10 14:09:33 -06:00
bda7c074b0
hru(1): adds support for pledge(2) 2024-08-10 13:34:44 -06:00
2f805cc942
mm(1): adds support for pledge(2) and unveil(2) 2024-08-10 13:29:27 -06:00
70bec49127
libopenbsd.rs(3): fixes API for unveil(2) 2024-08-10 13:25:56 -06:00
eae0b0352b
dj(1): fixes small rebase mistake 2024-08-10 13:07:46 -06:00
25eb08eb84
Makefile, include: makes conditional compilation more robust 2024-08-10 13:04:41 -06:00
6c882f54cb
fop(1): adds pledge support 2024-08-10 12:51:25 -06:00
cf96a13419
true(1), false(1): adds pledge(2) and unveil(2) support 2024-08-10 12:50:51 -06:00
1f59a9806e
dj(1): adds pledge(2) and unveil(2) support 2024-08-10 12:50:15 -06:00
c7c71c725b
libopenbsd(3): adds pledge(2) and unveil(2) support for Rust; Makefile, include: adds conditional compilation 2024-08-10 12:49:35 -06:00
b76ff8fd90
tests, Makefile: cleaning up 2024-08-09 23:50:31 -06:00
66f809162b
Merge branch 'testing' 2024-08-09 23:28:21 -06:00
7278a8fc41
Makefile: fixes testing import 2024-08-09 18:00:25 -06:00
DTB
8646d5c4ee
STYLE: make rules more granular and consistent, add examples 2024-08-08 02:31:54 -06:00
8e5090d13d
tests: README: updates README 2024-08-07 22:03:32 -06:00
eb821715f7
tests: bonsai/hru.mk: fixes hru.mk more 2024-08-07 20:45:26 -06:00
0bc0ffa0a5
tests: bonsai/hru.mk: updates to not use old variables 2024-08-07 20:38:03 -06:00
DTB
a7f16b5a7e
swab.1: Add note to keep example in tests 2024-08-07 08:35:24 -06:00
DTB
a94884cc2a
tests: bonsai/swab.mk: add example from tha man page 2024-08-07 08:34:29 -06:00
DTB
9412f95cb1
tests: bonsai/str.mk: add str_isalpha test 2024-08-05 21:16:18 -06:00
DTB
74d6ec16ac
Merge branch 'testing' of git.tebibyte.media:bonsai/harakit into testing 2024-08-04 13:45:10 -06:00
3880abaa4f
tests: bonsai/rpn.mk: added rpn(1) test 2024-08-05 13:47:12 -06:00
DTB
e93d218b87
tests/bonsai: str.mk: -h test 2024-08-04 09:14:19 -06:00
DTB
cd5983b10b
tests/bonsai: intcmp.mk: add a comment 2024-08-04 09:05:20 -06:00
DTB
acdbecf178
tests: bonsai/scrut.mk: add tests 2024-08-03 21:36:53 -06:00
DTB
0f2d357476
tests/bonsai: dj.mk: tee diagnostics to stderr instead of tty 2024-08-03 07:33:46 -06:00
DTB
588680406a
tests: bonsai/npc.mk: full ASCII test coverage 2024-08-03 07:29:04 -06:00
ee7b7e89b2
tests: bonsai/fop.mk: fixes record separators 2024-08-02 19:37:36 -06:00
DTB
91de98cea3
tests: bonsai/intcmp.mk: make tests more relevant to failure cases 2024-08-02 18:28:24 -06:00
DTB
bd09d16949
tests: bonsai/dj.mk: comment more 2024-08-02 18:22:45 -06:00
ce8a0a5be3
tests: converted to Makefiles 2024-08-02 17:29:30 -06:00
0b0bd9bd76
Makefile: fixes test and removes references to the RUSTLIBS variable 2024-08-01 13:12:54 -06:00
18aac06113
Makefile: changes test usage 2024-08-01 00:06:39 -06:00
4aeba9d13f
Makefile: uses dirname(1) to get normalized prefix, adds libraries to RUSTFLAGS, fixes typo 2024-07-29 23:04:08 -06:00
6a5739ea9d
Merge branch 'dj-formatting' 2024-07-29 22:40:18 -06:00
DTB
6bbccb3776
fop(1): fix minor optind bug, add some comments 2024-07-29 21:14:45 -06:00
DTB
fd13a7f189
swab(1): fix imports to be consistent 2024-07-29 14:43:21 -06:00
6c2b7b68fd
dj(1): more better comments 2024-07-29 14:38:33 -06:00
DTB
8d23c7fbac
dj(1): better ASV formatting and comments 2024-07-29 13:43:52 -06:00
DTB
7e347bebdf
mm(1): interpret retvals 2024-07-29 11:30:41 -06:00
DTB
a7b27f0d6a
strcmp(1): interpret all retvals 2024-07-29 11:14:48 -06:00
DTB
c554b96722
swab(1): fix type errors 2024-07-29 11:13:58 -06:00
DTB
7b930363bf
npc(1): fix syntax errors 2024-07-29 11:12:02 -06:00
DTB
5545846c92
dj(1): fix fprintio signature 2024-07-29 11:10:17 -06:00
DTB
162c6411b3
mm(1): remove terminate macro 2024-07-29 11:08:30 -06:00
DTB
2e172d93e8
dj(1): interpret all retvals 2024-07-29 11:04:00 -06:00
DTB
549fa98bdb
npc(1): interpret all retvals 2024-07-29 10:53:58 -06:00
361b34c50f
npc(1): improves program_name resolution 2024-07-29 02:35:59 -06:00
DTB
8bb57bf2e4
Merge branch 'dj-formatting' of git.tebibyte.media:bonsai/harakit into dj-formatting 2024-07-28 21:26:08 -06:00
DTB
ca6148e11f
intcmp(1): tweak comment 2024-07-28 21:06:53 -06:00
44d461fb16
scrut(1): return program_name ternary to former position 2024-07-28 18:33:49 -06:00
338a3e7155
intcmp(1): move program_name ternary 2024-07-28 18:31:01 -06:00
6b9d13b8a0
fop(1): better opt matching 2024-07-28 18:27:22 -06:00
68e31058a8
intcmp(1): move program_name ternary 2024-07-28 17:49:39 -06:00
d2ae35eac9
fop(1): fixes glaring issue with newline handling 2024-07-28 01:15:08 -06:00
0f121cbac7
fop(1): more descriptive command arguments variable name 2024-07-28 00:40:16 -06:00
09193a14d5
fop(1): fixes unhandled i/o error 2024-07-28 00:35:55 -06:00
d5d13b84a7
STYLE: repition & Kernighan quote 2024-07-28 00:34:42 -06:00
aed64840ea
STYLE: fixes some concerns 2024-07-28 00:12:34 -06:00
d45fa19d5c
dj(1): fixes extraneous ternary for program_name 2024-07-27 23:59:32 -06:00
DTB
970b25dee2
str(1): fix brackets 2024-07-27 18:35:02 -06:00
DTB
5d48114083
dj(1), intcmp(1), mm(1), npc(1), scrut(1), str(1): make usage function consistent 2024-07-27 18:32:04 -06:00
DTB
429d064209
mm(1): fix argv use 2024-07-27 18:22:43 -06:00
DTB
c5c0e543e4
mm(1): remove useless extern errno 2024-07-27 18:21:17 -06:00
DTB
cba8394d95
str(1): fix argv parsing and add comments 2024-07-27 18:18:29 -06:00
DTB
199d48d85b
swab(1): comment code 2024-07-27 17:59:47 -06:00
c0a5e11eef
mm(1): revert changes to program_name 2024-07-25 21:28:42 -06:00
0641a487d7
intcmp(1): fixes pointless condition; formatting 2024-07-25 21:17:30 -06:00
2fe3aa894c
dj(1), intcmp(1), mm(1), npc(1), scrut(1), strcmp(1): changes to use and modify program_name 2024-07-24 19:37:07 -06:00
0282b60e65
dj(1), mm(1), npc(1), scrut(1), str(1): consistent argv[0] handling 2024-07-20 07:18:59 -06:00
DTB
f96ed9c1f3
scrut(1): fix syntax error 2024-07-19 19:34:37 -06:00
DTB
19eee6b4e5
scrut(1): replace do/while loop 2024-07-19 19:31:34 -06:00
DTB
9086bf0d08
dj(1): remove do/while statement in read loop 2024-07-19 19:18:04 -06:00
DTB
c8b4f7a8b3
str(1): edit out goto 2024-07-19 19:03:23 -06:00
DTB
71f4a411b6
dj(1): replace do/while in write loop 2024-07-19 18:40:24 -06:00
DTB
a01cea572d
dj(1): replace do/while on hard seeking 2024-07-19 18:26:59 -06:00
f7ebe7cf57
STYLE: added 10 rules reference 2024-07-19 17:31:51 -06:00
22bb26b9cb
STYLE: added introduction statement 2024-07-19 17:04:49 -06:00
3ba6682ab3
STYLE: extern and use statements rules 2024-07-19 16:41:02 -06:00
DTB
6d7173e438
libfileis(3): finish removal 2024-07-18 20:50:12 -06:00
DTB
958f08bd9e
fileis.1: rename from scrut.1 2024-07-18 20:45:14 -06:00
DTB
02b5edae05
fileis(1): feature parity with C scrut(1) 2024-07-18 20:43:39 -06:00
DTB
0819eeb75d
fileis(1): scrap libfileis(3), work on rewriting scrut(1) in Rust 2024-07-18 19:11:22 -06:00
DTB
cd875df547
README: attempt to clarify intent 2024-07-18 09:32:58 -06:00
DTB
e90d25e30f
Merge branch 'strcmp' 2024-07-15 19:52:26 -06:00
DTB
71f372d2c2
Merge branch 'intcmp' 2024-07-15 15:21:31 -06:00
DTB
27ff64dffa
intcmp(1): add comments 2024-07-15 14:51:54 -06:00
DTB
becb3bac4e
strcmp(1): code clarification 2024-07-15 14:40:26 -06:00
DTB
0f12dcc552
scrut(1): fix ugly opt parsing 2024-07-15 14:28:00 -06:00
DTB
b9c4b49603
scrut(1): use libfileis 2024-07-15 14:13:53 -06:00
DTB
2c4349872c
scrut(1): banish gotos 2024-07-15 13:55:01 -06:00
DTB
59fa3ed3d2
scrut(1): get rid of -h 2024-07-15 13:47:55 -06:00
DTB
71655a8559
libfileis(3): a library for scrutinizing files 2024-07-15 13:47:04 -06:00
9f2447ce94
STYLE: code block indentation edits and example 2024-07-15 13:38:26 -06:00
fbdf4f9c45
intcmp(1): switch formatting 2024-07-15 13:38:08 -06:00
806ddac8da
dj(1): whitespace formatting 2024-07-15 13:31:38 -06:00
a0ed14a089
STYLE: example for trailing comma & add includes guideline 2024-07-15 13:29:12 -06:00
dc2a4a39ba
swab(1): note what sysexits are being used 2024-07-15 13:17:00 -06:00
7ff14214c3
npc(1): move bit comment to be next to hex 2024-07-15 13:16:41 -06:00
a3ceb845e3
dj(1): revert some formatting changes 2024-07-15 13:16:05 -06:00
c2e6744e2b
dj(1): revert changes to function return type formatting 2024-07-15 13:09:09 -06:00
71e98dbde7
dj.1: fixes more ambiguity 2024-07-15 13:06:34 -06:00
e9496cb4a5
dj.1: fixes ambiguity and false information 2024-07-15 13:04:23 -06:00
789046f694
STYLE: removes do while constraint & reword indentation rule 2024-07-15 13:03:53 -06:00
e7a6632b41
mm(1): improves comments 2024-07-15 12:52:02 -06:00
48dbea0228
mm(1): imports full enum 2024-07-15 12:52:02 -06:00
64f3f73d96
mm(1): formatting 2024-07-15 12:52:02 -06:00
eda5385058
mm.1: small correction 2024-07-15 12:52:02 -06:00
7a0ad78000
mm.1: reflects new changes 2024-07-15 12:52:02 -06:00
DTB
8dc763f05e
mm(1): treats trailing arguments as optargs for the last opt 2024-07-15 12:52:01 -06:00
20692d581a
mm(1): makes -e block inferring stdout as an output, mm.1: reflects changes to -e 2024-07-15 12:52:01 -06:00
8d693b6664
mm(1): removes debug print 2024-07-15 12:52:01 -06:00
37804aab6b
mm(1): fixes comment about flags -a & -t 2024-07-15 12:52:01 -06:00
3b76254599
mm(1): fixes creating files 2024-07-15 12:52:01 -06:00
e972ff468a
mm(1): added -t for disabling truncation; mm.1: updated docs 2024-07-15 12:52:01 -06:00
1b3b03cae0
mm(1): rewritten in Rust 2024-07-15 12:51:48 -06:00
DTB
efb3ce626d
strcmp(1): fix program_name type 2024-07-15 04:29:43 -06:00
DTB
16f23e11c0
strcmp.1: update docs to match utility 2024-07-15 04:26:57 -06:00
DTB
d87c278be5
strcmp(1): re-style, tweak exits 2024-07-15 04:21:50 -06:00
DTB
5caefbb465
strcmp(1): note used sysexit 2024-07-15 03:45:36 -06:00
DTB
8d743dab7a
strcmp(1): add copyright header
I could trace strcmp(1) as far back as
<293436c5ad/src/streq.c>
in my repo.
2024-07-15 03:43:25 -06:00
DTB
2f2270322a
intcmp(1): rewrite in rust 2024-07-15 03:14:57 -06:00
DTB
699893af89
intcmp(1): initial rust impl 2024-07-15 03:09:00 -06:00
DTB
9addfc9284
Merge branch 'dj' 2024-07-15 02:36:07 -06:00
ab003f7d4a
strcmp(1): commenting 2024-07-14 03:25:52 -06:00
3c243e4a09
str(1): formatting 2024-07-14 03:20:15 -06:00
49031102f2
npc(1): commenting 2024-07-14 03:17:21 -06:00
fd1ed79329
dj(1): return top-of-scope variable 2024-07-14 02:43:01 -06:00
fe0c631d42
dj.1: reverts change to hex literals 2024-07-14 02:41:59 -06:00
aa074ad9b6
dj.1: fixes ambiguity in block size options 2024-07-14 02:37:37 -06:00
b22ded9e98
STYLE: adds do while rule 2024-07-14 02:15:07 -06:00
579bf3b622
STYLE: initial commit 2024-07-14 02:10:18 -06:00
b0602388e7
rpn(1): better comments 2024-07-13 18:29:27 -06:00
a9b388fe4b
hru(1): adds more descriptive comments 2024-07-13 18:18:32 -06:00
e4e823a309
fop(1): adds more comments 2024-07-13 18:03:49 -06:00
c7c6ca2c60
mm(1): formatting 2024-07-13 17:23:31 -06:00
26b0c93f4d
strcmp(1): returns -1, specifies sysexits imports 2024-07-13 17:04:38 -06:00
35a20dca79
npc(1): specifies sysexits imports, formatting 2024-07-13 17:03:41 -06:00
8421f8be87
mm(1): specifies sysexits imports 2024-07-13 17:02:23 -06:00
0c530dffbf
intcmp(1): formatting, fixes argv[0] ternary and usage function 2024-07-13 17:01:53 -06:00
d9dd4e6057
intcmp(1): formatting, lists sysexits imports, allows no args 2024-07-13 16:57:52 -06:00
1dfad87e87
dj(1): lists sysexits imports, fixes negation 2024-07-13 16:56:56 -06:00
99f2b2963a
dj(1): more formatting 2024-07-12 16:39:11 -06:00
666c621a02
strcmp(1): more formatting 2024-07-12 16:38:50 -06:00
34cd715e37
mm(1): removes unnecessary macros 2024-07-12 16:22:56 -06:00
958bfa52ed
scrut(1): more formatting 2024-07-12 16:18:48 -06:00
59de0262bd
strcmp(1): adds copyright header, formatting, removes unused #include 2024-07-12 16:15:41 -06:00
5d2872d050
scrut(1): formatting, removes gotos 2024-07-12 16:04:07 -06:00
6e1e3db6c8
npc(1): forgot to update copyright 2024-07-12 15:55:36 -06:00
9cfc48c960
intcmp(1): formatting, removed gotos 2024-07-12 15:54:30 -06:00
acc3cf3e90
swab(1): formatting, remove gotos 2024-07-12 15:43:00 -06:00
1ad4114882
dj(1): remove unnecessary macros 2024-07-12 15:42:24 -06:00
db0dd02d55
dj(1): more formatting 2024-07-12 15:32:30 -06:00
6cf7fd9794
dj(1): reformatting 2024-07-12 15:23:57 -06:00
DTB
8f8de5de2b
dj(1): fix io[0].bufuse underflow 2024-07-08 22:53:44 -06:00
DTB
0df2c9f566
dj(1): fix infiniskipping 2024-07-08 22:48:16 -06:00
DTB
94873a2ddc
tests: bonsai/dj.sh: iron out some existing tests 2024-07-08 22:25:18 -06:00
DTB
aa819cabc2
tests: bonsai/npc.sh: initial arg parsing tests 2024-07-08 22:24:18 -06:00
DTB
7939985c98
Merge branch 'main' into testing 2024-07-08 14:53:47 -06:00
DTB
b7bc1f16ad
swab(1): use the getopt error message 2024-07-08 14:34:42 -06:00
ca6865688a
swab(1): updates getopt usage 2024-07-08 13:23:23 -06:00
DTB
1fd768057c
swab(1): don't accept positional arguments 2024-07-08 11:45:01 -06:00
DTB
35d54d84b0
swab(1): don't use the getopt error message 2024-07-08 11:31:35 -06:00
DTB
a141b95293
swab(1): remove -f 2024-07-08 11:30:21 -06:00
DTB
bf06e91be5
Merge branch 'main' into dj 2024-07-08 11:20:52 -06:00
DTB
5d5a6d2172
dj(1): fix retvals 2024-07-07 21:13:44 -06:00
DTB
691e94c0c1
dj(1): error reporting more of the time 2024-07-07 20:33:54 -06:00
45329ccb8c
tests/README: initial commit; tests/posix_env, tests/test.sh: updated to match README 2024-07-07 19:19:38 -06:00
cf744efc1b
swab(1): fixes not using strerror(3) 2024-07-07 18:21:48 -06:00
DTB
bab3cdd90e
dj(1): Io_write: don't add to bufuse 2024-07-07 18:14:48 -06:00
DTB
abfe7046e7
dj(1): fix some type issues 2024-07-05 08:02:09 -06:00
DTB
6ed7089b25
dj(1): statistics now track hard seeks 2024-07-04 21:32:05 -06:00
DTB
571796fe0d
dj.1: update man page to match behavior 2024-07-04 21:05:15 -06:00
DTB
9e8b82c4bb
dj(1): fix inaccurate statistics after Io_read and Io_write 2024-07-04 20:47:30 -06:00
DTB
906eb92f5a
dj(1): (broken) move hard skipping to the main loop 2024-07-04 20:27:31 -06:00
DTB
9f420131ee
dj(1): more work adapting hard skipping to the main loop 2024-07-04 20:16:54 -06:00
DTB
1fab60d779
dj(1): no more pointer arithmetic 2024-07-04 20:05:18 -06:00
DTB
fe175cab19
dj(1): add a variable for skipping in the main loop 2024-07-04 20:00:40 -06:00
DTB
8c33f0116c
dj(1): move open(2) flags, remove unnecessary comments 2024-07-04 19:45:53 -06:00
DTB
f8c0e0570c
dj(1): make Io_write handle prec and rec 2024-07-04 19:36:32 -06:00
DTB
fc0d9e374b
dj(1): make printio fprintio 2024-07-04 19:23:09 -06:00
DTB
f49a2d2eb8
dj(1): move prec and rec adjustment into Io_read 2024-07-04 19:21:40 -06:00
DTB
4004a4a006
dj(1): use the retvals of Io_read and Io_write 2024-07-04 18:41:20 -06:00
DTB
cc64561388
dj(1): only include sysexits if they aren't defined 2024-07-03 20:52:41 -06:00
DTB
5b1d4fef88
dj(1): remove Io_fdopen 2024-07-03 19:22:34 -06:00
DTB
2b593559af
dj(1): remove Io_bufrpad 2024-07-03 19:06:59 -06:00
DTB
b74160fa4e
dj(1): remove Io_bufxfer 2024-07-03 19:04:01 -06:00
DTB
3e1735f778
dj(1): clean up some stray ends 2024-07-03 18:44:42 -06:00
DTB
adda0d9580
dj.1: elaborate on skip/seek behavior, provide another example 2024-07-03 18:30:54 -06:00
DTB
2167f35f58
dj(1): fix segfault when bses are mismatched 2024-07-03 17:59:21 -06:00
DTB
f4b97be1f1
dj(1): iron out Io_bufxapp 2024-07-03 17:50:04 -06:00
DTB
7fe122ac3b
dj.1: clarify skip/seek behavior with regards to statistics output 2024-07-03 16:13:20 -06:00
DTB
944feef434
dj(1): Refactor out Io_fdseek entirely 2024-07-03 16:07:02 -06:00
DTB
66ca4b9a12
dj(1): remove unnecessary stderr checks 2024-07-03 15:47:48 -06:00
DTB
3897f44cf8
dj(1): prefix getopt optstring with : 2024-07-03 15:46:11 -06:00
DTB
6548a448c7
dj(1): fix potential skip/seek bug in non-std io 2024-07-03 15:44:42 -06:00
DTB
aff658d611
dj(1): remove debugging vestige, reflow output into printio 2024-07-03 14:50:50 -06:00
DTB
76252305f9
dj(1): remove Io_bufalloc 2024-07-03 14:46:56 -06:00
DTB
1cf67af281
dj(1): add a ton of assertions, fix if statement, fix io[i] mixups 2024-07-03 14:22:23 -06:00
DTB
064abb82a6
dj(1): fix option parsing regression 2024-07-03 13:50:24 -06:00
d1eefcb37e
Merge branch 'makefile-improved' 2024-06-30 22:17:39 -06:00
984c1c1f9a
Makefile: fixes portability issue 2024-06-30 21:21:02 -06:00
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
881df1bb18
Merge branch 'usage-text' 2024-06-29 17:20:19 -06:00
e38ea5b35d
Makefile: fixes dist 2024-06-29 08:36:12 -06:00
261c98ad14
Makefile: docs no longer builds every invocation, normalize PREFIX for setting man dir 2024-06-29 08:28:49 -06:00
014485d3c5
Merge branch 'main' into testing 2024-06-29 08:05:13 -06:00
40984453e3
Makefile: better leverage targets for sysexits build 2024-06-29 08:01:33 -06:00
cb88ed9809
Merge branch 'getopt-bindings' 2024-06-29 07:51:01 -06:00
cf5136d247
Makefile, swab(1): fixes swab build 2024-06-29 07:49:47 -06:00
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
07a12ba81c
docs, src: fixing man page and usage text readability 2024-06-29 05:28:23 -06:00
e341c38cd6
docs, src: updates usage text for utilities 2024-06-29 05:18:20 -06:00
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
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
0fc9a6b533
Makefile, docs: programmatically generate version for docs (i got tired of doing it myself) 2024-06-23 23:47:29 -06:00
8b400d8a62
Makefile, docs: rename 2024-06-23 23:34:23 -06:00
6e4aeb7be7
fop(1): bring getopt(3) usage up-to-date 2024-06-23 01:00:48 -06:00
965d1bb76e
Merge branch 'main' into getopt-bindings 2024-06-23 00:57:20 -06:00
e1bf49c75a
getopt.rs(3): adds comments & documentation 2024-06-22 22:30:30 -06:00
8f990ba515
getopt.rs(3): adds testing 2024-06-22 22:18:13 -06:00
4b3333d8d3
fop(1): record separator worky now? 2024-06-21 03:26:42 -06:00
578d947561
Merge branch 'README-clarity' 2024-06-19 23:30:03 -06:00
2e91338101
Merge branch 'makefile-posix-2024' 2024-06-19 23:29:42 -06:00
72f57ba08b
Makefile: adds octal disclaimer 2024-06-19 23:29:22 -06:00
35f49a699f
fop(1): fixes record separator, again 2024-06-19 15:26:46 -06:00
e1ac40e7ee
docs: updates version number 2024-06-19 15:03:06 -06:00
125b4c8930
README: updated for clarity 2024-06-19 02:52:58 -06:00
f553cff096
Makefile: removes unneeded comment 2024-06-18 16:33:22 -06:00
d201f9228c
Makefile: updates to use new POSIX 2024 standard features! 2024-06-18 16:32:20 -06:00
6814111ad1
dj.1: fixes many clunky sentences 2024-06-17 23:37:13 -06:00
e38dcc09b1
intcmp.1: bold 2024-06-17 23:27:19 -06:00
59eee27979
strcmp.1: or 2024-06-17 23:21:44 -06:00
bf10689606
swab.1: bold 2024-06-17 23:20:29 -06:00
266ee20d5c
fop.1: removes unnecessary stdin section and adds AUTHOR 2024-06-17 23:18:45 -06:00
1b299f8ee1
rpn.1: fixes clunkiness 2024-06-17 23:16:25 -06:00
53d5a1db73
hru.1: italics and removes clunky sentences 2024-06-17 23:10:01 -06:00
ee9d42d0d4
str.1: cleanup 2024-06-17 23:02:13 -06:00
15d5761cd7
mm.1: fixes clunky sentence 2024-06-17 22:57:08 -06:00
376feb9ae9
npc.1: fixes minor details 2024-06-17 22:56:17 -06:00
5cfccf75af
tests: bonsai/test_env: set -e; tests: strcmp: -h causes error 2024-06-07 23:30:13 -06:00
314254b32f
docs: hotfix 2024-06-06 13:32:54 -06:00
40da8135f1
getopt.rs(3): relicense to AGPLv3 2024-06-06 01:03:01 -06:00
51421b2128
dj.1, strcmp.1, swab.1: remove nonexistent roff macros 2024-06-06 00:14:55 -06:00
28823206bd
Merge branch 'docs' (closes #81) 2024-06-05 23:22:57 -06:00
4d27a4976c
swab.1: -f Ignores the SIGINT signal 2024-06-05 23:17:21 -06:00
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
526185c2c3
swab.1: typo 2024-06-05 22:35:50 -06:00
ad6f9d565b
dj.1: formats sysexits.h(3) reference 2024-06-05 20:39:37 -06:00
f3f3b7f9aa
Merge branch 'coc' (closes #100) 2024-06-05 17:35:16 -06:00
d22d90b6ec
Merge branch 'fop-delim' (closes #106) 2024-06-05 17:28:51 -06:00
516d10e21d
CONDUCT: wording 2024-06-05 17:27:13 -06:00
3392d64e44
fop(1): literal record separator 2024-06-05 17:25:22 -06:00
11b1b424a0
dj.1: more clarification 2024-06-05 17:18:21 -06:00
3c0725f137
dj.1: adds DESCRIPTION and removes seek/skip confusion 2024-06-05 17:15:42 -06:00
02daca87dc
fop(1): updated to latest optind api 2024-06-05 11:55:16 -06:00
e862b7fec6
getopt.rs(3): safe api around optind reading and setting 2024-06-05 11:54:55 -06:00
05cde15105
fop(1): updated to use new optind api 2024-06-04 19:15:55 -06:00
35ddc729fd
getopt.rs(3): makes optind part of the Opt struct 2024-06-04 19:15:22 -06:00
46c4be5c59
CONDUCT: no llm contributions 2024-06-04 16:23:27 -06:00
f4cad598c4
docs: formatting 2024-06-04 16:11:33 -06:00
b7283d54fe
getopt.rs(3): formatting & organization 2024-06-04 15:07:11 -06:00
a5a9c91cb6
getopt.rs(3): optind support 2024-06-04 15:04:01 -06:00
896b251434
hru.1: fixes formatting 2024-06-03 23:52:28 -06:00
fec61fee1b
swab.1: fixes formatting 2024-06-03 23:47:14 -06:00
beff2d98c6
mm.1: fixed manpage reference formatting 2024-06-03 23:45:26 -06:00
e7ba74013b
dj.1: fixes man page reference formatting 2024-06-03 23:21:00 -06:00
1211524dee
Merge branch 'README-updates' 2024-06-03 23:08:02 -06:00
c32c554e03
docs: removed unnecessary comments 2024-06-03 23:07:19 -06:00
d6fb08019e
CONDUCT: initial commit 2024-06-02 23:14:40 -06:00
c88c41b213
tests: adds toki pona locale 2024-06-02 20:14:06 -06:00
642774bccf
src: removed erroneous file 2024-06-02 19:21:56 -06:00
24b235e7c1
README: updated to make current 2024-06-02 19:15:23 -06:00
2dd6c0ded7
fop(1): adds the ability to use any string as a delimiter 2024-06-02 19:05:30 -06:00
70cbc52c93
docs: updates to use man(7) macros to fix formatting 2024-06-02 18:47:14 -06:00
406feb3dc7
tests: test.sh: fixes locales when run from root of project 2024-05-27 22:37:15 -06:00
787f0dc6e2
tests: test.sh: actually includes localization variables 2024-05-27 22:23:55 -06:00
94ada03ce4
tests: adds easy localization 2024-05-27 22:17:30 -06:00
9bfc587623
tests: posix: add environment script for sourcing in tests 2024-05-27 22:12:54 -06:00
8508479a5b
tests: test.sh: remove superfluous printing of test category 2024-05-27 22:07:17 -06:00
b7f52902b6
dj.1: grammar & formatting 2024-05-24 10:21:06 -06:00
922ee71283
dj.1: fixes standard input section grammar 2024-05-24 10:14:53 -06:00
122a10257e
dj.1: fixed option description locations 2024-05-24 10:13:59 -06:00
0660db3d02
dj.1: -c: better description 2024-05-24 10:13:17 -06:00
b4173a2327
dj.1: -o: better description 2024-05-24 10:06:15 -06:00
2137b0bf43
dj.1: -i: better description 2024-05-24 10:03:52 -06:00
d87f4b20a3
dj.1: -A: better description 2024-05-24 09:56:44 -06:00
c53f3fa617
dj.1: remove mention of KiB 2024-05-24 09:53:54 -06:00
432b19818e
made dj options no longer alphabetized 2024-04-28 20:42:44 -06:00
e028844825
tests: test.sh: utilize $BIN 2024-04-27 15:18:09 -06:00
f022121436
tests: posix: cat(1p): added POSIX-compliant cat 2024-04-27 15:16:52 -06:00
9093b06166
Merge branch 'manpage-install' 2024-04-27 12:58:50 -06:00
e278307daf
tests: bonsai: removed redundant code 2024-04-26 20:55:16 -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
4ad9e0da92
tests: bonsai: rename aliases sourced script to test_env 2024-04-25 18:57:05 -06:00
0113cf793d
tests: bonsai: hru.sh: adds test 2024-04-25 17:20:55 -06:00
488351da39
scrut(1): changed return status 0 to EXIT_SUCCESS 2024-04-25 16:46:05 -06:00
d8b54fdbf5
Makefile: fixes erroneous whitespace 2024-04-25 12:43:31 -06:00
f6aac60aee
Makefile: fixed manpage install location 2024-04-25 12:40:20 -06:00
1aa2b596d0
tests: aliases: added set -x 2024-04-24 19:28:33 -06:00
c6f30c4195
tests: bonsai: fop.sh: added test 2024-04-24 19:25:55 -06:00
3398fc372c
tests: bonsai: mm.sh: fixed copyright 2024-04-24 15:33:17 -06:00
28f2d44e2f
CONTRIBUTING: updated to include docs and tests requirement 2024-04-24 15:24:35 -06:00
aefa87d9e5
tests: fixed aliasing and created mm.sh 2024-04-24 15:22:07 -06:00
057f5571d6
moved tests into test directory 2024-04-24 14:58:35 -06:00
187d9486b7
dj.1: debug output clarification 2024-04-18 08:44:44 -06:00
b41af1b578
dj.1: -c: grammar 2024-04-18 08:41:55 -06:00
ed284b9949
dj.1: -a: More specific wording 2024-04-18 08:40:29 -06:00
3cdade71e2
dj.1: -S whether or not the input is a stream is irrelevant 2024-04-18 08:38:48 -06:00
df16707b0e
intcmp.1: -e permits adjacent integers to be equal to each other 2024-04-18 08:36:01 -06:00
78eacd660a
getopt.rs(3): better Opt return 2024-04-13 23:42:11 -06:00
d1b77d652b
getopt.rs(3): returns last parsed option 2024-04-13 17:11:04 -06:00
8c255e61fc
COPYING.GPL: initial commit 2024-04-12 00:12:37 -06:00
95927ba8c5
getopt.rs(3): formatting 2024-04-11 23:51:52 -06:00
ad92fe27d4
fop(1): switch to getopt.rs(3) 2024-04-11 23:37:04 -06:00
bb2c63bfaf
getopt.rs(3): fixed pointer shenanigans 2024-04-11 20:33:42 -06:00
88b0d55440
getopt.rs(3): refactor to remove as much as possible from unsafe 2024-04-11 20:21:01 -06:00
0164b681c0
Makefile: remove unneeded test 2024-04-01 20:41:07 -06:00
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
13ee16173e
fop.1: initial commit 2024-03-29 16:47:00 -06:00
5db09a5ca1
mm.1: cat(1p) and tee(1p) provide similar functionality 2024-03-29 16:30:06 -06:00
ce5a4dc4bd
mm.1: removed STANDARD INPUT section 2024-03-29 16:29:27 -06:00
abc599148d
mm.1: subsequent outputs are opened for appending 2024-03-29 16:26:41 -06:00
63c8ff8093
intcmp.1: compares integers to each other 2024-03-29 16:22:56 -06:00
9ea57a27b7
dj.1: stdin by default 2024-03-29 16:21:02 -06:00
4e33f945ae
dj.1: null bytes 2024-03-29 16:17:48 -06:00
70b0c2f924
dj.1: fixed -d description 2024-03-29 16:14:27 -06:00
603d8ee1d8
str.1: remove extraneous former implementation information 2024-03-27 00:17:33 -06:00
cdd8e79b01
str.1: strings are not tested against each other 2024-03-27 00:16:51 -06:00
d3bfc7b1f5
npc.1: ASCII bytes 2024-03-27 00:16:15 -06:00
3cb37d830a
mm.1: wording, consistency with dj.1 2024-03-27 00:14:34 -06:00
6158a39a4a
dj.1: consistency with mm.1 2024-03-27 00:14:02 -06:00
a2188dc674
dj.1: fix -H description 2024-03-27 00:09:17 -06:00
49e2022e52
dj.1: -d, -i, -o, fixed descriptions 2024-03-27 00:08:43 -06:00
bb43533a37
dj.1: -A and -a: fix descriptions 2024-03-26 23:58:00 -06:00
f565f0530b
dj.1: dd(1p) is not a disk utility 2024-03-26 23:53:10 -06:00
a1902df503
strcmp.1: Unicode is a proper noun 2024-03-26 23:50:16 -06:00
63a0c683f9
docs: remove unnecessary references to the name of each program 2024-03-26 19:22:30 -06:00
cf76fa94e6
mm.1: updated man page 2024-03-26 18:44:05 -06:00
a6fd1108c6
docs: fixed formatting of many manpages 2024-03-26 18:26:51 -06:00
DTB
e7021e127c
Testfile: make non-executable 2024-03-13 18:20:30 -06:00
DTB
417d7ca405
Testfile: fix syntax 2024-03-11 21:03:53 -06:00
DTB
f7a74dc430
Testfile 2024-03-11 21:01:29 -06:00
DTB
cabe08bca4
TESTING: start testing document/script 2024-02-29 20:33:09 -07:00
66 changed files with 3825 additions and 2717 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,24 +1,29 @@
When contributing a pull request to the main branch, please sign your commits
with a PGP key and add your name and the year to the bottom of the list of
copyright holders for the file. For example, an existing copyright header might
read:
Make sure to read our code of conduct in the CONDUCT file.
Copyright Information
=====================
When editing a file, create a copyright statement correlated to your
identity so that it is easier to keep track of who has touched what file.
Pseudonymous contributions are welcome (and encouraged). Place new copyright
information below existing copyright information. If there is an existing
copyright statement:
* Copyright (c) 20222023 Emma Tebibyte <emma@tebibyte.media>
You would add your name below it like this:
you would add your name below it like this:
* Copyright (c) 20222023 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 20XX Your Name <your e-mail address or website>
We accept contributions from people using aliases.
Only list years in which you modified the source file. For example:
* Copyright (c) 20202021, 2023 Your Name <your-address@example.com>
This header shows that “Your Name” worked on this source file in 2020, 2021, and
2023. Please use the en dash (“–”) to separate the years in the copyright
notice.
2023. Please use the en dash (“–”, U+2013) to separate consecutive years in the
copyright notice.
If you are contributing a new file, please prepend the following license header
text to it, replacing the proper text on the copyright line:
@ -90,6 +95,10 @@ notice:
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
Style
=====
Make sure lines never exceed 80 columns in width when using four-character
indentation steps. This helps contributors with smaller screens, those using
side-by-side editor windows or panes, and those who have no text wrapping in
@ -97,12 +106,18 @@ their editor or terminal.
For usage text and help messages, do not implement a -h option. Instead, print
usage information when any erroneous option is specified. Follow the NetBSD
style guide for the usage texts output format [1].
style guide for the usage texts output format [0].
[1] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
If committing a new utility, please include tests and documentation (see
tests/ and docs/) for the new tool.
If committing a new source file, format the commit message following these
guidelines:
Committing
==========
When contributing to Bonsai, please sign your commit with a PGP key and create
the commit with an identity which can be easily contacted.
Format commit messages following these guidelines:
$ git commit -m 'tool(1): add feature x'
@ -128,6 +143,13 @@ $ git commit -m 'tool(1): fix #42 & add feature x'
Commit messages should be written in the present tense.
References
==========
[0] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
--
This work © 20232024 by Emma Tebibyte is licensed under CC BY-SA 4.0. To view a
copy of this license, visit <http://creativecommons.org/licenses/by-sa/4.0/>

137
Makefile
View File

@ -1,4 +1,4 @@
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232025 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232024 DTB <trinity@trinity.moe>
# Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media>
# Copyright (c) 2024 Aaditya Aryal <aryalaadi123@gmail.com>
@ -8,70 +8,105 @@
# 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
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | sed 's/ /\n/g' \
# for conditionally compiling OS features
OS != uname
OS_INCLUDE != test -e include/$(OS).mk && printf 'include/$(OS).mk\n' \
|| printf '/dev/null\n'
# normalized prefix
PREFIX_N != realpath $(PREFIX)
MANDIR != test $(PREFIX_N) = / && printf '/usr/share/man\n' \
|| printf '$(PREFIX_N)/man\n'
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | tr ' ' '\n' \
| sed -n 's/sysexits\.h//p' || printf 'include\n'
CC ?= cc
RUSTC ?= rustc
RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
--extern strerror=build/o/libstrerror.rlib
RUSTFLAGS += --extern getopt=build/o/libgetopt.rlib \
--extern strerror=build/o/libstrerror.rlib \
--extern sysexits=build/o/libsysexits.rlib
CFLAGS += -I$(SYSEXITS)
.PHONY: all
all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
# testing requires the absolute path to the bin directory set
BIN = build/bin
.PHONY: default
default: all test
.PHONY: all
all: dj false fileis fop hru intcmp mm npc peek rpn str strcmp swab true
# keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703
build:
# keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703
mkdir -p build/bin build/include build/lib build/o build/test
mkdir -p build/bin build/docs build/include build/lib build/o build/test
.PHONY: clean
clean:
rm -rf build dist
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
dist: all docs
mkdir -p $(DESTDIR)/$(PREFIX_N)/bin $(DESTDIR)/$(MANDIR)/man1
cp build/bin/* $(DESTDIR)/$(PREFIX_N)/bin
cp build/docs/*.1 $(DESTDIR)/$(MANDIR)/man1
.PHONY: install
install: dist
cp -r $(DESTDIR)/* /
TESTFILES != for file in tests/*.mk; do printf '%s ' "$$file"; done
TESTS != printf '%s\n' "$(TESTFILES)" | xargs -n1 basename \
| sed 's/\.mk/_tests/g'
include $(TESTFILES)
.PHONY: test
test: build
tests/posix-compat.sh
$(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt
test: all $(TESTS) /tmp/getopt
@echo $(TESTS)
/tmp/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
# include OS feature libraries for compilation
include $(OS_INCLUDE)
.PHONY: rustlibs
rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \
build/o/libstrerror.rlib
rustlibs: build/o/libgetopt.rlib build/o/libstrerror.rlib \
build/o/libsysexits.rlib $(OSLIB)
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
bindgen --default-macro-constant-type signed --use-core --formatter=none \
build/o/libsysexits.rlib: build/include/sysexits.h
bindgen --fit-macro-constant-types --default-macro-constant-type unsigned --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
@ -82,41 +117,45 @@ false: build/bin/false
build/bin/false: src/false.c build
$(CC) $(CFLAGS) -o $@ src/false.c
.PHONY: fileis
fileis: build/bin/fileis
build/bin/fileis: src/fileis.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/fileis.rs
.PHONY: fop
fop: build/bin/fop
build/bin/fop: src/fop.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/fop.rs
$(RUSTC) $(RUSTFLAGS) -o $@ src/fop.rs
.PHONY: hru
hru: build/bin/hru
build/bin/hru: src/hru.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/hru.rs
$(RUSTC) $(RUSTFLAGS) -o $@ src/hru.rs
.PHONY: intcmp
intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.c build
$(CC) $(CFLAGS) -o $@ src/intcmp.c
build/bin/intcmp: src/intcmp.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/intcmp.rs
.PHONY: mm
mm: build/bin/mm
build/bin/mm: src/mm.c build
$(CC) $(CFLAGS) -o $@ src/mm.c
build/bin/mm: src/mm.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/mm.rs
.PHONY: npc
npc: build/bin/npc
build/bin/npc: src/npc.c build
$(CC) $(CFLAGAS) -o $@ src/npc.c
$(CC) $(CFLAGS) -o $@ src/npc.c
.PHONY: peek
peek: build/bin/peek
build/bin/peek: src/peek.c build
$(CC) $(CFLAGS) -o $@ src/peek.c
.PHONY: rpn
rpn: build/bin/rpn
build/bin/rpn: src/rpn.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/rpn.rs
.PHONY: scrut
scrut: build/bin/scrut
build/bin/scrut: src/scrut.c build
$(CC) $(CFLAGS) -o $@ src/scrut.c
$(RUSTC) $(RUSTFLAGS) -o $@ src/rpn.rs
.PHONY: str
str: build/bin/str
@ -130,10 +169,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) -o $@ src/swab.rs
.PHONY: true
true: build/bin/true

60
README
View File

@ -1,28 +1,33 @@
“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.
Bonsais Harakit is an alternative to the standard POSIX utilities that aims to
be simpler, easier, and more powerful. These tools are the result of careful
examination of the current state common Unix utilities, POSIX-compliant and
otherwise, following frustrations with design decisions and implementation
details. They represent a vision of accomplishing everyday use cases with tools
that follow the Unix philosophy of “do one thing and do it well” without
clinging to the past.
The 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.
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.
The intent of Harakit is not to conform to or extend POSIX, like the GNU or BSD
utilities do, but to invent new utilities to perform the same tasks in more
intuitive ways. GNU and BSD extensions are convenient but often unhealthy,
forgetting the purposes of the tools they extend, or building into existing
utilities features that would be more useful as their own tools to be used
anywhere. Other utility sets aim to provide a number of fully-featured
programs to be used individually, Harakit utilities are meant to be easily
composable and work together in pipelines.
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.
Building
========
Harakit utilities require a POSIX-compliant environment to compile, including a
C compiler and preprocessor (cc(1) and cpp(1) by default), an edition 2023 Rust
compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant make(1)
utility.
To build and install:
@ -38,11 +43,26 @@ To test the utilities:
$ make test
To remove all untracked files:
To remove all build and distributable files:
$ make clean
Contributing
============
See the CONTRIBUTING file for contribution guidelines.
Community
=========
xmpp://bonsai@covenant.murderu.us
irc://feeling.murderu.us/#bonsai
Read More
=========
An Introduction to the Unix Shell
<https://porkmail.org/era/unix/shell>
@ -56,6 +76,10 @@ Master Foo Discourses on the Unix-Nature
Shell Programming!
<https://tldp.org/LDP/abs/html/why-shell.html>
UNIX Style, or cat -v Considered Harmful
<http://harmful.cat-v.org/cat-v/>
--
Copyright © 20232024 Emma Tebibyte <emma@tebibyte.media>
Copyright © 2024 DTB <trinity@trinity.moe>

254
STYLE Normal file
View File

@ -0,0 +1,254 @@
“Everyone knows that debugging is twice as hard as writing a program in the
first place. So if youre as clever as you can be when you write it, how
will you ever debug it?”
Brian Kernighan, The Elements of Programming Style
The following guidelines are conducive to clear and readable code that is
consistent with the style of the rest of the Bonsai Computer System.
Use
===
0. A single line for control flow statements short enough to be easily
understood at a glance:
if !(argc < 0) { usage(program_name); }
This applies to C switch statements and cases and Rust match statements, as
well:
switch (value) { /* aligning stuff to make it easier to read is fine */
case possibility: variable = foo; break;
default: variable = NULL; break;
}
1. Switch cases in C and match arms in Rust should start another level of
indentation:
switch (value) {
case possibility:
statement;
break;
default:
statement;
break;
}
match result {
Ok(n) => variable = n,
Err(e) => error = e,
}
2. Braces in control flow where their inclusion is left optional in C:
if (condition) { statement; }
3. Empty lines between different kinds of statements:
int t;
assert(io->bufuse > 0);
assert(io->bufuse <= io->bs);
if ((t = write(io->fd, io->buf, io->bufuse)) < 0) {
io->error = errno;
t = 0;
} else if (t > 0) {
memmove(io->buf, &(io->buf)[t], (io->bufuse -= t));
}
io->bytes += t;
io->prec += (t > 0 && io->bufuse > 0);
io->rec += (t > 0 && io->bufuse == 0);
return io;
4. Compiler options that yield the most useful warnings, such as -Wpedantic in
a lot of C compilers. Fix the warnings, too [0].
5. One more level of indentation and one argument per line when a function
call or statement header is too long to fit on one line:
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
6. One more level of indentation than the keyword that initiated a multi-line
block.
if (condition) {
statement;
statement;
}
7. The return value of all non-void functions, or explicitly ignore them (like
casting to void in C) [0]:
if ((a = malloc(sizeof char)) == NULL) { /* handle this error */
(void)fprintf(stderr, "oh noes!"); /* explicitly ignore this one */
return EX_OSERR; /* ...because the program is exiting anyway */
}
8. The smallest possible scope for data [0].
9. Comments noting all the symbols and macros used from a C header file, next
to its include macro:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
(space-aligned) * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
10. Spaces in control flow statements, after the keyword and before the
opening brace:
for (i = 2; i < argc; ++i) {
11. In Rust, a trailing comma on all arguments or fields that are on their own
lines:
return Err(EvaluationError {
message: format!("{}: Invalid token", i),
code: EX_DATAERR,
})
12. In Rust, place extern statements after use statements that include standard
library crates. Group like statements:
use std::fs::Path;
extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE };
13. If text is on the same line as a brace, spaces after an opening brace and
before a closing one:
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
14. Alphabetic sorting, where applicable:
use std::io::{ BufWriter, Read, Write, stderr, stdin, stdout }
15. In Rust, use the to_owned() method on string types (str, OsStr, CStr, etc.)
and the to_string() method on other types.
Avoid
=====
16. Unbounded loops [0].
17. Function pointers [0].
18. Heap memory allocation [0].
19. Using too much nested logic (within reason).
20. Too many levels of dereferences [0]:
/* do not do this */
for (size_t i = 0; i < sizeof a / sizeof *a; ++i) {
if (a[i].id == MATCH) { a[i].val = 0; }
}
/* do this */
for (struct MadeUp *s = &a[0]; *s != NULL; s = &s[1]) {
if (s->id == MATCH) { s->val = 0; }
}
21. Using C preprocessor macros; the fewer, the better [0].
22. The exit(3p) and std::process::exit() functions; returning from the main
function skips a system call.
Do Not Use
==========
23. More than the length of one printed page for a function [0].
24. Recursion, as its complex and can unexpectedly overflow the stack [0].
25. Any functionality not in the POSIX C specification and language features not
in C99.
26. Do-while loops, as theyre unique to C and confusing for casual programmers.
27. Labels and goto statements; use sensible flow control [0].
28. Pointer arithmetic, as it tends to be confusing and unnecessary; use
index-reference patterns like &p[1] instead of p + 1. &p[n] is the address at
p + sizeof p * n, not p + n, like pointer arithmetic suggests.
29. C struct bitfields in unions, to access certain bits of bigger data types,
as its poorly defined in the C standards; use bit arithmetic.
30. C trigraphs.
31. Inclusions in C header files, to prevent multiple file inclusions.
32. C preprocessor variables to prevent multiple inclusions of the same file,
such as:
#ifdef _FILE
#define _FILE
/* file body */
#endif /* ifdef _FILE */
Instead, take the time to ensure other files arent including any files twice.
33. The gets(3p) function from <stdio.h>, as its impossible to prevent buffer
overflows when it's used; use fgets(3p) from <stdio.h>.
34. The scanf(3p) function from <stdio.h> [1].
35. Any functionality not described in the latest POSIX make(1) specification.
36. Macros which panic on failure in Rust (such as the print!() and println!()
macros). Use a function and handle any errors. However, do use the eprintln!()
macro for error messages. Handling an error for writing an error message is
redundant.
Usage Text
==========
This section is adapted from the NetBSD style guide [2].
When programs are invoked incorrectly and in the synopsis of manual pages, uasge
text should be provided to the user. The following is the format used by this
project for this purpose:
All optional arguments are to be placed in square brackets (U+005B, U+005D).
Mutually exclusive arguments can be separated by a vertical line (U+007C).
Groups of arguments should be specified in alphabetical order in most cases. The
order of arguments and an example of these rules follows:
0. Options with no option arguments.
1. Options with option arguments. Arguments should be specified inside the same
square brackets as the options.
3. Non-option arguments.
"usage: f [-aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\n"
"usage: f [-a | -b] [-c [-de] [-n number]]\n"
References
==========
[0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf>
[1] <http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html>
[2] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
--
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>
Copyright © 2024 DTB <trinity@trinity.moe>
Copyright © Wikipedia contributors
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit
<http://creativecommons.org/licenses/by-sa/4.0/>.

328
docs/dj.1
View File

@ -1,160 +1,226 @@
.\" 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-07-14 "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
.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.
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.
The offset used when skipping or seeking refers to how many bytes are skipped
or sought. Running
.BR dj (1)
with a skip offset of 1 skips one byte into the input and reads from the second
byte onwards. A programmer may think of a file as a zero-indexed array of
bytes; in this analogy, the offset given is the index of the byte at which to
start reading or writing.
.\"
.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
Takes a numeric argument as the size in bytes of the output buffer, the default
being 1024. Note that this option only affects the size of output writes and not
the amount of output data itself. See the CAVEATS section.
.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 blocks to read. 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 EXAMPLES
The following
.BR sh (1p)
line:
.RS
printf 'Hello, world!\(rsn' | dj -c 1 -b 7 -s 7 2>/dev/null
.RE
Produces the following output:
.RS
world!
.RE
The following
.BR sh (1p)
lines run sequentially:
.RS
tr '\(rs0' 0 </dev/zero | dj -c 1 -b 6 -o hello.txt
tr '\(rs0' H </dev/zero | dj -c 1 -b 1 -o hello.txt
tr '\(rs0' e </dev/zero | dj -c 1 -b 1 -o hello.txt -S 1
tr '\(rs0' l </dev/zero | dj -c 1 -b 2 -o hello.txt -S 2
tr '\(rs0' o </dev/zero | dj -c 1 -b 1 -o hello.txt -S 4
tr '\(rs0' '\(rsn' </dev/zero | dj -c 1 -b 1 -o hello.txt -S 5
dj -i hello.txt
.RE
Produce the following output:
.RS
Hello
.RE
It may be particularly illuminating to print the contents of the example
.B hello.txt
after each
.BR dj (1)
invocation.
.\"
.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
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.
option is specified, this could make written data nonsensical.
.\"
.SH CAVEATS
Existing files are not truncated on ouput and are instead overwritten.
Option variants that have lowercase and uppercase forms could be confused for
each other. The former affects input and the latter affects output.
The
.B -B
option could be mistaken for the count in bytes of data written to the output.
This conception is intuitive but incorrect, as the
.B -c
option controls the number of blocks to read and the
.B -b
option sets the size of the blocks. The
.B -B
option is similar to the latter but sets the size of blocks to be written,
regardless of the amount of data that will actually be written. In practice,
this means the input buffer should be very large to make use of modern hardware
input and output speeds.
The skipped or sought bytes while processing irregular files, such as streams,
are reported in the diagnostic output, because they were actually read or
written. This is as opposed to bytes skipped while processing regular files,
which are not reported.
.\"
.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)
.BR lseek (3p)
.BR mm (1)

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)

87
docs/fileis.1 Normal file
View File

@ -0,0 +1,87 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 20242025 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\"
.TH FILEIS 1 2025-02-24 "Harakit X.X.X"
.SH NAME
fileis \(en scrutinize file properties
.SH SYNOPSIS
fileis
.RB [ -LSbcdefgkprsuwx ]
.B file...
.\"
.SH DESCRIPTION
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
.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
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
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
.MT trinity@trinity.moe
.ME .
.\"
.SH COPYRIGHT
Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.\"
.SH SEE ALSO
.BR access (3p),
.BR chown (1p),
.BR lstat (3p),
.BR test (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)

103
docs/mm.1
View File

@ -2,75 +2,72 @@
.\"
.\" 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-07-14 "Harakit X.X.X"
.SH NAME
mm \(en middleman
.\"
.SH SYNOPSIS
mm
.RB ( -aenu )
.RB ( -i
.RB [ input ])
.RB ( -o
.RB [ output ])
.RB [ -aetu ]
.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
.IP \fB-a\fP
Opens outputs for appending rather than updating.
.IP \fB-e\fP
Use the standard error as an output.
.IP \fB-t\fP
Causes outputs to be overwritten instead of being truncated.
.IP \fB-u\fP
Ensures neither input or output will be buffered.
.IP \fB-i\fP\ \fIinput\fP
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
no inputs are specified, the standard input shall be used. If specified as the
last option and if there are trailing arguments to the program, they shall be
appended to the list of files to use as inputs.
.IP \fB-o\fP\ \fIoutput\fP
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
no outputs are specified and the
.B -e
option.
.PP
The
.B -u
option ensures neither input or output will be buffered.
.PP
The
.B -n
option tells mm to ignore SIGINT signals.
option is not specified, the standard output shall be used. If specified as the
last option and if there are trailing arguments to the program, they shall be
appended to the list of files to use as outputs.
.\"
.SH DIAGNOSTICS
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.
.SH BUGS
Mm does not truncate existing files, which may lead to unexpected results.
If an output cannot be written to, an error occurs; however, exiting will be
deferred until writing to any other specified outputs completes.
When an error is encountered, a diagnostic message is printed and the program
exits with the appropriate
.BR sysexits.h (3)
status.
.\"
.SH 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

111
docs/peek.1 Normal file
View File

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

View File

@ -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 +0,0 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.TH scrut 1
.SH NAME
scrut \(en scrutinize file properties
.SH SYNOPSIS
scrut
.RB ( -bcdefgkprsuwxLS )
.RB [ file... ]
.SH DESCRIPTION
Scrut determines if given files comply with the opted requirements.
.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.
.SH EXIT STATUS
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
.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>.
.SH COPYRIGHT
Copyright © 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)

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-07-15 "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 less than 128 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 greater than 128 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,75 @@
.\" 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 [ -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-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
.\" If you change this, make sure to change it in tests/bonsai/swab.mk too.
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)

6
include/FreeBSD.mk Normal file
View File

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

13
include/OpenBSD.mk Normal file
View File

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

720
src/dj.c
View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
@ -16,437 +17,422 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <ctype.h> /* isupper(3), tolower(3) */
#include <assert.h> /* assert(3) */
#include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */
#include <stdbool.h> /* bool */
#include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* free(3), malloc(3), strtol(3), size_t */
#include <stdlib.h> /* malloc(3), strtol(3), size_t */
#include <string.h> /* memcpy(3), memmove(3), memset(3) */
#include <sysexits.h> /* EX_OK, EX_USAGE */
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
extern int errno;
* pledge(2), unveil(2), optarg, optind, STDIN_FILENO,
* STDOUT_FILENO */
#include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR */
char *program_name = "dj";
/* 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. */
struct Io{
int bs; /* buffer size (-bB) */
struct Io {
char *buf; /* buffer */
char *fn; /* file name (-io) */
size_t bs; /* buffer size (-bB) */
size_t bufuse; /* buffer usage */
char *buf; /* buffer */
int bytes; /* bytes processed */
int fd; /* file descriptor */
int fl; /* file opening flags */
char *fn; /* file name (may be stdin_name or stdout_name) (-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 */
size_t bytes; /* bytes processed */
size_t prec; /* partial records processed */
size_t rec; /* records processed */
long seek; /* remaining bytes to seek/skip (-sS) */
int error; /* errno */
int fd; /* file descriptor */
int fl; /* file opening flags */
};
/* Additionally, the following global variables are used to store user options.
*/
/* (-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 creat_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
| S_IWOTH; /* Consistent with touch(1p). */
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))
/* Macro to check if fd is a std* file, e.g. stdin. */
#define fdisstd(fd) \
((fd) == STDIN_FILENO \
|| (fd) == STDOUT_FILENO \
|| (fd) == STDERR_FILENO)
/* Macro to check if fd is stdin or stdout */
#define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO)
/* Macro to call the cleanup functions that operate on struct io on the
* 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]); \
Io_fdclose(&(io)[0]); \
Io_fdclose(&(io)[1]); }while(0)
/* Allocates *io's buffer. Returns NULL if unsuccessful. */
static void *
Io_bufalloc(struct Io *io){
return (io->buf = malloc(io->bs * (sizeof *io->buf)));
}
/* Frees *io's buffer. Returns io. */
/* Completes one Io block read */
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 *
Io_bufrpad(struct Io *io, int padding){
memset(io->buf + io->bufuse, padding, io->bs - io->bufuse);
io->bufuse = io->bs;
return io;
}
/* Copies from the buffer in src as much as possible to the free space in the
* dest buffer, removing the copied units from src and permuting the remaining
* units in the src buffer to the start of the buffer, modifying both the src
* and dest bufuse and returning dest. */
static struct Io*
Io_bufxapp(struct Io *dest, struct Io *src){
int n;
n = MIN(src->bufuse, dest->bs - dest->bufuse);
memcpy(dest->buf + dest->bufuse, src->buf, n);
dest->bufuse += n;
memmove(src->buf, src->buf + n, src->bs - n);
src->bufuse -= n;
return dest;
}
/* Copies from the buffer in src to the buffer in dest no more than n units,
* removing the copied units from src and permuting the remaining units in the
* src buffer to the start of the buffer, modifying both the src and dest
* bufuse and returning dest. */
static struct Io*
Io_bufxfer(struct Io *dest, struct Io *src, int n){
memcpy(dest->buf, src->buf, (dest->bufuse = n));
memmove(src->buf, src->buf + n, (src->bufuse -= n));
return dest;
}
/* Closes io->fn and returns -1 on error, otherwise io->fd. */
static int
Io_fdclose(struct Io *io){
return fdisstd(io->fd)
? 0
: close(io->fd);
}
/* Opens io->fn and saves the file descriptor into io->fd. Returns io->fd,
* which will be -1 if an error occured. */
static int
Io_fdopen(struct Io *io, char *fn){
int fd;
if((fd = open(fn, io->fl,
/* these are the flags used by touch(1p) */
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH))
!= -1
&& Io_fdclose(io) == 0){
io->fd = fd;
io->fn = 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
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;
/* repeated code to get the condition out of the loop */
if(io->fl == write_flags){
memset(io->buf, '\0', io->bs);
/* We're going to cheat and use bufuse as the retval for write(2),
* which is fine because it'll be zeroed as this function returns
* anyway. */
do{
if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else if(io->fl == read_flags){
do{
if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else
return EX_SOFTWARE;
io->bufuse = 0;
return -1;
}
/* Reads io->bs bytes from *io's file descriptor into io->buf, storing the
* number of read bytes in io->bufuse and updating io->bytes. If io->bufuse is
* 0, errno will probably be set. Returns io. */
static struct Io *
Io_read(struct Io *io){
io->bytes += (io->bufuse = read(io->fd, io->buf, io->bs));
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. */
static struct Io *
Io_write(struct Io *io){
Io_read(struct Io *io) {
int t;
if((t = write(io->fd, io->buf, io->bufuse)) > 0)
memmove(io->buf, io->buf + t, (io->bufuse -= t));
io->bytes += t;
assert(io->bs > 0);
assert(io->bufuse < io->bs);
if ((t = read(io->fd, &(io->buf)[io->bufuse], io->bs - io->bufuse)) < 0) {
io->error = errno;
t = 0;
}
io->bufuse += t;
io->bytes += t;
io->prec += (0 < io->bufuse && io->bufuse < io->bs);
io->rec += (io->bufuse == io->bs);
assert(io->bufuse <= io->bs);
return io;
}
/* Prints an error message suitable for the event of an operating system error,
* with the error itself to be described in the string s. */
static int
oserr(char *s){
/* Completes one Io block write */
static struct Io *
Io_write(struct Io *io) {
int t;
fprintf(stderr, "%s: %s: %s\n", program_name, s, strerror(errno));
assert(io->bufuse > 0);
assert(io->bufuse <= io->bs);
if ((t = write(io->fd, io->buf, io->bufuse)) < 0) {
io->error = errno;
t = 0;
} else if (t > 0) {
(void)memmove(io->buf, &(io->buf)[t], (io->bufuse -= t));
}
io->bytes += t;
io->prec += (t > 0 && io->bufuse > 0);
io->rec += (t > 0 && io->bufuse == 0);
return io;
}
static int
oserr(char *e, int n) { /* program_name: [failing component:] error */
(void)fprintf(stderr, "%s: ", program_name);
if (e != NULL) { (void)fprintf(stderr, "%s: ", e); }
(void)fprintf(stderr, "%s\n", strerror(n));
return EX_OSERR;
}
/* Prints statistics regarding the use of dj, particularly partially and
* completely read and written records, accessing debug, ep, and fmt_output. */
static void
output(void){
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);
return;
* completely read and written records. */
static int
fprintio(FILE *stream, char *fmt, struct Io io[2]) {
return fprintf(
stream,
fmt,
io[0].rec,
io[0].prec,
io[1].rec,
io[1].prec,
io[0].bytes,
io[1].bytes
);
}
/* To be assigned to main:fmt and used with printio(). */
static char *fmt_asv =
"%d" /* io[0].rec */ "\037" /* ASCII US */
"%d" /* io[0].prec */ "\036" /* ASCII RS */
"%d" /* io[1].rec */ "\037" /* ASCII US */
"%d" /* io[1].prec */ "\035" /* ASCII GS */
"%d" /* io[0].bytes */ "\036" /* ASCII RS */
"%d" /* io[1].bytes */ "\034" /* ASCII FS */
"\n"
;
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
/* Parses the string s to an integer, returning either the integer or in the
* case of an error a negative integer. This is used for argument parsing
* (e.g. -B [int]) in dj and no negative integer would be valid anyway. */
static long
parse(char *s){
parse(char *s) {
long r;
errno = 0;
r = strtol(s, &s, 0);
return (*s == '\0' /* no chars left unparsed */ && errno == 0)
? r
: -1;
return (*s == '\0' /* no chars left unparsed */ && errno == 0) ? r : -1;
}
static int
usage(void){
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",
program_name);
usage(char *argv0) {
(void)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",
argv0
);
return EX_USAGE;
}
int main(int argc, char *argv[]){
int c;
int i;
int main(int argc, char *argv[]) {
int align; /* low 8b used, negative if no alignment is being done */
int count; /* -1 if dj(1) runs until no more reads are possible */
char *fmt; /* set to fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io (in or out) being modified */
bool retry; /* false if exits on partial reads or writes */
struct Io io[2 /* { in, out } */];
setdefaults;
#ifdef __OpenBSD__
if (pledge("cpath rpath stdio unveil wpath", NULL) == -1) {
return oserr(NULL, errno);
}
#endif
/* Set defaults. */
align = -1;
count = -1;
fmt = fmt_asv;
retry = 0;
for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bufuse = 0;
io[i].bytes = 0;
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name;
io[i].fl = i == 0 ? read_flags : write_flags;
io[i].error = 0;
io[i].prec = 0;
io[i].rec = 0;
io[i].seek = 0;
}
if (argc > 0) {
int c;
if(argc > 0){
program_name = argv[0];
while((c = getopt(argc, argv, "a:Ab:B:c:di:hHnqs:S:o:")) != -1)
switch(c){
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;
break;
}else if(Io_fdopen(&ep[i], optarg) != -1)
break;
terminate(ep);
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'){
align = optarg[0];
break;
}
/* FALLTHROUGH */
case 'c': case 'b': case 's': case 'B': case 'S':
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))
break;
/* FALLTHROUGH */
default:
terminate(ep);
return usage();
while ((c = getopt(argc, argv, "a:b:B:c:i:hHns:S:o:")) != -1) {
switch (c) {
case 'i': case 'o': /* input, output */
i = (c == 'o');
/* optarg == "-" (stdin/stdout) */
if (optarg[0] == '-' && optarg[1] == '\0') {
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name;
break;
} else {
int fd;
#ifdef __OpenBSD__
if (unveil(optarg, i == 0 ? "r" : "wc") == -1) {
return oserr(NULL, errno);
}
#endif
if (
(fd = open(optarg, io[i].fl, creat_mode)) != -1
&& (fdisstd(io[i].fd) || close(io[i].fd) == 0)
) {
io[i].fd = fd;
io[i].fn = optarg;
break;
}
}
return oserr(optarg, errno);
/* UNREACHABLE */
case 'n': retry = 1; break; /* retry failed reads once */
case 'H': fmt = fmt_human; break; /* human-readable output */
case 'a': /* input buffer padding */
if (optarg[0] == '\0' || optarg[1] == '\0') {
align = optarg[0];
break;
}
/* FALLTHROUGH */
case 'c': /* number of reads */
case 'b': case 'B': /* input/output block size */
case 's': case 'S': /* (s)kip/(S)eek in input/output */
if (c == 'c' && (count = parse(optarg)) >= 0) { break; }
i = (c >= 'A' && c <= 'Z');
c |= 0x20; /* 0b 0010 0000 (ASCII) make lowercase */
if ( /* if -b or -s is parsed out correctly */
(c == 'b' && (io[i].bs = parse(optarg)) > 0)
|| (c == 's' && (io[i].seek = parse(optarg)) >= 0)
) { break; } /* don't error */
/* FALLTHROUGH */
default:
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);
#ifdef __OpenBSD__
if (unveil(NULL, NULL) == -1) { return oserr(NULL, errno); }
#endif
if(argc > optind){
terminate(ep);
return usage();
}
assert(io->fd != STDIN_FILENO || io->fl == read_flags);
assert(io->fd != STDOUT_FILENO || io->fl == write_flags);
for(i = 0; i <= 1; ++i){
if(Io_bufalloc(&ep[i]) == NULL){
fprintf(stderr, "%s: Failed to allocate %d bytes\n",
program_name, ep[i].bs);
terminate(ep);
if (argc > optind) { return usage(program_name); }
for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
/* buffer allocation */
if ((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL) {
(void)fprintf(
stderr, "%s: Failed to allocate %zd bytes\n",
program_name, io[i].bs
);
return EX_OSERR;
}else if(ep[i].seek > 0)
switch(Io_fdseek(&ep[i])){
case EX_OK:
output();
terminate(ep);
return EX_OK;
}
}
/* easy seeking */
if (!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1) {
io[i].seek = 0;
}
}
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 */
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();
assert(io[1].bufuse == 0); /* requirement for hard seeking */
/* hard seeking; t is io[1].bufuse, before Io_write subtracts from it */
for(size_t t; io[1].seek > 0; io[1].seek -= (t - io[1].bufuse)) {
(void)memset(
io[1].buf, '\0', /* set buf to all nulls */
(t = io[1].bufuse = MIN(io[1].bs, io[1].seek)) /* saturate block */
);
if (Io_write(&io[1])->bufuse == t && !retry && io[1].error == 0) {
(void)Io_write(&io[1]); /* second chance */
}
if (io[1].error != 0) { return oserr(io[1].fn, io[1].error); }
if (io[1].bufuse == t) { break; } /* all writes failed! */
}
io[1].bufuse = 0; /* reset after hard seek */
if (io[1].seek > 0) { /* hard seeking failed */
(void)fprintio(stderr, fmt, io);
return oserr(io[1].fn, errno);
}
for ( ;
count == -1 || count > 0;
count -= (count != -1) /* decrement if counting */
) {
assert(io[0].bufuse == 0);
{ /* read */
long skipping;
size_t t;
/* hack to intentionally get a partial read from Io_read */
if ((skipping = MIN(io[0].seek, io[0].bs)) > 0) {
io[0].bufuse = io[0].bs - (size_t)skipping;
}
if(!noerror)
count = 1;
if(align >= 0)
Io_bufrpad(&ep[0], align);
}else
++ep[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)
continue; /* we could write more */
}else
Io_bufxfer(&ep[1], &ep[0], MIN(ep[0].bufuse, ep[1].bs));
t = io[0].bufuse;
if (Io_read(&io[0])->bufuse == t && !retry && io[0].error == 0) {
(void)Io_read(&io[0]); /* second chance */
}
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 */
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();
assert(io[0].bufuse >= t);
if (io[0].bufuse == t) { break; } /* that's all she wrote */
if (/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs) {
(void)fprintf(stderr, "%s: Partial read:\n\t", program_name);
(void)fprintio(stderr, fmt, io);
if (!retry) { count = 1; }
if (align >= 0) {
/* fill the rest of the ibuf with padding */
(void)memset(
&(io[0].buf)[io[0].bufuse],
align,
io[0].bs - io[0].bufuse
);
io->bufuse = io->bs;
}
if(!noerror)
count = 1;
}else if(ep[1].bufuse == 0 && c < ep[1].bs)
++ep[1].prec;
else
++ep[1].rec;
}while(ep[0].bufuse > 0);
}while(count == 0 || --count > 0);
}
output();
terminate(ep);
if (skipping > 0) {
io[0].seek -= skipping;
io[0].bufuse = 0;
count += (count != -1); /* increment if counting */
continue;
}
}
assert(io[0].bufuse > 0);
while (io[0].bufuse > 0) { /* write */
if (io[0].bs <= io[1].bs) {
int n;
(void)memcpy( /* saturate obuf */
io[1].buf, io[0].buf,
(io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs)))
);
/* permute the copied units out of ibuf */
(void)memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n));
} else /* if(io[0].bs > io[1].bs) */ {
int n;
/* drain what we can from ibuf */
(void)memcpy(
&(io[1].buf)[io[1].bufuse], io[0].buf,
(n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse))
);
io[1].bufuse += n;
/* permute out the copied units */
(void)memmove(io[0].buf, &(io[0].buf)[n], io[0].bs - n);
io[0].bufuse -= n;
if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1) {
continue; /* obuf not saturated - we could write more */
}
}
{ /* writes actually happen, or die */
size_t t;
t = io[1].bufuse;
if (Io_write(&io[1])->bufuse == t
&& !retry
&& io[1].error == 0) {
(void)Io_write(&io[1]); /* second chance */
}
assert(io[1].error == 0 || io[1].bufuse == t);
/* if the Io_writes errored, bufuse wouldn't have changed, and
* the error will be reported at the end of the read/write
* loop */
assert(io[1].bufuse <= t);
if (io[1].bufuse == t) { /* no more love */
count = 1;
break;
}
}
if (0 < io[1].bufuse /* && io[1].bufuse < t */) {
(void)fprintf(stderr, "%s: Partial write:\n\t", program_name);
(void)fprintio(stderr, fmt, io);
if(!retry) { count = 1; }
}
}
}
(void)fprintio(stderr, fmt, io);
for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
if (io[i].error) { return oserr(io[i].fn, io[i].error); }
}
return EX_OK;
}

View File

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

91
src/fileis.rs Normal file
View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{
env::args,
fs::metadata,
os::unix::fs::{ FileTypeExt, MetadataExt },
process::ExitCode,
};
extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::GetOpt;
use strerror::StrError;
use sysexits::EX_USAGE;
const OPTS: &str = "bcdefgkprsuwxLS";
fn usage(argv0: &str) -> ExitCode {
eprintln!("Usage: {} [-{}] file...", argv0, OPTS);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let mut sel = String::with_capacity(OPTS.len()); // selected options
let mut optind: usize = 1; // argv[0]
while let Some(opt) = argv.getopt(OPTS) {
if let Ok(optchr) = opt.opt() { sel.push_str(optchr); }
else { return usage(&argv[0]); }
optind = opt.ind();
}
if optind == argv.len() { return usage(&argv[0]); }
for arg in argv.iter().skip(optind) {
let fmeta = match metadata(arg) {
Ok(m) => m,
Err(e) => { // no perms or nonexistent
eprintln!("{}: {}: {}", argv[0], arg, e.strerror());
return ExitCode::FAILURE;
},
};
let fmode = fmeta.mode();
let ftype = fmeta.file_type();
for selection in sel.chars() { // run all selected tests
match selection {
'b' if ftype.is_block_device() => (),
'c' if ftype.is_char_device() => (),
'e' => (), // exists or metadata would have errored
'd' if fmeta.is_dir() => (),
'f' if fmeta.is_file() => (),
'g' if fmode & 0o2000 /* S_ISGID */ != 0 => (), // setgid
'k' if fmode & 0o1000 /* S_ISVTX */ != 0 => (), // setvtx
'p' if ftype.is_fifo() => (),
'r' if fmode & 0o0400 /* S_IRUSR */ != 0 => (), // read access
'u' if fmode & 0o4000 /* S_ISUID */ != 0 => (), // setuid
'w' if fmode & 0o0200 /* S_IWUSR */ != 0 => (), // write access
'x' if fmode & 0o0100 /* S_IXUSR */ != 0 => (), // exec access
'L' if fmeta.is_symlink() => (),
'S' if ftype.is_socket() => (),
_ => { return ExitCode::FAILURE; }
}
}
}
ExitCode::SUCCESS
}

View File

@ -18,103 +18,150 @@
use std::{
env::args,
io::{ Read, stdin, stdout, Write },
process::{ Command, exit, Stdio },
io::{ Error, Read, Write, stdin, stdout },
process::{ Command, ExitCode, Stdio, exit },
};
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:");
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
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]; }
fn err(argv0: &String, e: Error) {
eprintln!("{}: {}", argv0, e.strerror());
}
fn usage(argv0: &String) -> u8 {
eprintln!("Usage: {} [-d delimiter] index command [args...]", argv0);
EX_USAGE
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut d = '\u{1E}'.to_string(); /* ASCII record separator */
let mut optind = 1;
#[cfg(target_os="openbsd")] {
let promises = Promises::new("exec proc stdio unveil");
if let Err(e) = pledge(Some(promises), None) {
err(&argv[0], e);
return ExitCode::from(EX_OSERR);
}
if let Err(e) = unveil(None, None) {
err(&argv[0], e);
return ExitCode::from(EX_OSERR);
}
}
if argv.len() == 1 { return ExitCode::from(usage(&argv[0])); }
while let Some(opt) = argv.getopt("d:") {
match opt.opt() {
Ok("d") => {
/* delimiter */
d = opt.arg().unwrap();
optind = opt.ind();
},
_ => {},
_ => {
return ExitCode::from(usage(&argv[0]));
}
};
}
let index_arg = arg_parser.index();
let command_arg = arg_parser.index() + 1;
argv.get(command_arg).unwrap_or_else(|| {
eprintln!("Usage: {} [-d delimiter] index command [args...]", argv[0]);
exit(EX_USAGE);
});
let index = argv[index_arg].parse::<usize>().unwrap_or_else(|e| {
/* parse the specified index as a number we can use */
let index = argv[optind].parse::<usize>().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[1], e);
exit(EX_DATAERR);
exit(EX_DATAERR.into());
});
let mut buf = String::new();
let _ = stdin().read_to_string(&mut buf);
let mut fields = buf.split(d).collect::<Vec<&str>>();
/* index of the argv[0] for the operator command */
let command_arg = optind as usize + 1;
let opts = argv
/* argv[0] of the operator command */
let operator = argv.get(command_arg).unwrap_or_else(|| {
exit(usage(&argv[0]).into());
});
/* read entire standard input into memory */
let mut buf = String::new();
if let Err(e) = stdin().read_to_string(&mut buf) {
err(&argv[0], e);
exit(EX_IOERR.into());
};
/* split the buffer by the delimiter (by default, '\u{1E}') */
let mut fields = buf.split(&d).collect::<Vec<&str>>();
/* collect arguments for the operator command */
let command_args = argv
.iter()
.clone()
.skip(command_arg + 1)
.skip(command_arg + 1) /* skip the command name */
.collect::<Vec<&String>>();
let mut spawned = Command::new(argv.get(command_arg).unwrap())
.args(opts)
/* spawn the command to operate on the field */
let mut spawned = Command::new(operator)
.args(command_args) /* spawn with the specified arguments */
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stdout(Stdio::piped()) /* piped stdout to handle output ourselves */
.spawn()
.unwrap_or_else( |e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_UNAVAILABLE);
err(&argv[0], e);
exit(EX_UNAVAILABLE.into());
});
/* get field we want to pipe into spawned program */
let field = fields.get(index).unwrap_or_else(|| {
eprintln!(
"{}: {}: No such index in input",
argv[0],
index.to_string(),
);
exit(EX_DATAERR);
eprintln!("{}: {}: no such index in input", argv[0], index);
exit(EX_DATAERR.into());
});
/* get the stdin of the newly spawned program and feed it the field val */
if let Some(mut child_stdin) = spawned.stdin.take() {
let _ = child_stdin.write_all(field.as_bytes());
drop(child_stdin);
drop(child_stdin); /* stay safe! drop your children! */
}
let output = spawned.wait_with_output().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_IOERR);
err(&argv[0], e);
exit(EX_IOERR.into());
});
/* get the output with which the original field will be replaced */
let mut replace = output.stdout.clone();
if replace.pop() != Some(b'\n') { replace = output.stdout; }
/* pop trailing newline out if the input did not contain it */
if fields[index].chars().last() != Some('\n') /* no newline */
&& replace.pop() != Some(b'\n') { /* pop last char of replacement */
/* restore replacement to original command output if popped char was not
* a newline */
replace = output.stdout;
}
/* convert the output of the program to UTF-8 */
let new_field = String::from_utf8(replace).unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e);
exit(EX_IOERR);
eprintln!("{}: {}", argv[0], e);
exit(EX_IOERR.into());
});
/* store the new field in the old fields vector */
fields[index] = &new_field;
/* fop it */
stdout().write_all(
fields.join(&d.to_string()).as_bytes()
).unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
err(&argv[0], e);
exit(EX_IOERR.into());
});
ExitCode::SUCCESS
}

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

@ -19,7 +19,7 @@
use std::{
cmp::Ordering,
env::args,
io::{ stdin, stdout, Write },
io::{ Write, stdin, stdout },
process::{ ExitCode, exit },
};
@ -27,42 +27,61 @@ extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE };
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
/* list of SI prefixes */
const LIST: [(u32, &str); 10] = [
(3, "k"),
(6, "M"),
(9, "G"),
(12, "T"),
(15, "P"),
(18, "E"),
(21, "Z"),
(24, "Y"),
(27, "R"),
(30, "Q")
(3, "k"), /* kilo */
(6, "M"), /* mega */
(9, "G"), /* giga */
(12, "T"), /* tera */
(15, "P"), /* peta */
(18, "E"), /* exa */
(21, "Z"), /* zetta */
(24, "Y"), /* yotta */
(27, "R"), /* ronna */
(30, "Q"), /* quetta */
];
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
fn err(argv0: &String, message: String, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, message);
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
let mut out = (input as f64, (0_u32, ""));
if input < 1000 { return Ok(out); }
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {}", argv0);
ExitCode::from(EX_USAGE)
}
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
/* preserve decimal places in output by casting to a float */
let mut out = (input as f64, (0_u32, ""));
if input < 1000 { return Ok(out); } /* too low to convert */
for (n, p) in LIST {
let c = match 10_u128.checked_pow(n) {
Some(c) => c,
None => {
None => { /* too big for the laws of computing :( */
return Err(format!("10^{}: Integer overflow", n.to_string()));
},
};
match c.cmp(&input) {
Ordering::Less => {
Ordering::Less => { /* c < input */
/* the program will keep assigning out every loop until either
* the list runs out of higher prefix bases or the input is
* greater than the prefix base */
out = (input as f64 / c as f64, (n, p));
},
Ordering::Equal => {
Ordering::Equal => { /* c == input */
return Ok((input as f64 / c as f64, (n, p)));
},
Ordering::Greater => {},
Ordering::Greater => {}, /* c > input */
};
}
@ -71,38 +90,49 @@ fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
if let Some(_) = argv.get(1) { return usage(&argv[0]); }
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e.strerror(), Some(EX_OSERR));
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), Some(EX_OSERR));
}
}
let mut buf = String::new();
while let Ok(_) = stdin().read_line(&mut buf) {
if buf.is_empty() { return ExitCode::SUCCESS; }
let n: u128 = match buf.trim().parse() {
Ok(f) => {
buf.clear();
f
},
Err(err) => {
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_DATAERR as u8);
},
};
if let Err(e) = stdin().read_line(&mut buf) {
return err(&argv[0], e.strerror(), Some(EX_IOERR));
}
let (number, prefix) = match convert(n) {
Ok(x) => x,
Err(err) => {
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_SOFTWARE as u8);
},
};
if buf.is_empty() { return ExitCode::SUCCESS; }
let si_prefix = format!("{}B", prefix.1);
let n: u128 = match buf.trim().parse() {
Ok(f) => {
buf.clear();
f
},
Err(e) => return err(&argv[0], e.to_string(), Some(EX_DATAERR)),
};
let out = ((number * 10.0).round() / 10.0).to_string();
let (number, prefix) = convert(n).unwrap_or_else(|e| {
let _ = err(&argv[0], e.to_string(), None);
exit(EX_SOFTWARE.into());
});
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes())
.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
});
let si_prefix = prefix.1.to_owned() + "B";
/* round output number to one decimal place */
let rounded = (number * 10.0).round() / 10.0;
let out = rounded.to_string() + " " + &si_prefix + &'\n'.to_string();
if let Err(e) = stdout().write_all(out.as_bytes()) {
return err(&argv[0], e.strerror(), Some(EX_IOERR));
}
ExitCode::SUCCESS

View File

@ -1,82 +0,0 @@
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <errno.h> /* errno */
#include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* strtol(3), size_t, EXIT_FAILURE */
#include <unistd.h> /* getopt(3), optind */
#include <sysexits.h>
/* 0b00? */ /* Equal | -e | 0b001 | 1 */
#define EQUAL 0x01 /* Greater | -g | 0b010 | 2 */
/* 0b0?0 */ /* Greater or Equal | -ge | 0b011 | 3 */
#define GREATER 0x02 /* Less | -l | 0b100 | 4 */
/* 0b?00 */ /* Less or Equal | -le | 0b101 | 5 */
#define LESS 0x04 /* Inequal (Greater or Less) | -gl | 0b110 | 6 */
static char *program_name = "intcmp";
int main(int argc, char *argv[]){
int c;
size_t i;
unsigned char mode;
int r; /* reference integer */
mode = 0;
if(argc < 3)
goto usage;
while((c = getopt(argc, argv, "egl")) != -1)
switch(c){
case 'e': mode |= EQUAL; break;
case 'g': mode |= GREATER; break;
case 'l': mode |= LESS; break;
default: goto usage;
}
if(optind + 2 /* ref cmp */ > argc){
usage: fprintf(stderr,
"Usage: %s (-eghl) [integer] [integer...]\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;
}
i = optind;
do{ r = c;
c = strtol(argv[i], &argv[i], 10);
if(*argv[i] != '\0' || errno != 0){
fprintf(stderr, "%s: argument #%d: Invalid integer\n",
argv[0], (int)i);
return EX_USAGE;
}
if(i == optind)
continue;
/* rule enforcement; if a mode isn't permitted and the numbers
* correspond to it, return 1 */
if( (!(mode & EQUAL) && r == c)
|| (!(mode & GREATER) && r > c)
|| (!(mode & LESS) && r < c))
return 1;
}while(++i < argc);
return 0;
}

105
src/intcmp.rs Normal file
View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 20232024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{
env::args,
process::ExitCode
};
extern crate getopt;
extern crate sysexits;
use getopt::GetOpt;
use sysexits::{ EX_DATAERR, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] extern crate strerror;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
#[cfg(target_os="openbsd")] use strerror::StrError;
fn err(argv0: &String, e: String, code: u8) -> ExitCode {
eprintln!("{}: {}", argv0, e);
ExitCode::from(code)
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", argv0);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e.strerror(), EX_OSERR);
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), EX_OSERR);
}
}
let mut e = false; /* args can be == */
let mut g = false; /* args can be > */
let mut l = false; /* args can be < */
let mut optind = 0;
if argv.len() < 3 { return usage(&argv[0]); }
while let Some(opt) = argv.getopt("egl") {
match opt.opt() {
Ok("e") => e = true,
Ok("g") => g = true,
Ok("l") => l = true,
_ => return usage(&argv[0]),
}
optind = opt.ind();
}
if !e & !g & !l { return usage(&argv[0]); }
if argv.len() - optind < 2 /* see usage */ { return usage(&argv[0]); }
let mut prev: Option<usize> = None; /* no previous operand */
let mut currn: usize;
for arg in argv.iter().skip(optind) { /* iterate operands */
match arg.parse::<usize>() { /* parse current operand */
Ok(n) => currn = n,
Err(e) => {
let error = arg.to_owned() + ": " + &e.to_string();
return err(&argv[0], error, EX_DATAERR);
}
}
if let Some(prevn) = prev { /* if there was a previous opr., test */
if (!e && prevn == currn)
|| (!g && prevn > currn)
|| (!l && prevn < currn)
{ return ExitCode::FAILURE; }
}
prev = Some(currn); /* there is a previous operand */
}
ExitCode::SUCCESS
}

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);
}
}
}

99
src/libopenbsd.rs Normal file
View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{
ffi::{ CString, c_char },
io::Error,
ptr::null,
};
mod openbsd {
use std::ffi::{ c_char, c_int };
extern "C" {
pub fn pledge(arg1: *const c_char, arg2: *const c_char) -> c_int;
pub fn unveil(arg1: *const c_char, arg2: *const c_char) -> c_int;
pub fn __errno() -> *mut c_int;
}
}
pub struct Promises(*const c_char);
impl Promises {
pub fn new(promises: &str) -> Self {
let p = CString::new(promises).unwrap();
Promises(p.into_raw() as *const c_char)
}
}
impl Default for Promises {
fn default() -> Self { Promises::new("") }
}
pub fn pledge(
promises: Option<Promises>, execpromises: Option<Promises>
) -> Result<(), Error> {
/* From pledge(2):
*
* Passing NULL to promises or execpromises specifies to not change
* the current value. */
let arg1 = promises.unwrap_or(Promises(null())).0;
let arg2 = execpromises.unwrap_or(Promises(null())).0;
unsafe {
match openbsd::pledge(arg1, arg2) {
-1 => Err(Error::from_raw_os_error(*openbsd::__errno())),
0 => Ok(()),
_ => panic!(), /* unreachable */
}
}
}
pub struct UnveilPerms(CString);
impl UnveilPerms {
pub fn new<T: IntoIterator<Item = char>>(permissions: T) -> Self {
let perms = CString::new(
permissions.into_iter().collect::<String>()
).unwrap();
UnveilPerms(perms)
}
}
pub fn unveil(
path: Option<&str>,
permissions: Option<UnveilPerms>,
) -> Result<(), Error> {
let path_c = path.map(CString::new).map(Result::unwrap);
let arg1 = path_c.map(|p| p.into_raw() as *const c_char).unwrap_or(null());
let arg2 = permissions.map(|p| {
p.0.into_raw() as *const c_char
}).unwrap_or(null());
unsafe {
match openbsd::unveil(arg1, arg2) {
-1 => Err(Error::from_raw_os_error(*openbsd::__errno())),
0 => Ok(()),
_ => panic!(), /* unreachable */
}
}
}

224
src/mm.c
View File

@ -1,224 +0,0 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <errno.h> /* errno */
#include <signal.h> /* signal(2), SIG_ERR, SIG_IGN, SIGINT */
#include <stdio.h> /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3),
* setvbuf(3), size_t, _IONBF, NULL */
#include <stdlib.h> /* free(3), realloc(3) */
#include <string.h> /* strcmp(3), strerror(3) */
#include <unistd.h> /* getopt(3) */
#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \
|| !defined EX_USAGE
# include <sysexits.h>
#endif
extern int errno;
/* This structure is how open files are tracked. */
struct Files{
size_t a; /* allocation */
size_t s; /* used size */
char *mode; /* file opening mode */
char **names; /* file names */
FILE **files; /* file pointers */
};
/* How much to grow the allocation when it's saturated. */
#ifndef ALLOC_INCREMENT
# define ALLOC_INCREMENT 1
#endif
/* How much to grow the allocation at program start. */
#ifndef ALLOC_INITIAL
# define ALLOC_INITIAL 10
#endif
/* pre-allocated strings */
static char *program_name = "<no argv[0]>";
static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>";
static char *stderr_name = "<stderr>";
static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} };
static char *wharsh = "wb";
/* Adds the open FILE pointer for the file at the path s to the files struct,
* returning the FILE if successful and NULL if not, allocating more memory in
* the files buffers as needed. */
static FILE *
Files_append(struct Files *files, FILE *file, char *name){
if(file == NULL || (files->s == files->a
&& ((files->files = realloc(files->files,
(files->a += (files->a == 0)
? ALLOC_INITIAL
: ALLOC_INCREMENT)
* sizeof *(files->files))) == NULL
|| (files->names = realloc(files->names,
files->a * sizeof *(files->names))) == NULL)))
return NULL;
files->names[files->s] = name;
return files->files[files->s++] = file;
}
/* Opens the file at the path p and puts it in the files struct, returning NULL
* if either the opening or the placement of the open FILE pointer fail. */
#define Files_open(files, p) \
Files_append((files), fopen((p), (files)->mode), (p))
/* Prints a diagnostic message based on errno and returns an exit status
* appropriate for an OS error. */
static int
oserr(char *s, char *r){
fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno));
return EX_OSERR;
}
/* Hijacks i and j from main and destructs the files[2] struct used by main by
* closing its files and freeing its files and names arrays, returning retval
* from main. */
#define terminate \
for(i = 0; i < 2; ++i){ \
for(j = 0; j < files[i].s; ++j) \
if(files[i].files[j] != stdin \
&& files[i].files[j] != stdout \
&& files[i].files[j] != stderr) \
fclose(files[i].files[j]); \
free(files[i].files); \
free(files[i].names); \
} \
return retval
int main(int argc, char *argv[]){
int c;
struct Files files[2]; /* {read, write} */
size_t i;
size_t j;
size_t k; /* loop index but also unbuffer status */
int retval;
/* Initializes the files structs with their default values, standard
* input and standard output. If an input or an output is specified
* these initial values will be overwritten, so to, say, use mm(1)
* equivalently to tee(1p), -o - will need to be specified before
* additional files to ensure standard output is still written. */
for(i = 0; i < 2; ++i){
files[i].a = 0;
files[i].s = 0;
files[i].mode = fmode[i];
files[i].files = NULL;
files[i].names = NULL;
Files_append(&files[i], i == 0 ? stdin : stdout,
i == 0 ? stdin_name : stdout_name);
files[i].s = 0;
}
k = 0;
if(argc > 0)
program_name = argv[0];
if(argc > 1)
while((c = getopt(argc, argv, "aehi:no:u")) != -1)
switch(c){
case 'a': /* "rb+" -> "ab" */
files[1].mode[0] = 'a';
files[1].mode[2] = '\0';
break;
case 'e':
if(Files_append(&files[1], stderr, stderr_name) != NULL)
break;
retval = oserr(argv[0], "-e");
terminate;
case 'i':
if((strcmp(optarg, "-") == 0 && Files_append(&files[0],
stdin, stdin_name) != NULL)
|| Files_open(&files[0], optarg) != NULL)
break;
retval = oserr(argv[0], optarg);
terminate;
case 'o':
if((strcmp(optarg, "-") == 0 && Files_append(&files[1],
stdout, stdout_name) != NULL)
|| Files_open(&files[1], optarg) != NULL)
break;
/* does not exist, so try to create it */
if(errno == ENOENT){
files[1].mode = wharsh;
if(Files_open(&files[1], optarg) != NULL){
files[1].mode = fmode[1];
break;
}
}
retval = oserr(argv[0], optarg);
terminate;
case 'n':
if(signal(SIGINT, SIG_IGN) != SIG_ERR)
break;
retval = oserr(argv[0], "-n");
terminate;
case 'u':
k = 1;
break;
default:
fprintf(stderr, "Usage: %s (-aenu) (-i [input])..."
" (-o [output])...\n", argv[0]);
retval = EX_USAGE;
terminate;
}
files[0].s += files[0].s == 0;
files[1].s += files[1].s == 0;
/* Unbuffer files. */
if(k){
for(i = 0;
i < files[0].s;
setvbuf(files[0].files[i++], NULL, _IONBF, 0));
for(i = 0;
i < files[1].s;
setvbuf(files[1].files[i++], NULL, _IONBF, 0));
}
retval = EX_OK;
/* Actual program loop. */
for(i = 0; i < files[0].s; ++i) /* iterate ins */
while((c = getc(files[0].files[i])) != EOF) /* iterate chars */
for(j = 0; j < files[1].s; ++j) /* iterate outs */
if(putc(c, files[1].files[j]) == EOF){
/* notebook's full */
retval = EX_IOERR;
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
if(fclose(files[1].files[j]) == EOF)
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
/* massage out the tense muscle */
for(k = j--; k < files[1].s - 1; ++k){
files[1].files[k] = files[1].files[k+1];
files[1].names[k] = files[1].names[k+1];
}
if(--files[1].s == 0)
terminate;
}
terminate;
}

231
src/mm.rs Normal file
View File

@ -0,0 +1,231 @@
/*
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{
env::args,
fs::File,
io::{ Error, BufWriter, Read, Write, stderr, stdin, stdout },
os::fd::{ AsRawFd, FromRawFd },
process::{ ExitCode, exit},
};
extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::GetOpt;
use strerror::StrError;
use sysexits::{ EX_IOERR, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")]
use openbsd::{
Promises,
UnveilPerms,
pledge,
unveil,
};
use ArgMode::*;
enum ArgMode { In, Out }
fn err(argv0: &String, e: Error, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {} [-aetu] [-i input] [-o output]", argv0);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("cpath rpath stdio unveil wpath");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
let mut a = false; /* append to the file */
let mut e = false; /* use stderr as an output */
let mut t = true; /* do not truncate the file before writing */
let mut u = false; /* unbuffer i/o */
let mut ins = Vec::new(); /* initial input file path vector */
let mut outs = Vec::new(); /* initial output file path vector */
let mut mode: Option<ArgMode> = None; /* mode set by last-used option */
let mut optind = 0;
while let Some(opt) = argv.getopt("aei:o:tu") {
match opt.opt() {
Ok("a") => a = true,
Ok("e") => e = true,
Ok("u") => u = true,
Ok("t") => t = false,
Ok("i") => { /* add inputs */
let input = opt.arg().unwrap();
ins.push(input);
mode = Some(In); /* latest argument == -i */
},
Ok("o") => { /* add output */
let output = opt.arg().unwrap();
outs.push(output);
mode = Some(Out); /* latest argument == -o */
},
Err(_) | Ok(_) => {
return usage(&argv[0]);
},
};
optind = opt.ind();
}
let remaining = argv.iter().skip(optind);
/* check the last flag specified */
if let Some(m) = mode {
for arg in remaining {
/* move the subsequent arguments to the list of inputs or outputs */
match m {
In => ins.push(arg.to_string()),
Out => outs.push(arg.to_string()),
};
}
}
#[cfg(target_os="openbsd")] {
for input in &ins {
let perms = UnveilPerms::new(['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
for output in &outs {
let perms = UnveilPerms::new(['c', 'w']);
if let Err(e) = unveil(Some(&output), Some(perms)) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
if ins.is_empty() && outs.is_empty() && argv.len() > optind {
return usage(&argv[0]);
}
/* use stdin if no inputs are specified */
if ins.is_empty() { ins.push("-".to_string()); }
/* use stdout if no outputs are specified */
if outs.is_empty() && !e { outs.push("-".to_string()); }
/* map all path strings to files */
let inputs = ins.iter().map(|file| {
/* if a file is “-”, it is stdin */
if *file == "-" {
/* portable way to access stdin as a file */
return unsafe { File::from_raw_fd(stdin().as_raw_fd()) };
}
match File::open(file) {
Ok(f) => f,
Err(e) => {
let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR.into());
},
}
}).collect::<Vec<_>>();
/* map all path strings to files */
let mut outputs = outs.iter().map(|file| {
/* of a file is “-”, it is stdout */
if *file == "-" {
/* portable way to access stdout as a file */
return unsafe { File::from_raw_fd(stdout().as_raw_fd()) };
}
let options = File::options()
/* dont truncate if -t is specified, append if -a is specified */
.truncate(t)
.append(a)
/* enable the ability to create and write to files */
.create(true)
.write(true)
/* finally, open the file! */
.open(file);
match options {
Ok(f) => return f,
Err(e) => {
let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR.into());
},
};
}).collect::<Vec<_>>();
/* if -e is specified, use stderr */
if e {
/* portable way to access stderr as a file */
outputs.push(unsafe { File::from_raw_fd(stderr().as_raw_fd()) });
}
let mut outputs = outputs.iter().map(|o| {
if u {
/* unbuffered writing through a buffer of capacity 0 */
BufWriter::with_capacity(0, o)
} else {
/* theoretically buffered writing */
BufWriter::new(o)
}
}).collect::<Vec<_>>();
for file in inputs {
for byte in file.bytes().map(|b| {
b.unwrap_or_else(|e| {
let _ = err(&argv[0], e, None);
exit(EX_IOERR.into());
})
}) {
for out in &mut outputs {
if let Err(e) = out.write(&[byte]) {
return err(&argv[0], e, Some(EX_IOERR));
}
if u {
/* immediately flush the output for -u */
if let Err(e) = out.flush() {
return err(&argv[0], e, Some(EX_IOERR));
}
}
}
}
}
ExitCode::SUCCESS
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
@ -16,46 +17,72 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <stdio.h> /* fprintf(3), fputs(3), getc(3), putc(3), stdin, stdout,
* EOF */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <unistd.h> /* getopt(3) */
#include <sysexits.h>
#include <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin,
* stdout, EOF */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */
#include <unistd.h> /* NULL, getopt(3), pledge(2), unveil(2) */
int main(int argc, char *argv[]){
char *program_name = "npc";
static int
ioerr(char *argv0) {
perror(argv0);
return EX_IOERR;
}
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-et]\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
int c;
char showend;
char showtab;
char showend = 0; /* print a dollar sign before each newline */
char showtab = 0; /* prints tab characters in caret notation */
showend = 0;
showtab = 0;
#ifdef __OpenBSD__
if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL)) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}
#endif
if(argc > 0)
while((c = getopt(argc, argv, "et")) != -1)
switch(c){
case 'e': showend = 1; break;
case 't': showtab = 1; break;
default: goto usage;
if (argc > 0) {
program_name = argv[0];
while ((c = getopt(argc, argv, "et")) != -1) {
switch (c){
case 'e': showend = 1; break;
case 't': showtab = 1; break;
default: return usage(program_name);
}
if(argc > optind){
usage: fprintf(stderr, "Usage: %s (-eht)\n", argv[0]);
return EX_USAGE;
}
}
while((c = getc(stdin)) != EOF){
if((c & 0x80) != 0)
fputs("M-", stdout);
switch(c ^ 0x80 /* 0b 1000 0000 */){
case 0x7f: fputs("^?", stdout);
if (argc > optind) { return usage(program_name); }
while ((c = getc(stdin)) != EOF) {
if ((c & 0x80) != 0 && fputs("M-", stdout) == EOF) {
return ioerr(argv[0]);
}
switch (c ^ 0x80 /* 0b 1000 0000 */) {
case 0x7f: /* ASCII DEL (127d) */
if(fputs("^?", stdout) == EOF) { return ioerr(argv[0]); }
break;
case '\n': if(showend)
putc('$', stdout);
default:
if(c >= ' ' || c == '\n' || (!showtab && c == '\t'))
putc(c, stdout);
else
fprintf(stdout, "^%c", c + '@');
case '\n':
if (showend && fputc('$', stdout) == EOF) {
return ioerr(argv[0]);
}
default:
if (c >= ' ' || c == '\n' || (!showtab && c == '\t')) {
if (fputc(c, stdout) == EOF) { return ioerr(argv[0]); }
} else if (fprintf(stdout, "^%c", c + '@') < 0) {
return ioerr(argv[0]);
}
}
}

147
src/peek.c Normal file
View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 20232024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <signal.h> /* sigaction(2), signal(2), struct sigaction, SIGINT */
#include <stdbool.h> /* bool */
#include <stdio.h> /* fprintf(3), fgetc(3), perror(3), fputc(3), stderr, stdin,
* stdout, EOF, NULL */
#include <stdlib.h> /* exit(3), EXIT_FAILURE */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_USAGE */
#include <termios.h> /* tcgetattr(3), tcsetattr(3), struct termios, ECHO */
#include <unistd.h> /* getopt(3), isatty(3), pledge(2), unveil(2),
* STDIN_FILENO */
char *program_name = "peek";
/* Restores terminal echo; otherwise when a user ^Cs the terminal would
* continue to not display typed text. If sig isn't zero, this will terminate
* the program. */
static void
restore_echo(int sig) {
static struct termios t;
/* Failure isn't reported because this is the termination routine anyway;
* errors will be obvious. */
if (tcgetattr(STDIN_FILENO, &t) == 0) {
t.c_lflag |= ECHO;
(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);
}
if (sig != 0) { exit(EXIT_FAILURE); } /* Terminated by signal. */
return;
}
static int
ioerr(char *argv0) {
perror(argv0);
restore_echo(0);
return EX_IOERR;
}
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-i]\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]){
bool is_term; /* Is stdin a terminal? */
bool must_be_term = 1; /* Must it be? */
#ifdef __OpenBSD__
if (pledge("stdio tty unveil", "") != 0 || unveil(NULL, NULL) != 0) {
/* This isn't fatal; these return values could be cast to void just as
* easily. */
(void)perror(argv[0] == NULL ? argv[0] : program_name);
}
#endif
is_term = isatty(STDIN_FILENO);
if (argc > 0) { /* option parsing */
int c;
program_name = argv[0];
while ((c = getopt(argc, argv, "i")) != -1) {
switch (c) {
case 'i': must_be_term = 0; break;
default: return usage(argv[0]);
}
}
if (argc > optind) { return usage(argv[0]); }
}
if (!is_term && must_be_term) {
(void)fprintf(
stderr,
"%s: Must be run in a terminal (specify -i to skip this check)\n",
argv[0]
);
return EX_USAGE;
}
if (is_term) {
{ /* Install signal handler */
/* There isn't a difference in functionality between the signal(2)
* and sigaction(2) methods. sigaction(2) is vastly preferred for
* portability but some older systems only have signal(2). */
/* Errors aren't terminating because the worst that happens is some
* terminal phooeyness if things go awry. */
#if defined _POSIX_C_SOURCE
struct sigaction act = { 0 };
act.sa_handler = restore_echo;
if (sigaction(SIGINT, &act, NULL) != 0) { perror(program_name); }
#else
if (signal(SIGINT, restore_echo) == SIG_ERR) {
perror(program_name);
}
#endif
}
{ /* Banish terminal echo */
/* This terminates when it fails because it's the whole point of
* the program. */
struct termios t;
if (tcgetattr(STDIN_FILENO, &t) != 0) {
return ioerr(program_name);
}
t.c_lflag ^= ECHO;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t) != 0) {
return ioerr(program_name);
}
}
}
{ /* Input loop */
int c;
while ((c = fgetc(stdin)) != EOF) {
if (fputc(c, stdout) == EOF) { return ioerr(program_name); }
}
}
if (is_term) { restore_echo(0); }
return EX_OK;
}

View File

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

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
@ -16,89 +17,100 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <assert.h> /* assert(3) */
#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) */
#include <unistd.h> /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */
#include <sysexits.h> /* EX_OSERR, EX_USAGE */
#include <unistd.h> /* access(3), getopt(3), pledge(2), unveil(2), 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)];
static char *program_name = "scrut";
char *program_name = "scrut";
#define OPTS "bcdefgkprsuwxLS"
/* this is an array so main:sel's size can be known at compile time */
static char opts[] = OPTS;
int main(int argc, char *argv[]){
struct stat buf;
int c;
size_t i;
char *p;
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-" OPTS "] file...\n", argv0);
if(argc < 2)
goto usage;
return EX_USAGE;
}
memset(ops, '\0', sizeof ops);
while((c = getopt(argc, argv, args)) != -1)
if((p = strchr(args, c)) == NULL)
goto usage;
else
ops[p - args] = c;
/* straighten out ops */
for(i = 0, p = ops; i < (sizeof ops) / (sizeof *ops); ++i)
if(ops[i] != '\0'){
*p = ops[i];
if(&ops[i] != p++)
ops[i] = '\0';
int main(int argc, char *argv[]) {
char sel[(sizeof opts) / (sizeof *opts)];
program_name = argv[0] == NULL ? program_name : argv[0];
#ifdef __OpenBSD__
if (pledge("rpath stdio unveil", "") == -1) {
perror(program_name);
return EX_OSERR;
}
#endif
if (argc < 2) { return usage(program_name); }
{ /* option parsing */
char *p;
memset(sel, '\0', sizeof sel);
for (int c; (c = getopt(argc, argv, opts)) != -1;) {
if ((p = strchr(opts, c)) == NULL) { return usage(argv[0]); }
else {
assert(p - opts < sizeof sel / sizeof *sel); /* bounds check */
sel[p - opts] = c;
}
}
if(optind == argc)
goto usage;
/* straighten out selections; permute out nulls */
p = sel;
for (size_t i = 0; i < (sizeof sel) / (sizeof *sel); ++i) {
if (sel[i] != '\0') {
*p = sel[i];
if (&sel[i] != p++) { sel[i] = '\0'; }
}
}
}
argv += optind;
do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1)
return 1; /* doesn't exist or isn't stattable */
if (optind == argc) { return usage(argv[0]); }
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",
argv[0] == NULL
? program_name
: argv[0],
args);
for (argv += optind ; *argv != NULL; argv = &argv[1]) {
struct stat buf;
return EX_USAGE;
}else if(
(ops[i] == 'b'
&& !S_ISBLK(buf.st_mode))
|| (ops[i] == 'c'
&& !S_ISCHR(buf.st_mode))
|| (ops[i] == 'd'
&& !S_ISDIR(buf.st_mode))
|| (ops[i] == 'f'
&& !S_ISREG(buf.st_mode))
|| (ops[i] == 'g'
&& !(buf.st_mode & S_ISGID))
|| (ops[i] == 'k'
&& !(buf.st_mode & S_ISVTX))
|| (ops[i] == 'p'
&& !S_ISFIFO(buf.st_mode))
|| (ops[i] == 'r'
&& access(*argv, R_OK) != 0)
|| (ops[i] == 'u'
&& !(buf.st_mode & S_ISUID))
|| (ops[i] == 'w'
&& access(*argv, W_OK) != 0)
|| (ops[i] == 'x'
&& access(*argv, X_OK) != 0)
|| (ops[i] == 'L'
&& !S_ISLNK(buf.st_mode))
|| (ops[i] == 'S'
&& !S_ISSOCK(buf.st_mode)))
return 1;
}while(*++argv != NULL);
#ifdef __OpenBSD__
if (unveil(*argv, "rw") == -1) {
perror(program_name);
return EX_OSERR;
}
#endif
return 0;
if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) {
return EXIT_FAILURE; /* doesn't exist or isn't stattable */
}
for (size_t i = 0; sel[i] != '\0'; ++i) {
if (
(sel[i] == 'b' && !S_ISBLK(buf.st_mode))
|| (sel[i] == 'c' && !S_ISCHR(buf.st_mode))
|| (sel[i] == 'd' && !S_ISDIR(buf.st_mode))
|| (sel[i] == 'e' && 0)
|| (sel[i] == 'f' && !S_ISREG(buf.st_mode))
|| (sel[i] == 'g' && !(buf.st_mode & S_ISGID))
|| (sel[i] == 'k' && !(buf.st_mode & S_ISVTX))
|| (sel[i] == 'p' && !S_ISFIFO(buf.st_mode))
|| (sel[i] == 'r' && access(*argv, R_OK) != 0)
|| (sel[i] == 'u' && !(buf.st_mode & S_ISUID))
|| (sel[i] == 'w' && access(*argv, W_OK) != 0)
|| (sel[i] == 'x' && access(*argv, X_OK) != 0)
|| (sel[i] == 'L' && !S_ISLNK(buf.st_mode))
|| (sel[i] == 'S' && !S_ISSOCK(buf.st_mode))
) { return EXIT_FAILURE; }
}
}
return EXIT_SUCCESS;
}

View File

@ -19,17 +19,21 @@
#include <ctype.h>
#include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3) */
#include <stdlib.h> /* EXIT_FAILURE */
#include <stdio.h> /* fprintf(3), perror(3) */
#include <stdlib.h> /* size_t, EXIT_FAILURE */
#include <string.h> /* strcmp(3) */
#include <sysexits.h>
#include <sysexits.h> /* EX_OSERR, EX_USAGE */
static char *program_name = "str";
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2), unveil(2) */
#endif
char *program_name = "str";
static struct {
char *name;
int (*f)(int);
}ctypes[] = {
} ctypes[] = {
{ "isalnum", isalnum },
{ "isalpha", isalpha },
{ "isblank", isblank },
@ -41,35 +45,52 @@ static struct {
{ "isprint", isprint },
{ "ispunct", ispunct },
{ "isspace", isspace },
{ "isupper", isupper }
{ "isupper", isupper },
{ NULL, NULL } /* marks end */
};
int main(int argc, char *argv[]){
int ctype;
int i;
int r;
if(argc >= 3){
for(ctype = 0; ctype < (sizeof ctypes) / (sizeof *ctypes);
++ctype)
if(strcmp(argv[1], ctypes[ctype].name) == 0)
goto pass;
}
fprintf(stderr, "Usage: %s [type] [string...]\n",
argv[0] == NULL ? program_name : argv[0]);
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s type string...\n", argv0);
return EX_USAGE;
pass: for(argv += 2, r = 1; *argv != NULL; ++argv)
for(i = 0; argv[0][i] != '\0'; ++i)
/* First checks if argv[0][i] is valid ASCII; ctypes(3)
* don't handle non-ASCII.
* This is bad. */
if((unsigned char)argv[0][i] < 0x80 && !ctypes[ctype].f(argv[0][i]))
return 1;
else
r = 0;
return r;
}
int main(int argc, char *argv[]) {
size_t ctype; // selected from ctypes.h; index of ctype
int retval; // initially fail but becomes success on the first valid char
program_name = argv[0] == NULL ? program_name : argv[0];
#ifdef __OpenBSD__
if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL) == -1) {
perror(program_name);
return EX_OSERR;
}
#endif
if (argc < 3) { return usage(program_name); }
for ( /* iterate ctypes */
ctype = 0;
ctypes[ctype].f != NULL /* break at the end of ctypes */
&& strcmp(argv[1], ctypes[ctype].name) != 0; /* break at match */
++ctype
);
if (ctypes[ctype].f == NULL) { return usage(argv[0]); }
/* iterate args */
for (argv += 2, retval = EXIT_FAILURE; *argv != NULL; ++argv) {
for (size_t i = 0; argv[0][i] != '\0'; ++i) { /* iterate arg bytes */
/* First checks if argv[0][i] is valid ASCII; ctypes(3) don't
* handle non-ASCII. This is bad. */
if(
(unsigned char)argv[0][i] < 0x80 // argv[0][i] is ASCII,
&& !ctypes[ctype].f(argv[0][i]) // so use ctypes(3)
) { return EXIT_FAILURE; }
else { retval = EXIT_SUCCESS; }
}
}
return retval;
}

View File

@ -1,24 +1,62 @@
#include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* EXIT_FAILURE */
#include <sysexits.h>
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* 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/.
*/
#include <stdio.h> /* fprintf(3), perror(3), stderr */
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
static char *program_name = "strcmp";
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2), unveil(2) */
#endif
int main(int argc, char *argv[]){
int i;
char *program_name = "strcmp";
int main(int argc, char *argv[]) {
unsigned int i;
#ifdef __OpenBSD__
if (pledge("stdio unveil", "") == -1 || unveil(NULL, NULL) == -1) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}
#endif
if (argc < 3) {
(void)fprintf(
stderr,
"Usage: %s string string...\n",
argv[0] == NULL ? program_name : argv[0]
);
if(argc < 3){
fprintf(stderr, "Usage: %s [string] [string...]\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;
}
for(; *argv[1] != '\0'; ++argv[1])
for(i = 2; i < argc; ++i)
if(*argv[i-1] > *argv[i])
for (; *argv[1] != '\0'; ++argv[1]) {
for (i = 2; i < argc; ++i) {
/* a former string has a greater byte value */
if (*argv[i-1] > *argv[i]) {
return 1;
else if(*argv[i-1] < *argv[i]++)
/* a latter string has a greater byte value */
} else if (*argv[i-1] < *argv[i]++) {
return -1; /* actually 255 */
}
}
}
return 0;
return EX_OK;
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
@ -18,73 +19,89 @@
use std::{
env::args,
io::{ stdin, stdout, Error, ErrorKind, Read, Write },
io::{ stdin, stdout, Error, Read, Write },
process::ExitCode,
vec::Vec
};
extern crate getopt;
use getopt::{ Opt, Parser };
extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
extern crate strerror;
fn oserr(s: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", s, e);
ExitCode::from(EX_OSERR as u8)
use getopt::GetOpt;
use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE };
use strerror::StrError;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
fn err(argv0: &String, e: Error, code: u8) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} (-f) (-w [wordsize])", s);
ExitCode::from(EX_USAGE as u8)
fn usage(s: &String) -> ExitCode {
eprintln!("Usage: {} [-w word_size]", s);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut buf: Vec<u8> = Vec::new();
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e, EX_OSERR);
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e, EX_OSERR);
}
}
let mut buf: Vec<u8> = Vec::new(); // holds the sequence getting swabbed
let mut input = stdin();
let mut output = stdout().lock();
let mut optind: usize = 1; // argv[0]
let mut wordsize: usize = 2; // default; mimics dd(1p) conv=swab
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("w:") {
match opt.opt() {
Ok("w") => {
match opt.arg().unwrap().parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; },
_ => { return usage(&argv[0]); },
}
optind = opt.ind();
},
_ => { return usage(&argv[0]); }
}
}
if optind < argv.len() {
return usage(&argv[0]);
}
buf.resize(wordsize, 0);
loop {
match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8),
Ok(v) if v == wordsize => {
Ok(0) => break ExitCode::from(EX_OK), // read nothing; bye
Ok(v) if v == wordsize => { // read full block; swab
let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) {
break oserr(&argv[0], e)
break err(&argv[0], e, EX_IOERR);
}
},
Ok(v) => {
Ok(v) => { // partial read; partially write
if let Err(e) = output.write(&buf[..v]) {
break oserr(&argv[0], e)
break err(&argv[0], e, EX_IOERR);
}
},
Err(e) if e.kind() == ErrorKind::Interrupted && force => continue,
Err(e) => break oserr(&argv[0], e)
Err(e) => break err(&argv[0], e, EX_OSERR)
}
}
}

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);
});
}

View File

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

25
tests/README Normal file
View File

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

48
tests/dj.mk Executable file
View File

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

18
tests/false.mk Executable file
View File

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

46
tests/fileis.mk Executable file
View File

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

32
tests/fop.mk Executable file
View File

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

32
tests/hru.mk Executable file
View File

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

67
tests/intcmp.mk Executable file
View File

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

40
tests/mm.mk Executable file
View File

@ -0,0 +1,40 @@
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: mm_tests
mm_tests: mm_args mm_help mm_stderr mm_remaining mm_remaining_options
.PHONY: mm_none
mm_none: $(BIN)/mm
test "$$(printf 'meow\n' | $(BIN)/mm)" = meow
.PHONY: mm_args
# mm(1) will error if positional arguments are given without -i or -o
mm_args: $(BIN)/mm
! $(BIN)/mm argument
.PHONY: mm_help
mm_help: $(BIN)/mm
! $(BIN)/mm -h
.PHONY: mm_stderr
# check if stderr is empty upon specifying -e
mm_stderr: $(BIN)/mm
test "$$(printf 'test\n' | $(BIN)/mm -e 2>&1 >/dev/null )" = "test"
.PHONY: mm_remaining
# check to make sure remaining arguments are used
mm_remaining: $(BIN)/mm
test "$$($(BIN)/mm -i README COPYING)" = "$$(cat README COPYING)"
$(BIN)/mm -i README -o /tmp/mm_test0 /tmp/mm_test1
diff /tmp/mm_test0 /tmp/mm_test1
.PHONY: mm_remaining_options
# check to make sure mm -i with trailing arguments interprets -o as one
mm_remaining_options:
! $(BIN)/mm -i README COPYING -o - 2>&1 | cut -d: -f2 \
| xargs test " -o" =

94
tests/npc.mk Executable file
View File

@ -0,0 +1,94 @@
#!/bin/sh
# Copyright (c) 2024 DTB <trinity@trinity.moe>
# Copyright (c) 20242025 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice and this
# notice are preserved. This file is offered as-is, without any warranty.
.PRAGMA: command_comment
.PHONY: npc_tests
npc_tests: npc_help npc_args npc_ascii
.PHONY: npc_help
npc_help: $(BIN)/npc
! $(BIN)/npc -h
.PHONY: npc_args
# arg parsing
npc_args:
$(BIN)/npc -e </dev/null
$(BIN)/npc -t </dev/null
$(BIN)/npc -et </dev/null
! $(BIN)/npc -et 5 </dev/null
.PHONY: npc_ascii
# Test 0x00 to 0x7f in input; in other words, the full 7b ASCII range.
npc_ascii: npc_ascii_controls npc_ascii_uppers # npc_ascii_symbols \
# npc_ascii_lowers
.PHONY: npc_ascii_controls
# (control characters)
npc_ascii_controls:
# The following test prints the bytes 0x00 (inclusive) through 0x20
# (exclusive) and pipes them through npc(1). npc(1) should then replace all
# non-printing, non-space (in the isspace(3p) sense) characters with their
# graphical carat-char counterparts (see the npc(1) man page). The head(1p)
# invocation then strips off everything past the first line (or past the
# first newline byte, 0x0A) and xargs(1p) is used to test(1p) the output
# against the known good answer.
# Immediately before that newline, 0x09 is printed - in ASCII, the
# horizontal tab. If xargs' -I option is used, tr(1p) should used to delete
# that tab. If the tab is left as part of input, OpenBSD's xargs(1)
# implementation has been observed to strip it along with the other
# trailing whitespace (the newline), but Busybox's and GNU's xargs(1)
# implementations have been observed to leave the tab in. All three
# implementations strip off the trailing tab if `-I` is not used. The POSIX
# specification for `-I` is ambiguous as to which behavior is correct.
# This comment is the result of much bewilderment and debugging.
# ASCII 0x00 to 0x0a (before the newline, due to xargs(1p) issues)
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); }' \
| $(BIN)/npc \
| head -n 1 \
| xargs test "^@^A^B^C^D^E^F^G^H" =
# ASCII 0x0a (otherwise the head|tail sequence won't work) to 0x1f
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); print }' \
| $(BIN)/npc \
| head -n 2 \
| tail -n 1 \
| xargs -I out test "^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_"
# This test is broken on Linux and will need closer inspection along with GNU
# xargs(1).
# .PHONY: npc_ascii_symbols
# # ASCII 0x1f to 0x3f (^_ and symbols)
# npc_ascii_symbols:
# # shell quoting olympics
# c="$(awk 'BEGIN{ for (i = 31; i < 64; ++i) printf("%c", i); print }')"
#
# printf '%s\n' "$c" | $(BIN)/npc \
# | sed -e s"/\'/\\\'/g" -e 's/"/\\"/g' \
# | tr -d '\n' \
# | xargs -I out test "^_ !\"#$$%&\'()*+,-./0123456789:;<=>?" = out
.PHONY: npc_ascii_uppers
# ASCII 0x40 to 0x5f (uppercases)
npc_ascii_uppers:
awk 'BEGIN{ for (i = 64; i < 96; ++i) printf("%c", i); print }' \
| $(BIN)/npc \
| sed 's/\\/\\\\/' \
| xargs -I out test @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ = out
# This test is broken and will need closer inspection along with the npc(1)
# source.
# .PHONY: npc_ascii_lowers
# # ASCII 0x60 to 0x7f (lowercases)
# npc_ascii_lowers:
# awk 'BEGIN{ for (i = 96; i < 128; ++i) printf("%c", i); print }' \
# | $(BIN)/npc \
# | xargs -I out test "\`abcdefghijklmnopqrstuvwxyz{|}~^?" = out

24
tests/peek.mk Executable file
View File

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

View File

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

48
tests/rpn.mk Executable file
View File

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

20
tests/str.mk Executable file
View File

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

31
tests/strcmp.mk Executable file
View File

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

22
tests/swab.mk Executable file
View File

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

19
tests/true.mk Executable file
View File

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