about summary refs log tree commit diff stats
path: root/shin
diff options
context:
space:
mode:
Diffstat (limited to 'shin')
-rwxr-xr-xshin203
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}
11FNR == 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}
20function 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}
25function 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
40if [ "x$1" = -h ]; then 54_shin_build() ( # _shin_build FILE ...
41 cat <<\EOF 55 for file
42SHIN: include shell files in other shell files 56 do
43USAGE: 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
45FLAGS: 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
48PARAMETERS: 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 121shin() {
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
151SHIN DEBUGGING: ON
152VARIABLES:
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
64EOF 159EOF
65else 160 set -x
66 _shin "$@" 161 fi
67fi 162
163 "$_shin_func" "$@"
164}