diff options
Diffstat (limited to 'shin')
-rwxr-xr-x | shin | 203 |
1 files changed, 150 insertions, 53 deletions
diff --git a/shin b/shin index 9ba488b..ddd9f0e 100755 --- a/shin +++ b/shin | |||
@@ -1,67 +1,164 @@ | |||
1 | #!/bin/sh | 1 | #!/bin/sh |
2 | # SHIN v. 0/1 | 2 | # SHIN --- include files in shell scripts |
3 | # Copyright (C) Case Duckworth <acdw@acdw.net> | 3 | # Copyright (C) Case Duckworth <acdw@acdw.net> |
4 | # Licensed under the Fair License. See COPYING for details. | ||
5 | 4 | ||
6 | _shin() { | 5 | _shin_check_SHINPATH() { |
7 | awk 'BEGIN { | 6 | printf "%s\n" "${SHINPATH:-.}" | |
8 | if (ENVIRON["SHINPATH"]) split(ENVIRON["SHINPATH"], SHINPATH, ":") | 7 | tr : '\n' | |
9 | else SHINPATH[1] = "." | 8 | while read -r path |
9 | do | ||
10 | if test -f "$path/$1" | ||
11 | then | ||
12 | printf '%s\n' "$path/$1" | ||
13 | return 0 | ||
14 | fi | ||
15 | done | ||
16 | return 1 | ||
10 | } | 17 | } |
11 | FNR == 1 { outfile = FILENAME; sub(/in$/, "", outfile) } | 18 | |
12 | { print($0) > outfile } | 19 | _shin_resolve_file() { |
13 | /^#</ { | 20 | f= |
14 | inclfile = shin_resolve(substr($0, 3)) | 21 | case "$1" in |
15 | while ((getline l < inclfile) > 0) print(l) > outfile | 22 | ~/*) f="$HOME/${1#~/}" ;; |
16 | close(inclfile) | 23 | /*) f="/${1#/}" ;; |
17 | sub(/</, ">", $0) | 24 | *) f="$(_shin_check_SHINPATH "$1")" ;; |
18 | print > outfile | 25 | esac |
26 | |||
27 | if test -f "$f" | ||
28 | then | ||
29 | printf '%s\n' "$f" | ||
30 | elif test -f "$f.sh" | ||
31 | then | ||
32 | printf '%s\n' "$f.sh" | ||
33 | else | ||
34 | return 1 | ||
35 | fi | ||
36 | return 0 | ||
19 | } | 37 | } |
20 | function shin_test(filename) { | 38 | |
21 | if (! system("test -f \"" f "\"")) return filename | 39 | _shin_include() { # _shin_include FILE ... |
22 | print("Cannot find \"" filename "\" in " sp) > (STDERR ? STDERR : "/dev/stderr") | 40 | for lib |
23 | exit 1 | 41 | do |
42 | lib="$(_shin_resolve_file "$lib")" | ||
43 | test -f "$lib" && . "$lib" | ||
44 | done | ||
24 | } | 45 | } |
25 | function shin_resolve(filename) { | 46 | |
26 | if (match(filename, "^/")) return shin_test(filename) | 47 | _shin_cleanup() { |
27 | if (match(filename, "^~")) return shin_test(ENVIRON["HOME"] "/" substr(filename, 2)) | 48 | if "$_shin_rmtemp" && test -f "$_shin_temp" |
28 | sub(/^[ \t]*/, "", filename) | 49 | then |
29 | sub(/[ \t]*$/, "", filename) | 50 | rm -f "$_shin_temp" |
30 | sp = "" | 51 | fi |
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 | } | 52 | } |
39 | 53 | ||
40 | if [ "x$1" = -h ]; then | 54 | _shin_build() ( # _shin_build FILE ... |
41 | cat <<\EOF | 55 | for file |
42 | SHIN: include shell files in other shell files | 56 | do |
43 | USAGE: shin [-h] FILE... | 57 | file="$(_shin_resolve_file "$file")" |
58 | |||
59 | # Determine output file | ||
60 | if test -z "$_shin_output" | ||
61 | then | ||
62 | case "$file" in | ||
63 | # file.shin -> file.sh | ||
64 | *.shin) _shin_output="${file%in}" ;; | ||
65 | # file.sh -> file | ||
66 | *.sh) _shin_output="${file%.sh}" ;; | ||
67 | # file.ext -> file.ext.sh | ||
68 | *) _shin_output="$file.sh" ;; | ||
69 | esac | ||
70 | fi | ||
44 | 71 | ||
45 | FLAGS: | 72 | while read -r line |
46 | -h Show this help and exit. | 73 | do |
74 | case "$line" in | ||
75 | # skip lines where the user sources the library | ||
76 | .*shin) ;; | ||
77 | .*shin.sh) ;; | ||
78 | # where the user invokes shin, replace with file | ||
79 | shin*) # cursed | ||
80 | eval \ | ||
81 | for file in ${line#shin}\; \ | ||
82 | do \ | ||
83 | test -f \"\$file\" \&\& \ | ||
84 | sed \"/^#!/d\" \"\$file\"\; \ | ||
85 | done | ||
86 | ;; | ||
87 | # else, print the line | ||
88 | *) printf '%s\n' "$line" ;; | ||
89 | esac | ||
90 | done < "$file" > "$_shin_temp" | ||
47 | 91 | ||
48 | PARAMETERS: | 92 | ec=$? |
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 | 93 | ||
53 | #< INCLUDE | 94 | if test "x$_shin_output" = x- |
95 | then # already output to standard out | ||
96 | cat "$_shin_temp" | ||
97 | continue | ||
98 | elif test $ec -ne 0 | ||
99 | then # uh oh we messed up | ||
100 | echo >&2 "shin: errors building." | ||
101 | echo >&2 "shin: part-built file: $_shin_temp" | ||
102 | _shin_rmtemp=false | ||
103 | elif test -f "$_shin_output" && ! "$_shin_force" | ||
104 | then # can't overwrite sumthin without -f | ||
105 | echo >&2 "shin: file already exists: $_shin_output" | ||
106 | echo >&2 "shin: part-built file: $_shin_temp" | ||
107 | _shin_rmtemp=false | ||
108 | else # move the temp file to the output | ||
109 | if mv "$_shin_temp" "$_shin_output" | ||
110 | then | ||
111 | printf >&2 '%s\n' "$_shin_output" | ||
112 | else | ||
113 | echo >&2 "shin: errors moving $_shin_output" | ||
114 | echo >&2 "shin: part-built file: $_shin_temp" | ||
115 | _shin_rmtemp=false | ||
116 | fi | ||
117 | fi | ||
118 | done | ||
119 | ) | ||
54 | 120 | ||
55 | If INCLUDE begins with / or ~, it's taken as a literal | 121 | shin() { |
56 | file name; otherwise SHINPATH will be searched for the | 122 | _shin_func="${_shin_func:-_shin_include}" |
57 | file. SHINPATH is a colon-separated list of paths | 123 | _shin_temp=/tmp/shinf |
58 | like $PATH. If SHINPATH is unset, it defaults to the | 124 | _shin_rmtemp=true |
59 | current directory. | 125 | _shin_force=false |
126 | _shin_debug=false | ||
60 | 127 | ||
61 | If INCLUDE is not found, shin will error and quit. | 128 | trap _shin_cleanup INT EXIT QUIT |
62 | Otherwise, shin will add INCLUDE below that comment, | 129 | |
63 | as well as a comment denoting the end of the INCLUDE. | 130 | while getopts dfcio: OPT |
131 | do | ||
132 | case "$OPT" in | ||
133 | c) _shin_func=_shin_build ;; | ||
134 | i) _shin_func=_shin_include ;; | ||
135 | o) # output file implies build. | ||
136 | _shin_func=_shin_build | ||
137 | _shin_output="$OPTARG" | ||
138 | echo "$_shin_output" | ||
139 | ;; | ||
140 | f) _shin_force=true ;; | ||
141 | d) _shin_debug=true ;; | ||
142 | *) exit 1 ;; | ||
143 | esac | ||
144 | done | ||
145 | shift $((OPTIND - 1)) | ||
146 | OPTIND=0 | ||
147 | |||
148 | if "$_shin_debug" | ||
149 | then | ||
150 | cat >&2 <<EOF | ||
151 | SHIN DEBUGGING: ON | ||
152 | VARIABLES: | ||
153 | _shin_func $_shin_func | ||
154 | _shin_output $_shin_output | ||
155 | _shin_temp $_shin_temp | ||
156 | |||
157 | _shin_rmtemp $_shin_rmtemp | ||
158 | _shin_force $_shin_force | ||
64 | EOF | 159 | EOF |
65 | else | 160 | set -x |
66 | _shin "$@" | 161 | fi |
67 | fi | 162 | |
163 | "$_shin_func" "$@" | ||
164 | } | ||