Compare commits

...

177 Commits
main ... main

Author SHA1 Message Date
Emma Tebibyte 9093b06166
Merge branch 'manpage-install' 2024-04-27 12:58:50 -06:00
dtb 6132c9bf47
Merge branch 'main' into scrut-sysexits 2024-04-26 19:37:34 -06:00
dtb 919b171400
scrut(1): conditionally include non-POSIX sysexits.h 2024-04-26 19:35:33 -06:00
dtb 8e38db92c7
scrut(1): update copyright header 2024-04-26 19:33:45 -06:00
dtb adc9dbded5
scrut(1): changed return status 1 to EXIT_FAILURE 2024-04-26 19:30:33 -06:00
Emma Tebibyte 488351da39
scrut(1): changed return status 0 to EXIT_SUCCESS 2024-04-25 16:46:05 -06:00
Emma Tebibyte d8b54fdbf5
Makefile: fixes erroneous whitespace 2024-04-25 12:43:31 -06:00
Emma Tebibyte f6aac60aee
Makefile: fixed manpage install location 2024-04-25 12:40:20 -06:00
dtb 61382c34d9
mm(1): error out when given positional arguments 2024-03-31 22:54:03 -06:00
Emma Tebibyte 8d9ac33566
Merge branch 'strerror' 2024-03-29 19:23:27 -06:00
Emma Tebibyte b503d97625
Makefile: directory specification changes 2024-03-29 19:22:55 -06:00
Aaditya Aryal b562898a74
copyright 2024-03-27 09:21:19 +05:45
Aaditya Aryal d05d2fae05 Makefile: add a missing build target for npc(1) 2024-03-26 17:19:21 +05:45
Emma Tebibyte d87b5d0958
hru(1), fop(1): changed to reflect strerror changes 2024-03-18 21:31:25 -06:00
Emma Tebibyte 1891c3e1aa
strerror(3): created strerror() method 2024-03-18 21:30:43 -06:00
Emma Tebibyte 127192185f
Merge branch 'dj-fix' (closes #66) 2024-03-18 21:01:48 -06:00
Emma Tebibyte f81232685a
Merge branch 'mm' 2024-03-18 20:56:06 -06:00
Emma Tebibyte 05b5a4480c
Merge branch 'swab' (closes #22) 2024-03-18 20:53:08 -06:00
Emma Tebibyte b356ac522f
strerror(3): changed panic to 0 2024-03-18 20:45:53 -06:00
Emma Tebibyte b2d56bbc9a
Makefile: even more changes 2024-03-09 22:02:19 -07:00
Emma Tebibyte 2ad7140e1e
Makefile: DESTDIR 2024-03-09 11:53:03 -07:00
Emma Tebibyte 8193e471f0
changes redundant .unwrap_or_else(panic!()) to .unwrap() 2024-03-03 17:26:40 -07:00
Emma Tebibyte c392dbc680
Makefile: better deps; fop(1), hru(1), strerror(3): changed strerror wrapper function name 2024-03-02 10:16:32 -07:00
Emma Tebibyte 898044cd43
Makefile: better macro assignment 2024-03-01 23:51:36 -07:00
Emma Tebibyte ca01ca4074
rpn(1): remove erroneous strerror(3) dependency 2024-03-01 23:14:58 -07:00
Emma Tebibyte 3e6dc5cc46
rpn(1): make error messages consistent with other tools 2024-03-01 23:14:18 -07:00
Emma Tebibyte cf1d16f860
hru(1): changed to use strerror(3) 2024-03-01 23:12:44 -07:00
Emma Tebibyte 8f956d775c
fop(1): changed to use strerror(3) 2024-03-01 23:10:28 -07:00
Emma Tebibyte e81703c6e1
sterror(3): added library for C-like error messages; Makefile: some efficiency changes 2024-03-01 23:04:53 -07:00
dtb 135bf2a8eb
mm(1), mm.1: bring source in line with documentation 2024-02-26 09:11:47 -07:00
dtb d74bc715cf
mm(1): fix full file handling 2024-02-26 09:02:58 -07:00
dtb f14877118d
Makefile: add swab(1) 2024-02-26 08:43:03 -07:00
dtb bbac85daf8
swab(1): add copyright notice 2024-02-26 08:42:06 -07:00
dtb 1e041a52a2
swab.1: add swab(1) man page 2024-02-24 03:21:20 -07:00
dtb e788947fc4
swab(1): add argument parsing 2024-02-24 03:04:05 -07:00
Emma Tebibyte c97201fca9
Merge branch 'contributing-changes' 2024-02-23 23:20:16 -07:00
Emma Tebibyte 06fc461985
CONTRIBUTING: fixed random "utils" 2024-02-23 23:19:28 -07:00
Emma Tebibyte e6b1db3f40
Merge branch 'hru' (closes #33) 2024-02-23 22:15:43 -07:00
Emma Tebibyte ef1643660b
hru(1): fixed lack of newline 2024-02-23 22:11:23 -07:00
Emma Tebibyte b0636b416e
Merge branch 'fop-trim' (closes #54, closes #58) 2024-02-23 22:05:56 -07:00
Emma Tebibyte bd08ef479c
hru(1): error handling tweaks 2024-02-23 21:58:23 -07:00
Emma Tebibyte 3cee3de900
fop(1): committing to Rust error messages 2024-02-23 21:46:50 -07:00
dtb 3f0d95fe8f
swab(1): minimum viable program 2024-02-23 20:49:24 -07:00
dtb 58245c9484
dj(1): remove function pointer hijinks (fixes #66) 2024-02-22 19:27:14 -07:00
dtb 395205d4c6
mm.1: fix case in copyright thingy 2024-02-22 19:14:38 -07:00
Emma Tebibyte 90de1bf9a4
Makefile: RUSTFLAGS not RUSTCFLAGS 2024-02-18 20:03:04 -07:00
Emma Tebibyte 1c2e7ea14b
Merge branch 'scrut-manpage' (fixes #49) 2024-02-18 15:20:13 -07:00
Emma Tebibyte 0d445c71f4
CONTRIBUTING: fixed wording and updated 2024-02-18 15:09:09 -07:00
Emma Tebibyte ee3877b607
hru.1: update manpage to be more clear 2024-02-18 14:59:12 -07:00
Emma Tebibyte 0801d89128
hru(1): fixed kilo prefix 2024-02-18 14:49:09 -07:00
dtb 194b19f94b
mm(1): add 2024-02-17 23:43:22 -07:00
dtb e93745ef28
.editorconfig: remove stale configure config 2024-02-17 23:25:35 -07:00
dtb abb8fdf935
Merge branch 'scrut-fix' 2024-02-17 23:15:07 -07:00
dtb de9f8dc037
scrut.1: update man page based on review (see #62) 2024-02-17 23:12:39 -07:00
Emma Tebibyte 413ee49af4
hru.1: updated manpage with correct SI reference 2024-02-16 21:36:16 -07:00
Emma Tebibyte dac7567405
Merge branch 'readme-changes' (closes #51 & closes #24) 2024-02-16 21:02:41 -07:00
Emma Tebibyte 97382b79fd
README: changed dependency section and update copyright 2024-02-16 21:02:00 -07:00
dtb bee0165074
scrut(1): add man page 2024-02-16 01:40:38 -07:00
dtb 64a1a19bd8
scrut(1): fix buffer overflow in option parsing 2024-02-16 01:13:37 -07:00
Emma Tebibyte cc93389232
README: added cpp(1) as build dep 2024-02-15 17:39:26 -07:00
Emma Tebibyte 361f0ddb8b
hru.1: added manpage for hru(1) 2024-02-14 23:31:54 -07:00
Emma Tebibyte 448211bbe2
fop(1): better newline removal (preserves existing newlines) 2024-02-14 23:05:39 -07:00
Emma Tebibyte 4c663bf9dd
fop(1): closure fix 2024-02-14 22:55:28 -07:00
Emma Tebibyte f8e3013563
fop(1): fixed trimming and handled unwraps (closes #58) 2024-02-14 22:53:45 -07:00
Emma Tebibyte e3a0069180
hru(1): improved SI prefix logic 2024-02-14 00:07:06 -07:00
Emma Tebibyte 1299aefc39
hru(1): fixed overflow 2024-02-13 23:56:01 -07:00
Emma Tebibyte d0205b71da
hru(1): read stdin until EOF 2024-02-13 17:48:25 -07:00
Emma Tebibyte 1ee668aed6
hru(1): made it actually work 2024-02-13 17:32:31 -07:00
Emma Tebibyte 5b364e104e
rpn(1): fixed exponentiation 2024-02-13 16:57:29 -07:00
Emma Tebibyte d45e3410f8
CONTRIBUTING: fixed some small issues 2024-02-07 22:03:08 -07:00
Emma Tebibyte 452a1295e6
CONTRIBUTING: updated and added copyright info 2024-02-07 21:54:46 -07:00
Emma Tebibyte fa686eefb9
hru(1): updated copyright year 2024-02-07 21:42:43 -07:00
Emma Tebibyte 42c5c5642e
README: bullets 2024-02-07 21:38:32 -07:00
Emma Tebibyte c753eeea9c
README: added build information and copyright 2024-02-07 21:27:43 -07:00
Emma Tebibyte be89e72c45
Makefile, hru(1): add hru(1) 2024-02-07 20:58:57 -07:00
Emma Tebibyte d3470233ea
Makefile, .gitignore, tests/cc-compat.sh: brought up-to-date 2024-02-07 20:13:45 -07:00
Emma Tebibyte 3e39739e88
Makefile: added rpn to all 2024-02-07 19:42:11 -07:00
Emma Tebibyte 0f5247e722
merged rpn(1); closes #2 2024-02-07 19:23:51 -07:00
Emma Tebibyte 7fb68a2c7a
rpn.1: fixed DESCRIPTION 2024-02-07 19:17:59 -07:00
Emma Tebibyte b95b198923
rpn(1): typo 2024-02-07 19:12:31 -07:00
Emma Tebibyte af53375ff2
rpn.1: fixed some clunky stuff 2024-02-07 19:11:53 -07:00
Emma Tebibyte 4e040e0021
rpn(1): added alternative representation for powers 2024-02-07 18:51:58 -07:00
Emma Tebibyte d768a257a3
rpn(1): made EvaluationError struct contain exit code 2024-02-06 23:29:43 -07:00
Emma Tebibyte 5b47936477
Makefile: removed configure and made library builds better 2024-02-06 22:21:46 -07:00
Emma Tebibyte 8e0eaeab38
rpn.1: made more better 2024-02-03 17:20:31 -07:00
Emma Tebibyte 6d7522be01
rpn.1: made better 2024-02-02 23:24:45 -07:00
Emma Tebibyte 228a0111c7
rpn(1): fixed Invalid error handling 2024-02-02 21:05:34 -07:00
Emma Tebibyte a30152d783
rpn.1: made more precise 2024-02-02 20:12:12 -07:00
Emma Tebibyte 4993c53080
rpn(1): re-fixed arg parsing 2024-02-02 19:17:00 -07:00
Emma Tebibyte 6f98fdd1d4
rpn(1): fixed expressions on one line 2024-02-02 19:13:53 -07:00
Emma Tebibyte fb2a164507
rpn(1): rewrote parser for the second time 2024-02-02 15:35:36 -07:00
Emma Tebibyte 4cb5e8e2b0
rpn(1): better error handling 2024-02-02 14:16:49 -07:00
Emma Tebibyte 885f167afc
rpn(1): changes to error message 2024-02-02 13:04:57 -07:00
dtb 2ec04c0564
true(1): fix my copyright dates in man page 2024-02-02 06:23:12 -07:00
dtb a39772d7a1
false(1): fix my copyright dates in man page 2024-02-02 06:22:59 -07:00
Emma Tebibyte 258881dbb2
rpn(1): minor change 2024-02-01 23:35:49 -07:00
Emma Tebibyte b35f87bd27
rpn(1): code cleanup 2024-02-01 23:34:20 -07:00
Emma Tebibyte e7ee75b03f
rpn(1): added From trait to CalcType 2024-02-01 23:32:37 -07:00
dtb a90c9b7998
true(1): fix man page POSIX reference 2024-02-01 20:42:28 -07:00
dtb fb929e63f5
strcmp(1): fix man page POSIX reference 2024-02-01 20:42:15 -07:00
dtb 791db2ed6d
str(1): fix man page POSIX reference 2024-02-01 20:41:57 -07:00
dtb 870548b46b
npc(1): fix man page POSIX reference 2024-02-01 20:41:42 -07:00
dtb af0c472173
intcmp(1): fix man page POSIX reference 2024-02-01 20:40:50 -07:00
dtb 6715e0a50f
false(1): fix man page POSIX reference 2024-02-01 20:40:18 -07:00
Emma Tebibyte 0d97f81f49
rpn(1): unicode subtraction, multiplication, division & code cleanup 2024-02-01 13:06:46 -07:00
Emma Tebibyte b68f13acdc
rpn(1): added floor division 2024-02-01 12:38:12 -07:00
Emma Tebibyte 42d268319e
docs/rpn.1: added manpage 2024-02-01 11:36:38 -07:00
Emma Tebibyte f7108245ea
rpn(1): more code cleanup 2024-02-01 10:54:28 -07:00
Emma Tebibyte 34b9519e03
rpn(1): code cleanup 2024-02-01 10:46:10 -07:00
Emma Tebibyte 90ca10990f
Makefile: fixed build 2024-02-01 01:35:31 -07:00
Emma Tebibyte f966233e19
Makefile: RUSTCFLAGS -> RUSTFLAGS 2024-02-01 01:33:47 -07:00
Emma Tebibyte 5f8e90b592
rpn(1): added modulo 2024-02-01 01:26:48 -07:00
Emma Tebibyte 5debb9d954
rpn(1): highest possible precision 2024-02-01 01:18:13 -07:00
Emma Tebibyte 942e284f93
rpn(1): fixed exiting the program on EOF 2024-02-01 00:57:51 -07:00
Emma Tebibyte 4870f4679c
Makefile, rpn(1): add sysexits dependency 2024-02-01 00:41:27 -07:00
Emma Tebibyte 32537895cb
Makefile: added rpn build recipe 2024-02-01 00:31:41 -07:00
Emma Tebibyte ab4d732aaf
rpn: initial working prototype 2024-02-01 00:24:38 -07:00
Emma Tebibyte e246290bff
Merge branch 'djfix' 2024-01-31 13:56:04 -07:00
dtb 271510476c
dj(1): fix getopt optstring 2024-01-29 22:33:04 -07:00
Emma Tebibyte caff26209e
configure: fixed lack of rustc compiler argument parsing 2024-01-19 20:31:34 -07:00
Emma Tebibyte 9095602296
Merge branch 'makefile-fixes' (closes #29) 2024-01-19 20:29:14 -07:00
Emma Tebibyte 7cc5cf162c
Merge branch 'djman' (closes #30) 2024-01-19 20:28:18 -07:00
Emma Tebibyte 601954a0f6
Merge branch 'dj-fixes' 2024-01-19 20:27:31 -07:00
dtb b7bd6a6e7b
dj(1): add copyright header in man page 2024-01-17 21:03:09 -07:00
Emma Tebibyte 5e1749ca43
configure: now you can do multiple compilers! 2024-01-16 16:47:26 -07:00
Emma Tebibyte f99cc797e6
Makefile: changed to be POSIX-compliant 2024-01-16 16:43:00 -07:00
dtb a1b29bb6ed
Makefile: fix PHONYness of existing recipes and make non-PHONY recipes 2024-01-16 00:06:48 -07:00
dtb c237af0e37
Makefile: move PHONYs to their respective recipes 2024-01-15 23:54:42 -07:00
Emma Tebibyte 48c6ad1252
tests: updated tests with copyright and Makefile 2024-01-15 23:28:52 -07:00
Emma Tebibyte 0cbfb5c6cc
Makefile: moved man pages to $PREFIX/share/man 2024-01-15 23:13:53 -07:00
Emma Tebibyte 04923fb1ad
Makefile, true(1), false(1): ported true and false back to C for simplicity and size 2024-01-15 16:53:34 -07:00
Emma Tebibyte c444198efc
closed #18 2024-01-15 16:49:46 -07:00
Emma Tebibyte 28becbafbc
fop(1): updated copyright year and usage info 2024-01-15 16:33:51 -07:00
Emma Tebibyte 52def0a32d
Makefile, fop(1): added argument parsing and -d option 2024-01-15 16:30:00 -07:00
Emma Tebibyte 91e129b813
Makefile, getopt-rs: added getopt-rs library 2024-01-15 15:50:12 -07:00
Emma Tebibyte 49a3bb9f30
Makefile, .gitignore, .editorconfig, configure, tests/cc-compat.sh: added configure script for compiler optimizations 2024-01-15 15:30:21 -07:00
Emma Tebibyte 622c13021d
GNUmakefile: replaced with POSIX Makefile 2024-01-15 14:34:59 -07:00
dtb ba7bd26fe6
dj(1): fix typos in comments 2024-01-15 13:34:30 -07:00
dtb d25c8a64f2
dj(1): add copyright header to dj.c 2024-01-15 13:32:33 -07:00
dtb 14fd23ddba Merge pull request 'dj(1) - disk jockey' (#28) from dj into main
Reviewed-on: bonsai/coreutils#28
2024-01-10 23:07:06 +00:00
dtb 612067890f
dj(1): interpret a '-' file name as standard input/output 2024-01-10 15:03:04 -07:00
dtb 88a66bcc01
dj(1): import from git.sr.ht/~trinity/src 2024-01-09 23:43:45 -07:00
Emma Tebibyte 0c2223d4fb
getopt-rs(3): added getopt library for Rust 2023-12-29 15:17:39 -07:00
Emma Tebibyte 75ead441ec
.editorconfig: Makefile and shell file indentation 2023-12-28 23:34:23 -07:00
Emma Tebibyte f89a686d3c
fop(1): made checking argv better 2023-12-28 22:42:05 -07:00
Emma Tebibyte cb12a5b8fc
GNUmakefile: relative path for include, formatting 2023-12-28 22:33:04 -07:00
Emma Tebibyte 6f4756bc76
GNUmakefile: made compliant with fhs(5) 2023-12-28 01:27:13 -07:00
Emma Tebibyte e16e7bd322
GNUmakefile: made install recipe not install non-existent files 2023-12-28 01:07:30 -07:00
Emma Tebibyte 5df9c33d70
GNUmakefile: bandaid solution to fallback sysexits.h not working with Rust 2023-12-27 23:20:31 -07:00
Emma Tebibyte 9ed70e9648
GNUmakefile: removed stdlib dep 2023-12-27 22:48:36 -07:00
Emma Tebibyte b74bbd6c92
fop(1): finished implementing sysexits 2023-12-27 22:47:06 -07:00
Emma Tebibyte 3355cb3dc3
GNUmakefile, fop(1): added sysexits support 2023-12-27 22:42:50 -07:00
Emma Tebibyte 5d9f6f3245
GNUmakefile: fixed fop(1) segfault 2023-12-27 16:55:50 -07:00
Emma Tebibyte 7eda8bb721
fop(1): working prototype 2023-12-27 15:44:19 -07:00
Emma Tebibyte 36c0c9193c
.editorconfig: fixed utf8 issue 2023-12-27 15:43:05 -07:00
Emma Tebibyte 1dff7d7e18
Revert ".editorconfig: fixed utf8 issue"
This reverts commit d20a1da6f8.
2023-12-27 15:42:27 -07:00
Emma Tebibyte d20a1da6f8
.editorconfig: fixed utf8 issue 2023-12-27 15:40:31 -07:00
Emma Tebibyte cbe8f5f8e8
.editorconfig: added editor configuration; CONTRIBUTING: changed tabstop requirement to 4 2023-12-27 15:38:38 -07:00
Emma Tebibyte 83d6f0b2c2
GNUmakefile: smaller Rust binaries! 2023-12-26 12:35:41 -07:00
Emma Tebibyte 94ba336ee4
GNUmakefile, intcmp(1), npc(1), scrut(1), str(1), strcmp(1), include/sysexits.h: added fallback header file for when systems don’t have sysexits.h(3) 2023-12-25 21:50:45 -07:00
Emma Tebibyte 74d5b65f78
Merge branch 'nosysexits' 2023-12-25 19:04:44 -07:00
Emma Tebibyte 3249a83861
Merge branch 'rmlibfileis' 2023-12-25 19:02:00 -07:00
Emma Tebibyte 10647a01fd
GNUmakefile: updated make recipes 2023-12-25 19:00:17 -07:00
Emma Tebibyte a675386fe3
true(1): ported to Rust 2023-12-25 18:59:46 -07:00
dtb a54a634516
GNUmakefile: remove libfileis(3) 2023-12-25 17:50:23 -07:00
dtb de304b7ef7
remove libfileis(3) 2023-12-25 17:49:05 -07:00
dtb 8a8558442e
GNUmakefile: fix build for false(1) 2023-12-25 17:11:34 -07:00
dtb 324328d2cc
false(1): rewrite from C to Rust 2023-12-25 17:05:19 -07:00
dtb 8e330d8a63
strcmp(1): without <sysexits.h> fall back to ANSI C 2023-12-25 15:58:47 -07:00
dtb d4ee9bb93b
str(1): without <sysexits.h> fall back to ANSI C 2023-12-25 15:58:24 -07:00
dtb 52d150b02b
scrut(1): without <sysexits.h> fall back to ANSI C 2023-12-25 15:58:08 -07:00
dtb 1bc574eafa
npc(1): without <sysexits.h> fall back to ANSI C 2023-12-25 15:57:58 -07:00
dtb cee6575a57
intcmp(1): without <sysexits.h> fall back to ANSI C 2023-12-25 15:56:38 -07:00
Sasha Koshka 47f673ef45
GNUmakefile: repair formatting 2023-12-24 21:30:33 -07:00
Sasha Koshka cfdc2821b2
cc-compat.sh: list build/bin instead of build 2023-12-24 21:30:28 -07:00
Sasha Koshka c12f382608
GNUmakefile: add name 2023-12-24 21:30:21 -07:00
Sasha Koshka 48acf30cd4
GNUmakefile: new build dir structure, only install necessary files 2023-12-24 21:29:50 -07:00
43 changed files with 3107 additions and 287 deletions

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = tab
indent_size = 4
insert_final_newline = true
[*akefile]
indent_size = 2
[*.sh]
indent_size = 2

1
.gitignore vendored
View File

@ -1 +1,2 @@
build/
dist/

View File

@ -90,26 +90,44 @@ notice:
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
When writing code, make sure lines never exceed 80 characters in width when
using two-character-wide tabs.
Make sure lines never exceed 80 columns in width when using four-character
indentation steps. This helps contributors with smaller screens, those using
side-by-side editor windows or panes, and those who have no text wrapping in
their editor or terminal.
For usage text and help messages, please do not implement a -h option. Just
print usage information when any erroneous option is specified. Follow the
NetBSD style guide for usage text output format [1].
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].
If committing a new source file for a utility, format the commit message like
this:
[1] <http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/share/misc/style>
$ git commit -m 'tool(1): <information>'
If committing a new source file, format the commit message following these
guidelines:
$ git commit -m 'tool(1): add feature x'
If committing a new library or header file:
$ git commit -m 'library(1): <information>'
$ git commit -m 'library(3): fix overflow'
$ git commit -m 'header.h(3): add header.h(3)'
If committing a new manual page:
$ git commit -m 'tool.1: add author details'
If modifying some other file or directory:
$ git commit -m 'README: clarification'
$ git commit -m 'tests: posix: fixed bug #47'
$ git commit -m 'docs: tool(1): added author information'
$ git commit -m 'README: clarify'
$ git commit -m 'tests/posix: fix bug #47'
etc.
For multiple of these:
$ git commit -m 'Makefile, tool(1): add tool(1)'
$ git commit -m 'tool(1): add tool(1); library(3), library.3: add library(3)'
$ git commit -m 'tool(1): fix #42 & add feature x'
Commit messages should be written in the present tense.
--
This work © 20232024 by Emma Tebibyte is licensed under CC BY-SA 4.0. To view a
copy of this license, visit <http://creativecommons.org/licenses/by-sa/4.0/>

View File

@ -1,73 +0,0 @@
# Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 2023 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.
# If we want to use POSIX make we cant use ifeq
# .POSIX:
# .PRAGMA: posix_202x
.PHONY: clean
.PHONY: install
.PHONY: test
PREFIX=/usr/local
CC=cc
CFLAGS=-O3 -Lbuild
ifeq ($(CC), gcc)
CFLAGS=-O3 -s -Wl,-z,noseparate-code,-z,nosectionheader -flto -Lbuild
endif
ifeq ($(CC), clang)
CFLAGS=-O3 -Wall -Lbuild
endif
ifeq ($(CC), tcc)
CFLAGS=-O3 -s -Wl -flto -Lbuild
endif
build: build_dir false intcmp scrut str strcmp true
build_dir:
mkdir -p build
clean:
rm -rf build/
install: build
mkdir -p $(PREFIX)/bin $(PREFIX)/lib $(PREFIX)/man/man1 $(PREFIX)/man/man3
cp -f build/*.o $(PREFIX)/lib/
cp -f build/*.so $(PREFIX)/lib/
cp -f build/* $(PREFIX)/bin/
cp -f docs/*.1 $(PREFIX)/man/man1/
cp -f docs/*.3 $(PREFIX)/man/man3/
test: build
tests/cc-compat.sh
tests/posix-compat.sh
false: src/false.c build_dir
$(CC) $(CFLAGS) -o build/false src/false.c
intcmp: src/intcmp.c build_dir
$(CC) $(CFLAGS) -o build/intcmp src/intcmp.c
scrut: src/scrut.c libfileis build_dir
$(CC) $(CFLAGS) -lfileis -o build/scrut src/scrut.c
str: src/str.c build_dir
$(CC) $(CFLAGS) -o build/str src/str.c
strcmp: src/strcmp.c build_dir
$(CC) $(CFLAGS) -o build/strcmp src/strcmp.c
true: src/true.c build_dir
$(CC) $(CFLAGS) -o build/true src/true.c
libfileis: src/libfileis.c src/libfileis.h build_dir
$(CC) $(CFLAGS) -c -fPIC -o build/libfileis.o src/libfileis.c
$(CC) -shared -o build/libfileis.so build/libfileis.o

142
Makefile Normal file
View File

@ -0,0 +1,142 @@
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232024 DTB <trinity@trinity.moe>
# Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media>
# Copyright (c) 2024 Aaditya Aryal <aryalaadi123@gmail.com>
# 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.
.POSIX:
# 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?
DESTDIR ?= dist
PREFIX ?= /usr/local
MANDIR != [ $(PREFIX) = / ] && printf '/usr/share/man\n' \
|| printf '/share/man\n'
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | sed 's/ /\n/g' \
| sed -n 's/sysexits\.h//p' || printf 'include\n'
CC ?= cc
RUSTC ?= rustc
RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
--extern strerror=build/o/libstrerror.rlib
CFLAGS += -I$(SYSEXITS)
.PHONY: all
all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
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
.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)/$(MANDIR)/man1
.PHONY: install
install: dist
cp -r $(DESTDIR)/* /
.PHONY: test
test: build
tests/posix-compat.sh
$(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt
.PHONY: rustlibs
rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \
build/o/libstrerror.rlib
build/o/libgetopt.rlib: build src/getopt-rs/lib.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \
-o $@ src/getopt-rs/lib.rs
build/o/libstrerror.rlib: build src/strerror.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \
src/strerror.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/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ -
.PHONY: dj
dj: build/bin/dj
build/bin/dj: src/dj.c build
$(CC) $(CFLAGS) -o $@ src/dj.c
.PHONY: false
false: build/bin/false
build/bin/false: src/false.c build
$(CC) $(CFLAGS) -o $@ src/false.c
.PHONY: fop
fop: build/bin/fop
build/bin/fop: src/fop.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/fop.rs
.PHONY: hru
hru: build/bin/hru
build/bin/hru: src/hru.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/hru.rs
.PHONY: intcmp
intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.c build
$(CC) $(CFLAGS) -o $@ src/intcmp.c
.PHONY: mm
mm: build/bin/mm
build/bin/mm: src/mm.c build
$(CC) $(CFLAGS) -o $@ src/mm.c
.PHONY: npc
npc: build/bin/npc
build/bin/npc: src/npc.c build
$(CC) $(CFLAGAS) -o $@ src/npc.c
.PHONY: rpn
rpn: build/bin/rpn
build/bin/rpn: src/rpn.rs build 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
.PHONY: str
str: build/bin/str
build/bin/str: src/str.c build
$(CC) $(CFLAGS) -o $@ src/str.c
.PHONY: strcmp
strcmp: build/bin/strcmp
build/bin/strcmp: src/strcmp.c build
$(CC) $(CFLAGS) -o $@ src/strcmp.c
.PHONY: swab
swab: build/bin/swab
build/bin/swab: src/swab.rs build build/o/libsysexits.rlib
$(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/swab.rs
.PHONY: true
true: build/bin/true
build/bin/true: src/true.c build
$(CC) $(CFLAGS) -o $@ src/true.c

32
README
View File

@ -17,6 +17,31 @@ purposes beyond its scope.
See docs/ for more on the specific utilities currently implemented.
Building
The coreutils require a POSIX-compliant environment to compile, including a C
compiler and preprocessor (cc(1) and cpp(1) by default) with the -idirafter
flag, a Rust compiler (rustc(1) by default), bindgen(1), and a POSIX-compliant
make(1) utility.
To build and install:
$ make
$ make PREFIX="/your/preferred/location" install
To build with a different compiler than the default:
$ make CC=clang
$ make RUSTC=gccrs
To test the utilities:
$ make test
To remove all untracked files:
$ make clean
Read More
An Introduction to the Unix Shell
@ -30,3 +55,10 @@ Master Foo Discourses on the Unix-Nature
Shell Programming!
<https://tldp.org/LDP/abs/html/why-shell.html>
--
Copyright © 20232024 Emma Tebibyte <emma@tebibyte.media>
Copyright © 2024 DTB <trinity@trinity.moe>
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/>.

160
docs/dj.1 Normal file
View File

@ -0,0 +1,160 @@
.\" 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 dj 1
.SH NAME
dj \(en disk jockey
.SH SYNOPSIS
dj
.RB ( -AdHnq )
.RB ( -a
.RB [ byte ])
.RB ( -c
.RB [ count ])
.RB ( -i
.R [
.B input file
.R ])
.RB ( -b
.R [
.B input block size
.R ])
.RB ( -s
.R [
.B input offset
.R ])
.RB ( -o
.R [
.B output file
.R ])
.RB ( -B
.R [
.B output block size
.R ])
.RB ( -S
.R [
.B output offset
.R ])
.SH USAGE
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.
.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.
.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
.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.
.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.
.SH COPYRIGHT
Copyright (C) 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
dd(1)

View File

@ -1,4 +1,4 @@
.\" Copyright (c) 2022 DTB <trinity@trinity.moe>
.\" Copyright (c) 2022, 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
@ -32,4 +32,4 @@ This work is marked with CC0 1.0. To see a copy of this license, visit
.SH SEE ALSO
true(1)
true(1p)

57
docs/hru.1 Normal file
View File

@ -0,0 +1,57 @@
.\" 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 rpn 1
.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.
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.
.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.
.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).
.SH AUTHOR
Written by Emma Tebibyte <emma@tebibyte.media>.
.SH COPYRIGHT
Copyright (c) 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

View File

@ -1,4 +1,4 @@
.\" Copyright (c) 2023 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
@ -75,4 +75,4 @@ Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.SH SEE ALSO
strcmp(1), scrut(1), str(1), test(1)
strcmp(1), scrut(1), str(1), test(1p)

76
docs/mm.1 Normal file
View File

@ -0,0 +1,76 @@
.\" 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 mm 1
.SH NAME
mm \(en middleman
.SH SYNOPSIS
mm
.RB ( -aenu )
.RB ( -i
.RB [ input ])
.RB ( -o
.RB [ output ])
.SH DESCRIPTION
Mm catenates input files and writes them to the start of each output file.
.SH OPTIONS
Mm, upon receiving the
.B -a
option, will open subsequent outputs for appending rather than updating.
.PP
The
.B -i
option opens a path as an input. Without any inputs specified mm will use
standard input. Standard input itself can be specified by giving the path '-'.
.PP
The
.B -o
option opens a path as an output. Without any outputs specified mm will use
standard output. Standard output itself can be specified by giving the
path '-'. Standard error itself can be specified with the
.B -e
option.
.PP
The
.B -u
option ensures neither input or output will be buffered.
.PP
The
.B -n
option tells mm to ignore SIGINT signals.
.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.
.SH RATIONALE
Mm was modeled after the cat and tee utilities specified in POSIX.
.SH COPYRIGHT
Copyright (c) 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)

View File

@ -1,4 +1,4 @@
.\" Copyright (c) 2023 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
@ -62,7 +62,7 @@ Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.SH SEE ALSO
cat(1), cat-v(1)
cat(1p), cat-v(1)
.I UNIX Style, or cat -v Considered Harmful
by Rob Pike

70
docs/rpn.1 Normal file
View File

@ -0,0 +1,70 @@
.\" Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
.\" 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 rpn 1
.SH NAME
rpn \(en reverse polish notation evaluation
.SH SYNOPSIS
rpn
.RB [numbers...]\ [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.
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
stack.
For information on for reverse polish notation syntax, see 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
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.
.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.
.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.
.SH AUTHOR
Written by Emma Tebibyte <emma@tebibyte.media>.
.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

93
docs/scrut.1 Normal file
View File

@ -0,0 +1,93 @@
.\" 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,4 +1,4 @@
.\" Copyright (c) 2023 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
@ -55,4 +55,4 @@ Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.SH SEE ALSO
ctype(3), strcmp(1), ascii(7)
ctype(3p), strcmp(1), ascii(7)

View File

@ -1,10 +1,9 @@
.\" Copyright (c) 2023 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 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
.SH NAME
@ -60,4 +59,4 @@ Copyright © 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.SH SEE ALSO
strcmp(3), intcmp(1), scrut(1), test(1)
strcmp(3), intcmp(1), scrut(1), test(1p)

71
docs/swab.1 Normal file
View File

@ -0,0 +1,71 @@
.\" 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 swab 1
.SH NAME
swab \(en swap bytes
.SH SYNOPSIS
swab
.RB ( -f )
.RB ( -w
.R [
.B word size
.R ])
.SH USAGE
Swab swaps the latter and former halves of a block of bytes.
.SH EXAMPLES
The following sh(1p) line:
.R printf 'hello world!\n' | swab
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.
.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).
.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.
.SH COPYRIGHT
Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
dd(1p)

View File

@ -1,4 +1,4 @@
.\" Copyright (c) 2022 DTB <trinity@trinity.moe>
.\" Copyright (c) 2022, 2024 DTB <trinity@trinity.moe>
.\" Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
@ -32,4 +32,4 @@ This work is marked with CC0 1.0. To see a copy of this license, visit
.SH SEE ALSO
false(1)
false(1p)

38
include/sysexits.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: CC0
*
* This work is marked with CC0 1.0. To view a copy of this license, visit
* <http://creativecommons.org/publicdomain/zero/1.0>.
*/
#ifndef _SYSEXITS_H
#define _SYSEXITS_H 2
#ifndef EXIT_FAILURE
#include <stdlib.h>
#endif
#define EX_OK EXIT_SUCCESS
#define EX__BASE EXIT_FAILURE /* base value for error messages */
#define EX_USAGE EXIT_FAILURE /* command line usage error */
#define EX_DATAERR EXIT_FAILURE /* data format error */
#define EX_NOINPUT EXIT_FAILURE /* cannot open input */
#define EX_NOUSER EXIT_FAILURE /* addressee unknown */
#define EX_NOHOST EXIT_FAILURE /* host name unknown */
#define EX_UNAVAILABLE EXIT_FAILURE /* service unavailable */
#define EX_SOFTWARE EXIT_FAILURE /* internal software error */
#define EX_OSERR EXIT_FAILURE /* system error (e.g., can't fork) */
#define EX_OSFILE EXIT_FAILURE /* critical OS file missing */
#define EX_CANTCREAT EXIT_FAILURE /* can't create (user) output file */
#define EX_IOERR EXIT_FAILURE /* input/output error */
#define EX_TEMPFAIL EXIT_FAILURE /* temp failure; user is invited to retry */
#define EX_PROTOCOL EXIT_FAILURE /* remote error in protocol */
#define EX_NOPERM EXIT_FAILURE /* permission denied */
#define EX_CONFIG EXIT_FAILURE /* configuration error */
#define EX__MAX EXIT_FAILURE /* maximum listed value */
#endif

452
src/dj.c Normal file
View File

@ -0,0 +1,452 @@
/*
* 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 <ctype.h> /* isupper(3), tolower(3) */
#include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */
#include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* free(3), malloc(3), strtol(3), size_t */
#include <string.h> /* memcpy(3), memmove(3), memset(3) */
#include <sysexits.h> /* EX_OK, EX_USAGE */
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
extern int errno;
/* 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) */
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 */
/* 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)
#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 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. */
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){
int t;
if((t = write(io->fd, io->buf, io->bufuse)) > 0)
memmove(io->buf, io->buf + t, (io->bufuse -= t));
io->bytes += t;
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){
fprintf(stderr, "%s: %s: %s\n", program_name, s, strerror(errno));
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;
}
/* 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){
long r;
errno = 0;
r = strtol(s, &s, 0);
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);
return EX_USAGE;
}
int main(int argc, char *argv[]){
int c;
int i;
setdefaults;
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();
}
}
if(debug >= 3)
fprintf(stderr,
"argv0=%s\n"
"in=%s\tibs=%d\tskip=%ld\talign=%hhx\tcount=%d\n"
"out=%s\tobs=%d\tseek=%ld\tdebug=%2d\tnoerror=%d\n",
program_name,
ep[0].fn, ep[0].bs, ep[0].seek, align, count,
ep[1].fn, ep[1].bs, ep[1].seek, debug, noerror);
if(argc > optind){
terminate(ep);
return usage();
}
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);
return EX_OSERR;
}else if(ep[i].seek > 0)
switch(Io_fdseek(&ep[i])){
case EX_OK:
output();
terminate(ep);
return EX_OK;
}
}
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();
}
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));
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();
}
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);
return EX_OK;
}

120
src/fop.rs Normal file
View File

@ -0,0 +1,120 @@
/*
* 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::{
env::args,
io::{ Read, stdin, stdout, Write },
process::{ Command, exit, Stdio },
};
extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::{ Opt, Parser };
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:");
while let Some(opt) = arg_parser.next() {
match opt {
Ok(Opt('d', Some(arg))) => {
let arg_char = arg.chars().collect::<Vec<char>>();
if arg_char.len() > 1 {
eprintln!("{}: {}: Not a character.", argv[0], arg);
exit(EX_USAGE);
} else { d = arg_char[0]; }
},
_ => {},
};
}
let 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| {
eprintln!("{}: {}: {}", argv[0], argv[1], e);
exit(EX_DATAERR);
});
let mut buf = String::new();
let _ = stdin().read_to_string(&mut buf);
let mut fields = buf.split(d).collect::<Vec<&str>>();
let opts = argv
.iter()
.clone()
.skip(command_arg + 1)
.collect::<Vec<&String>>();
let mut spawned = Command::new(argv.get(command_arg).unwrap())
.args(opts)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else( |e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_UNAVAILABLE);
});
let field = fields.get(index).unwrap_or_else(|| {
eprintln!(
"{}: {}: No such index in input",
argv[0],
index.to_string(),
);
exit(EX_DATAERR);
});
if let Some(mut child_stdin) = spawned.stdin.take() {
let _ = child_stdin.write_all(field.as_bytes());
drop(child_stdin);
}
let output = spawned.wait_with_output().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_IOERR);
});
let mut replace = output.stdout.clone();
if replace.pop() != Some(b'\n') { replace = output.stdout; }
let new_field = String::from_utf8(replace).unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e);
exit(EX_IOERR);
});
fields[index] = &new_field;
stdout().write_all(
fields.join(&d.to_string()).as_bytes()
).unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
});
}

95
src/getopt-rs/error.rs Normal file
View File

@ -0,0 +1,95 @@
/*
* 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

@ -0,0 +1,61 @@
/*
* 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,
}

72
src/getopt-rs/lib.rs Normal file
View File

@ -0,0 +1,72 @@
/*
* 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;

89
src/getopt-rs/opt.rs Normal file
View File

@ -0,0 +1,89 @@
/*
* 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)
}
}

382
src/getopt-rs/parser.rs Normal file
View File

@ -0,0 +1,382 @@
/*
* 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))))
}
}
}
}

59
src/getopt-rs/result.rs Normal file
View File

@ -0,0 +1,59 @@
/*
* 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>;

228
src/getopt-rs/tests.rs Normal file
View File

@ -0,0 +1,228 @@
/*
* 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
}
}

109
src/hru.rs Normal file
View File

@ -0,0 +1,109 @@
/*
* 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::{
cmp::Ordering,
env::args,
io::{ stdin, stdout, Write },
process::{ ExitCode, exit },
};
extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE };
const LIST: [(u32, &str); 10] = [
(3, "k"),
(6, "M"),
(9, "G"),
(12, "T"),
(15, "P"),
(18, "E"),
(21, "Z"),
(24, "Y"),
(27, "R"),
(30, "Q")
];
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
let mut out = (input as f64, (0_u32, ""));
if input < 1000 { return Ok(out); }
for (n, p) in LIST {
let c = match 10_u128.checked_pow(n) {
Some(c) => c,
None => {
return Err(format!("10^{}: Integer overflow", n.to_string()));
},
};
match c.cmp(&input) {
Ordering::Less => {
out = (input as f64 / c as f64, (n, p));
},
Ordering::Equal => {
return Ok((input as f64 / c as f64, (n, p)));
},
Ordering::Greater => {},
};
}
Ok(out)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
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);
},
};
let (number, prefix) = match convert(n) {
Ok(x) => x,
Err(err) => {
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_SOFTWARE as u8);
},
};
let si_prefix = format!("{}B", prefix.1);
let out = ((number * 10.0).round() / 10.0).to_string();
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes())
.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
});
}
ExitCode::SUCCESS
}

View File

@ -18,11 +18,9 @@
#include <errno.h> /* errno */
#include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* strtol(3), size_t */
#ifndef EX_USAGE
# include <sysexits.h> /* EX_USAGE */
#endif
#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 */

View File

@ -1,63 +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 "libfileis.h"
#include <unistd.h> /* access(3), F_OK, R_OK, W_OK, X_OK */
int f_executable(char *path){ return access(path, X_OK) == 0; } /* test -x */
int f_exists(char *path){ return access(path, F_OK) == 0; } /* test -e */
int f_readable(char *path){ return access(path, R_OK) == 0; } /* test -r */
int f_writeable(char *path){ return access(path, W_OK) == 0; } /* test -w */
#include <sys/stat.h> /* lstat(3), struct stat, S_ISBLK, S_ISCHR, S_ISDIR,
* S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK,
* S_ISUID, S_ISVTX */
static struct stat buf;
int f_blockspecial(char *path){ /* test -b */
return lstat(path, &buf) != -1 && S_ISBLK(buf.st_mode);
}
int f_charspecial(char *path){ /* test -c */
return lstat(path, &buf) != -1 && S_ISCHR(buf.st_mode);
}
int f_directory(char *path){ /* test -d */
return lstat(path, &buf) != -1 && S_ISDIR(buf.st_mode);
}
int f_fifospecial(char *path){ /* test -p */
return lstat(path, &buf) != -1 && S_ISFIFO(buf.st_mode);
}
int f_gid(char *path){ /* test -g */
return lstat(path, &buf) != -1 && (buf.st_mode & S_ISGID);
}
int f_regular(char *path){ /* test -f */
return lstat(path, &buf) != -1 && S_ISREG(buf.st_mode);
}
int f_socket(char *path){ /* test -S */
return lstat(path, &buf) != -1 && S_ISSOCK(buf.st_mode);
}
int f_sticky(char *path){ /* test -k */
return lstat(path, &buf) != -1 && (buf.st_mode & S_ISVTX);
}
int f_symlink(char *path){ /* test -h; test -L */
return lstat(path, &buf) != -1 && S_ISLNK(buf.st_mode);
}
int f_uid(char *path){ /* test -u */
return lstat(path, &buf) != -1 && (buf.st_mode & S_ISUID);
}

View File

@ -1,62 +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/.
*/
/* libfileis functions return true if the condition is true and false if the
* condition is false. Returned values are 0 or 1 only. */
/* True if file exists and is a block special file. */
int f_blockspecial(char *path);
/* True if file exists and is a character special file. */
int f_charspecial(char *path);
/* True if file exists and is a directory. */
int f_directory(char *path);
/* True if file exists and is executable. */
int f_executable(char *path);
/* True if file exists (regardless of type). */
int f_exists(char *path);
/* True if file exists and is a named pipe (FIFO). */
int f_fifospecial(char *path);
/* True if file exists and its set group ID flag is set. */
int f_gid(char *path);
/* True if file exists and is readable. */
int f_readable(char *path);
/* True if file exists and is a regular file. */
int f_regular(char *path);
/* True if file exists and is a socket. */
int f_socket(char *path);
/* True if file exists and its sticky bit is set. */
int f_sticky(char *path);
/* True if file exists and is a symbolic link. */
int f_symlink(char *path);
/* True if file exists and its set user ID flag is set. */
int f_uid(char *path);
/* True if file exists and is writeable. */
int f_writeable(char *path);

236
src/mm.c Normal file
View File

@ -0,0 +1,236 @@
/*
* 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
/* Prints a usage text, in which s is the program being run (i.e. argv[0]), and
* returns an exit status appropriate for a usage error. */
int usage(char *s){
fprintf(stderr, "Usage: %s (-aenu) (-i [input])... (-o [output])...\n", s);
return EX_USAGE;
}
int main(int argc, char *argv[]){
int c;
struct Files files[2]; /* {read, write} */
size_t i;
size_t j;
size_t k; /* loop index but also unbuffer status */
int retval;
/* Initializes the files structs with their default values, standard
* input and standard output. If an input or an output is specified
* these initial values will be overwritten, so to, say, use mm(1)
* equivalently to tee(1p), -o - will need to be specified before
* additional files to ensure standard output is still written. */
for(i = 0; i < 2; ++i){
files[i].a = 0;
files[i].s = 0;
files[i].mode = fmode[i];
files[i].files = NULL;
files[i].names = NULL;
Files_append(&files[i], i == 0 ? stdin : stdout,
i == 0 ? stdin_name : stdout_name);
files[i].s = 0;
}
k = 0;
if(argc > 0)
program_name = argv[0];
if(argc > 1)
while((c = getopt(argc, argv, "aehi:no:u")) != -1)
switch(c){
case 'a': /* "rb+" -> "ab" */
files[1].mode[0] = 'a';
files[1].mode[2] = '\0';
break;
case 'e':
if(Files_append(&files[1], stderr, stderr_name) != NULL)
break;
retval = oserr(argv[0], "-e");
terminate;
case 'i':
if((strcmp(optarg, "-") == 0 && Files_append(&files[0],
stdin, stdin_name) != NULL)
|| Files_open(&files[0], optarg) != NULL)
break;
retval = oserr(argv[0], optarg);
terminate;
case 'o':
if((strcmp(optarg, "-") == 0 && Files_append(&files[1],
stdout, stdout_name) != NULL)
|| Files_open(&files[1], optarg) != NULL)
break;
/* does not exist, so try to create it */
if(errno == ENOENT){
files[1].mode = wharsh;
if(Files_open(&files[1], optarg) != NULL){
files[1].mode = fmode[1];
break;
}
}
retval = oserr(argv[0], optarg);
terminate;
case 'n':
if(signal(SIGINT, SIG_IGN) != SIG_ERR)
break;
retval = oserr(argv[0], "-n");
terminate;
case 'u':
k = 1;
break;
default:
retval = usage(argv[0]);
terminate;
}
if(optind != argc){
retval = usage(argv[0]);
terminate;
}
files[0].s += files[0].s == 0;
files[1].s += files[1].s == 0;
/* Unbuffer files. */
if(k){
for(i = 0;
i < files[0].s;
setvbuf(files[0].files[i++], NULL, _IONBF, 0));
for(i = 0;
i < files[1].s;
setvbuf(files[1].files[i++], NULL, _IONBF, 0));
}
retval = EX_OK;
/* Actual program loop. */
for(i = 0; i < files[0].s; ++i) /* iterate ins */
while((c = getc(files[0].files[i])) != EOF) /* iterate chars */
for(j = 0; j < files[1].s; ++j) /* iterate outs */
if(putc(c, files[1].files[j]) == EOF){
/* notebook's full */
retval = EX_IOERR;
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
if(fclose(files[1].files[j]) == EOF)
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
/* massage out the tense muscle */
for(k = j--; k < files[1].s - 1; ++k){
files[1].files[k] = files[1].files[k+1];
files[1].names[k] = files[1].names[k+1];
}
if(--files[1].s == 0)
terminate;
}
terminate;
}

View File

@ -18,10 +18,9 @@
#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) */
#if !defined EX_USAGE || !defined EX_OK
# include <sysexits.h>
#endif
#include <sysexits.h>
int main(int argc, char *argv[]){
int c;

248
src/rpn.rs Normal file
View File

@ -0,0 +1,248 @@
/*
* 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/.
*
* This file incorporates work covered by the following copyright and permission
* notice:
*
* MIT License
*
* Copyright (c) 2022 Lilly Cham
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
use std::{
collections::VecDeque,
env::args,
fmt::{ self, Display, Formatter },
io::stdin,
process::ExitCode,
};
use CalcType::*;
extern crate sysexits;
use sysexits::EX_DATAERR;
#[derive(Clone, PartialEq, PartialOrd, Debug)]
// enum CalcType is a type containing operations used in the calculator
enum CalcType {
Add,
Subtract,
Multiply,
Divide,
Power,
Floor,
Modulo,
Val(f64),
Invalid(String),
}
impl From<&str> for CalcType {
fn from(value: &str) -> Self {
match value {
"+" => Add,
"-" | "" => Subtract,
"*" | "×" => Multiply,
"/" | "÷" => Divide,
"^" | "**" => Power,
"//" => Floor,
"%" => Modulo,
_ => {
match value.parse::<f64>() {
Ok(x) => Val(x),
Err(_) => Invalid(value.to_owned()),
}
},
}
}
}
impl Display for CalcType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let y: String;
write!(f, "{}", match self {
Add => "addition",
Subtract => "subtraction",
Multiply => "multiplication",
Divide => "division",
Power => "exponentiation",
Floor => "floor division",
Modulo => "modulus",
Val(x) => {
y = x.to_string(); &y
},
Invalid(i) => {
y = i.to_string(); &y
},
})
}
}
#[derive(Debug, Clone)]
struct EvaluationError {
message: String,
code: i32,
}
// Im no math nerd but I want the highest possible approximation of 0.9
// repeating and it seems this can give it to me
const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0;
fn eval(
input: &str,
initial_stack: VecDeque<f64>,
) -> Result<(VecDeque<f64>, bool), EvaluationError> {
let mut stack = initial_stack;
let mut oper = false;
if input.is_empty() {
stack.clear();
return Ok((stack, oper));
}
// 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() {
match n {
Val(v) => stack.push_back(v),
Invalid(i) => {
return Err(EvaluationError {
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,
})
}
},
};
}
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);
(value * multiplier).round() / multiplier
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
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 {
let input = argv
.iter()
.skip(1)
.map(|x| x.to_owned())
.collect::<Vec<String>>()
.join(" ");
match eval(&input, stack) {
Ok(s) => {
stack = s.0.clone();
let val = match stack.iter().last() {
Some(v) => v,
None => return ExitCode::from(0),
};
println!("{}", round_precise(val, precision).to_string())
},
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(err.code as u8);
},
};
}
ExitCode::from(0)
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* Copyright (c) 20232024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
@ -17,7 +18,8 @@
*/
#include <stdio.h> /* fprintf(3), stderr, NULL */
#include <string.h> /* strchr(3) */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <string.h> /* memset(3), strchr(3) */
#ifndef EX_USAGE
# include <sysexits.h>
#endif
@ -34,24 +36,31 @@ int main(int argc, char *argv[]){
struct stat buf;
int c;
size_t i;
char *p;
if(argc < 2)
goto usage;
i = 0;
memset(ops, '\0', sizeof ops);
while((c = getopt(argc, argv, args)) != -1)
if(strchr(args, c) == NULL)
if((p = strchr(args, c)) == NULL)
goto usage;
else
ops[i++] = c;
ops[i] = '\0';
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';
}
if(optind == argc)
goto usage;
argv += optind;
do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1)
return 1; /* doesn't exist or isn't stattable */
return EXIT_FAILURE; /* doesn't exist or isn't stattable */
for(i = 0; ops[i] != '\0'; ++i)
if(ops[i] == 'e')
@ -91,8 +100,8 @@ usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n",
&& !S_ISLNK(buf.st_mode))
|| (ops[i] == 'S'
&& !S_ISSOCK(buf.st_mode)))
return 1;
return EXIT_FAILURE;
}while(*++argv != NULL);
return 0;
return EXIT_SUCCESS;
}

View File

@ -20,10 +20,9 @@
#include <ctype.h>
#include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3) */
#include <stdlib.h> /* EXIT_FAILURE */
#include <string.h> /* strcmp(3) */
#if !defined EX_USAGE
# include <sysexits.h>
#endif
#include <sysexits.h>
static char *program_name = "str";

View File

@ -1,7 +1,6 @@
#include <stdio.h> /* fprintf(3), stderr */
#ifndef EX_USAGE
# include <sysexits.h> /* EX_USAGE */
#endif
#include <stdlib.h> /* EXIT_FAILURE */
#include <sysexits.h>
static char *program_name = "strcmp";

31
src/strerror.rs Normal file
View File

@ -0,0 +1,31 @@
/*
* 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.
*/
use std::ffi::{ c_int, c_char, CStr };
pub trait StrError { fn strerror(&self) -> String; }
impl StrError for std::io::Error {
/* wrapper function for use in Rust */
fn strerror(&self) -> String {
/* Get the raw OS error. If its None, what the hell is going on‽ */
let errno = self.raw_os_error().unwrap_or(0) as c_int;
/* Get a CStr from the error message so that its referenced and then
* convert it to an owned value. If the string is not valid UTF-8,
* return that error instead. */
match unsafe { CStr::from_ptr(strerror(errno)) }.to_str() {
Ok(s) => s.to_owned(), // yay!! :D
Err(e) => e.to_string(), // awww :(
}
}
}
/* binding to strerror(3p) */
extern "C" { fn strerror(errnum: c_int) -> *mut c_char; }

90
src/swab.rs Normal file
View File

@ -0,0 +1,90 @@
/*
* 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,
io::{ stdin, stdout, Error, ErrorKind, Read, Write },
process::ExitCode,
vec::Vec
};
extern crate getopt;
use getopt::{ Opt, Parser };
extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
fn oserr(s: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", s, e);
ExitCode::from(EX_OSERR as u8)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} (-f) (-w [wordsize])", s);
ExitCode::from(EX_USAGE as u8)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut buf: Vec<u8> = Vec::new();
let mut input = stdin();
let mut output = stdout().lock();
let mut opts = Parser::new(&argv, "fw:");
let mut force = false;
let mut wordsize: usize = 2;
loop {
match opts.next() {
None => break,
Some(opt) =>
match opt {
Ok(Opt('f', None)) => force = true,
Ok(Opt('w', Some(arg))) => {
match arg.parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); },
}
},
_ => { return usage(&argv[0]); }
}
}
}
buf.resize(wordsize, 0);
loop {
match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8),
Ok(v) if v == wordsize => {
let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) {
break oserr(&argv[0], e)
}
},
Ok(v) => {
if let Err(e) = output.write(&buf[..v]) {
break oserr(&argv[0], e)
}
},
Err(e) if e.kind() == ErrorKind::Interrupted && force => continue,
Err(e) => break oserr(&argv[0], e)
}
}
}

10
src/test.rs Normal file
View File

@ -0,0 +1,10 @@
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,39 +0,0 @@
#!/bin/sh
# Copyright (c) 2023 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 GNUmakefile >/dev/null 2>&1
then
printf '%s: Run this script in the root of the project.\n' "$0" 1>&2
exit 64 # sysexits.h(3) EX_USAGE
fi
make clean
for CC in cc \
clang \
gcc \
tcc \
'zig cc'
do
export CC
command -v "$(printf '%s\n' "$CC" | cut -d ' ' -f1)" >/dev/null 2>&1 \
|| continue
printf '%s: %s: Testing build.\n' "$0" "$CC"
make CC="$CC" && printf '%s: Build successful.\n' "$0"
ls -lA build/
make clean
printf '\n'
done

View File

@ -1,6 +1,6 @@
#!/bin/sh
# Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
@ -9,7 +9,7 @@
set -e
if ! ls GNUmakefile >/dev/null 2>&1
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