diff options
author | Case Duckworth | 2022-07-26 23:10:44 -0500 |
---|---|---|
committer | Case Duckworth | 2022-07-26 23:11:46 -0500 |
commit | b4d834728e89beb57377fd810abf9ee8a3365d42 (patch) | |
tree | a24914900e3f039e9e1922dd89cf5b35a46877ec | |
download | shin-b4d834728e89beb57377fd810abf9ee8a3365d42.tar.gz shin-b4d834728e89beb57377fd810abf9ee8a3365d42.zip |
Initial commit
-rw-r--r-- | COPYING | 9 | ||||
-rw-r--r-- | Makefile | 31 | ||||
-rw-r--r-- | README.md | 36 | ||||
-rwxr-xr-x | shin | 67 | ||||
-rwxr-xr-x | shin.awk | 60 |
5 files changed, 203 insertions, 0 deletions
diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0520203 --- /dev/null +++ b/COPYING | |||
@@ -0,0 +1,9 @@ | |||
1 | Copyright (C) 2022 Case Duckworth <acdw@acdw.net> | ||
2 | |||
3 | Usage of the works is permitted provided that this instrument is | ||
4 | retained with the works, so that any entity that uses the works is | ||
5 | notified of this instrument. | ||
6 | |||
7 | DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. | ||
8 | |||
9 | |||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a9401a1 --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,31 @@ | |||
1 | NAME = shin | ||
2 | DESC = Include shell scripts in other shell scripts | ||
3 | |||
4 | DESTDIR = | ||
5 | PREFIX = /usr/local | ||
6 | |||
7 | BIN = $(DESTDIR)$(PREFIX)/bin/$(NAME) | ||
8 | |||
9 | .PHONY: help | ||
10 | help: | ||
11 | @echo "$(NAME) : $(DESC)" | ||
12 | @echo "(C) 2022 Case Duckworth <acdw@acdw.net>" | ||
13 | @echo "Licensed under the Fair License; see COPYING for details." | ||
14 | @echo | ||
15 | @echo "TARGETS:" | ||
16 | @echo " install Install $(NAME) to $(BIN)." | ||
17 | @echo " link Install $(NAME) using symlinks." | ||
18 | @echo " Probably only useful for development." | ||
19 | @echo " uninstall Uninstall $(NAME)-related files." | ||
20 | |||
21 | .PHONY: install | ||
22 | install: $(NAME) | ||
23 | install -D $< $(BIN) | ||
24 | |||
25 | .PHONY: link | ||
26 | link: | ||
27 | ln -sf $(PWD)/$(NAME) $(BIN) | ||
28 | |||
29 | .PHONY: uninstall | ||
30 | uninstall: | ||
31 | rm $(BIN) | ||
diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b68c96 --- /dev/null +++ b/README.md | |||
@@ -0,0 +1,36 @@ | |||
1 | # shin | ||
2 | ## include shell files | ||
3 | |||
4 | built to scratch a personal itch. SHIN is an awk(1) script that plops include | ||
5 | files in to .shin files. here's the usage text: | ||
6 | |||
7 | ``` | ||
8 | SHIN: include shell files in other shell files | ||
9 | USAGE: shin FILE... | ||
10 | |||
11 | FILEs named FILE.shin will be built to FILE.sh in the same directory. | ||
12 | to include files in shin files, use the following comment syntax: | ||
13 | |||
14 | #< INCLUDE | ||
15 | |||
16 | shin will add INCLUDE below that comment, as well as a comment | ||
17 | denoting the end of INCLUDE. | ||
18 | ``` | ||
19 | |||
20 | it's really that simple. and stupid. | ||
21 | |||
22 | ## configuration | ||
23 | |||
24 | SHIN will look for include files in the current directory, unless you set the | ||
25 | SHINPATH environment variable. $SHINPATH is a colon-separated list of | ||
26 | directories like $PATH or $MANPATH. | ||
27 | |||
28 | ## extras | ||
29 | |||
30 | `shin.awk` is the plain awk script. because awk is dumb about handling | ||
31 | arguments, `shin` is a shell script that will do `shin.awk`'s work unless you | ||
32 | pass `shin -h`, in which case it will print a helpful message. | ||
33 | |||
34 | ## license | ||
35 | |||
36 | SHIN is licensed under the FAIR license. see COPYING for details. | ||
diff --git a/shin b/shin new file mode 100755 index 0000000..b966091 --- /dev/null +++ b/shin | |||
@@ -0,0 +1,67 @@ | |||
1 | #!/bin/sh | ||
2 | # SHIN v. 0/1 | ||
3 | # Copyright (C) Case Duckworth <acdw@acdw.net> | ||
4 | # Licensed under the Fair License. See COPYING for details. | ||
5 | |||
6 | _shin() { | ||
7 | awk 'BEGIN { | ||
8 | if (ENVIRON["SHINPATH"]) split(ENVIRON["SHINPATH"], SHINPATH, ":") | ||
9 | else SHINPATH[1] = "." | ||
10 | } | ||
11 | FNR == 1 { outfile = FILENAME; sub(/in$/, "", outfile) } | ||
12 | { print($0) > outfile } | ||
13 | /^#</ { | ||
14 | inclfile = shin_resolve(substr($0, 3)) | ||
15 | while (getline l < inclfile) print(l) > outfile | ||
16 | close(inclfile) | ||
17 | sub(/</, ">", $0) | ||
18 | print > outfile | ||
19 | } | ||
20 | function shin_test(filename) { | ||
21 | if (! system("test -f \"" f "\"")) return filename | ||
22 | print("Cannot find \"" filename "\" in " sp) > (STDERR ? STDERR : "/dev/stderr") | ||
23 | exit 1 | ||
24 | } | ||
25 | function shin_resolve(filename) { | ||
26 | if (match(filename, "^/")) return shin_test(filename) | ||
27 | if (match(filename, "^~")) return shin_test(ENVIRON["HOME"] "/" substr(filename, 2)) | ||
28 | sub(/^[ \t]*/, "", filename) | ||
29 | sub(/[ \t]*$/, "", filename) | ||
30 | sp = "" | ||
31 | for (p in SHINPATH) { | ||
32 | sp = sp (sp ? ", " : "") "\"" SHINPATH[p] "\"" | ||
33 | f = SHINPATH[p] "/" filename | ||
34 | gsub("//", "/", f) | ||
35 | return shin_test(f) | ||
36 | } | ||
37 | }' | ||
38 | } | ||
39 | |||
40 | if [ "x$1" = -h ]; then | ||
41 | cat <<\EOF | ||
42 | SHIN: include shell files in other shell files | ||
43 | USAGE: shin [-h] FILE... | ||
44 | |||
45 | FLAGS: | ||
46 | -h Show this help and exit. | ||
47 | |||
48 | PARAMETERS: | ||
49 | FILE... Input files. FILEs named FILE.shin will be built to | ||
50 | FILE.sh in the same directory. To include files in | ||
51 | shin files, use the following comment syntax: | ||
52 | |||
53 | #< INCLUDE | ||
54 | |||
55 | If INCLUDE begins with / or ~, it's taken as a literal | ||
56 | file name; otherwise SHINPATH will be searched for the | ||
57 | file. SHINPATH is a colon-separated list of paths | ||
58 | like $PATH. If SHINPATH is unset, it defaults to the | ||
59 | current directory. | ||
60 | |||
61 | If INCLUDE is not found, shin will error and quit. | ||
62 | Otherwise, shin will add INCLUDE below that comment, | ||
63 | as well as a comment denoting the end of the INCLUDE. | ||
64 | EOF | ||
65 | else | ||
66 | _shin "$@" | ||
67 | fi | ||
diff --git a/shin.awk b/shin.awk new file mode 100755 index 0000000..80c76aa --- /dev/null +++ b/shin.awk | |||
@@ -0,0 +1,60 @@ | |||
1 | #!/usr/bin/awk -f | ||
2 | # SHIN: include files in shell scripts | ||
3 | # by Case Duckworth <acdw@acdw.net> | ||
4 | # usage: shin -- FILE.shin... | ||
5 | # each FILE.shin will output to FILE.sh in the same directory | ||
6 | BEGIN { | ||
7 | if (ENVIRON["SHINPATH"]) { | ||
8 | split(ENVIRON["SHINPATH"], SHINPATH, ":") | ||
9 | } else { | ||
10 | SHINPATH[1] = "." | ||
11 | } | ||
12 | } | ||
13 | |||
14 | FNR == 1 { | ||
15 | outfile = FILENAME | ||
16 | sub(/in$/, "", outfile) | ||
17 | } | ||
18 | |||
19 | { | ||
20 | print($0) > outfile | ||
21 | } | ||
22 | |||
23 | /^#</ { | ||
24 | inclfile = shin_resolve(substr($0, 3)) | ||
25 | while (getline l < inclfile) { | ||
26 | print(l) > outfile | ||
27 | } | ||
28 | close(inclfile) | ||
29 | sub(/</, ">", $0) | ||
30 | print > outfile | ||
31 | } | ||
32 | |||
33 | |||
34 | function shin_resolve(filename) | ||
35 | { | ||
36 | if (match(filename, "^/")) { | ||
37 | return shin_test(filename) | ||
38 | } | ||
39 | if (match(filename, "^~")) { | ||
40 | return shin_test(ENVIRON["HOME"] "/" substr(filename, 2)) | ||
41 | } | ||
42 | sub(/^[ \t]*/, "", filename) | ||
43 | sub(/[ \t]*$/, "", filename) | ||
44 | sp = "" | ||
45 | for (p in SHINPATH) { | ||
46 | sp = sp (sp ? ", " : "") "\"" SHINPATH[p] "\"" | ||
47 | f = SHINPATH[p] "/" filename | ||
48 | gsub("//", "/", f) | ||
49 | return shin_test(f) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | function shin_test(filename) | ||
54 | { | ||
55 | if (! system("test -f \"" f "\"")) { | ||
56 | return filename | ||
57 | } | ||
58 | print("Cannot find \"" filename "\" in " sp) > (STDERR ? STDERR : "/dev/stderr") | ||
59 | exit 1 | ||
60 | } | ||