about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Makefile15
-rw-r--r--README.md35
-rwxr-xr-xshin203
-rwxr-xr-xshin.awk60
-rwxr-xr-xshin.sh119
-rwxr-xr-xshinb7
6 files changed, 179 insertions, 260 deletions
diff --git a/Makefile b/Makefile index a9401a1..b6efa17 100644 --- a/Makefile +++ b/Makefile
@@ -1,10 +1,12 @@
1NAME = shin 1NAME = shin
2DESC = Include shell scripts in other shell scripts 2DESC = Include shell scripts in other shell scripts
3 3
4INSTALLED = shin shinb
5
4DESTDIR = 6DESTDIR =
5PREFIX = /usr/local 7PREFIX = /usr/local
6 8
7BIN = $(DESTDIR)$(PREFIX)/bin/$(NAME) 9BIN = $(DESTDIR)$(PREFIX)/bin
8 10
9.PHONY: help 11.PHONY: help
10help: 12help:
@@ -19,13 +21,16 @@ help:
19 @echo " uninstall Uninstall $(NAME)-related files." 21 @echo " uninstall Uninstall $(NAME)-related files."
20 22
21.PHONY: install 23.PHONY: install
22install: $(NAME) 24install: $(INSTALLED)
23 install -D $< $(BIN) 25 install -Dt $(BIN) $?
24 26
25.PHONY: link 27.PHONY: link
26link: 28link: $(NAME) shinb $(BIN)
27 ln -sf $(PWD)/$(NAME) $(BIN) 29 ln -sf $(PWD)/$(NAME) $(PWD)/shinb $(BIN)
28 30
29.PHONY: uninstall 31.PHONY: uninstall
30uninstall: 32uninstall:
31 rm $(BIN) 33 rm $(BIN)
34
35$(BIN):
36 mkdir -p $(BIN)
diff --git a/README.md b/README.md index 4b68c96..f6f03d8 100644 --- a/README.md +++ b/README.md
@@ -2,35 +2,24 @@
2## include shell files 2## include shell files
3 3
4built to scratch a personal itch. SHIN is an awk(1) script that plops include 4built to scratch a personal itch. SHIN is an awk(1) script that plops include
5files in to .shin files. here's the usage text: 5files in to .shin files.
6 6
7``` 7recently rewritten. now use it like this:
8SHIN: include shell files in other shell files
9USAGE: shin FILE...
10
11FILEs named FILE.shin will be built to FILE.sh in the same directory.
12to include files in shin files, use the following comment syntax:
13 8
14#< INCLUDE 9```sh
10#!/bin/sh
15 11
16shin will add INCLUDE below that comment, as well as a comment 12. shin # if shin is in $PATH this should Just Work(TM)
17denoting the end of INCLUDE.
18```
19 13
20it's really that simple. and stupid. 14do_stuff_here
21 15
22## configuration 16shin included_file
23 17
24SHIN will look for include files in the current directory, unless you set the 18other_stuff
25SHINPATH environment variable. $SHINPATH is a colon-separated list of 19```
26directories like $PATH or $MANPATH.
27
28## extras
29 20
30`shin.awk` is the plain awk script. because awk is dumb about handling 21you can run the above script itself, or you can "compile" it by running `shin -c <script>`, which will insert `included_file` in the correct spot in the file and remove the `. shin` line (only if it's by itself, which really it should be).
31arguments, `shin` is a shell script that will do `shin.awk`'s work unless you
32pass `shin -h`, in which case it will print a helpful message.
33 22
34## license 23## build a shell file
35 24
36SHIN is licensed under the FAIR license. see COPYING for details. 25use the program `shinb` to build a shell file on the command line. You don't need to include `-c` in the command line when calling `shinb`.
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}
diff --git a/shin.awk b/shin.awk deleted file mode 100755 index 80c76aa..0000000 --- a/shin.awk +++ /dev/null
@@ -1,60 +0,0 @@
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
6BEGIN {
7 if (ENVIRON["SHINPATH"]) {
8 split(ENVIRON["SHINPATH"], SHINPATH, ":")
9 } else {
10 SHINPATH[1] = "."
11 }
12}
13
14FNR == 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
34function 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
53function 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}
diff --git a/shin.sh b/shin.sh deleted file mode 100755 index 97f79e5..0000000 --- a/shin.sh +++ /dev/null
@@ -1,119 +0,0 @@
1#!/bin/sh
2# SHIN --- include files in shell scripts
3# Copyright (C) Case Duckworth <acdw@acdw.net>
4
5_shin_check_SHINPATH{) {
6 printf "%s\n" "${SHINPATH:-.}" |
7 tr : '\n' |
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
17}
18
19_shin_resolve_file(} {
20 f=
21 case "$1" in
22 ~/*) f="$HOME/${1#~/}" ;;
23 /*) f="/${1#/}" ;;
24 *) f="$(_shin_check_SHINPATH "$1")" ;;
25 esac
26
27 if test -f "$f"
28 then
29 printf '%s\n' "$f"
30 return 0
31 else
32 return 1
33 fi
34}
35
36_shin_include() { # _shin_include FILE ...
37 for lib
38 do
39 lib="$(_shin_resolve_file "$lib")"
40 test -f "$lib" && . "$lib"
41 done
42}
43
44_shin_build() ( # _shin_build FILE ...
45 temp=/tmp/shinf; trap 'rm "$temp"' INT EXIT QUIT
46 for file
47 do
48 file="$(_shin_resolve_file "$file")"
49
50 # Determine output file
51 if test -z "$outfile"
52 then
53 case "$file" in
54 # file.shin -> file.sh
55 *.shin) outfile="${file%in}" ;;
56 # file.sh -> file
57 *.sh) outfile="${file%.sh}" ;;
58 # file.ext -> file.ext.sh
59 *) outfile="$file.sh" ;;
60 esac
61 fi
62
63 while read -r line
64 do
65 case "$line" in
66 # skip lines where the user sources the library
67 .*shin) ;;
68 .*shin.sh) ;;
69 # where the user invokes shin, replace with file
70 shin*) # cursed
71 eval \
72 for file in ${line#shin}\; \
73 do \
74 test -f \"\$file\" \&\& \
75 cat \"\$file\"\; \
76 done
77 ;;
78 # else, print the line
79 *) printf '%s\n' "$line" ;;
80 esac
81 done < "$file" > "$temp"
82
83 if test "x$outfile" = x-
84 then # already output to standard out
85 cat "$temp"
86 continue
87 elif test $? -ne 0
88 then # uh oh we messed up
89 echo >&2 "shin: errors building."
90 echo >&2 "shin: part-built file: $temp"
91 elif test -f "$outfile"
92 then # can't overwrite sumthin
93 echo >&2 "shin: file already exists: $outfile"
94 echo >&2 "shin: part-built file: $temp"
95 else # move the temp file to the output
96 mv "$temp" "$outfile"
97 fi
98 done
99)
100
101shin() {
102 func=_shin_include
103 while getopts fio: OPT
104 do
105 case "$OPT" in
106 f) func=_shin_build ;;
107 i) func=_shin_include ;;
108 o) # output file implies build.
109 func=_shin_build
110 outputfile="$OPTARG"
111 ;;
112 *) exit 1 ;;
113 esac
114 done
115 shift $((OPTIND - 1))
116 OPTIND=0
117
118 "$func" "$@"
119}
diff --git a/shinb b/shinb new file mode 100755 index 0000000..855b153 --- /dev/null +++ b/shinb
@@ -0,0 +1,7 @@
1#!/bin/sh
2
3. shin
4
5_shin_func=_shin_build
6
7shin "$@"