about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorCase Duckworth2020-05-22 08:38:40 -0500
committerCase Duckworth2020-05-22 08:38:40 -0500
commit6cce4bd5e7e89406c0ca5942076c95c458bba2dc (patch)
tree89e5a9393b5c99aa2a6eb0fc87e9e97e7ec84e57
downloadbollux-6cce4bd5e7e89406c0ca5942076c95c458bba2dc.tar.gz
bollux-6cce4bd5e7e89406c0ca5942076c95c458bba2dc.zip
Ready to upload
-rw-r--r--README.md7
-rwxr-xr-xbollux191
-rwxr-xr-xbollux.sh264
-rwxr-xr-xpage48
-rw-r--r--test16
-rw-r--r--winch13
-rwxr-xr-xwrap31
7 files changed, 570 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..78723be --- /dev/null +++ b/README.md
@@ -0,0 +1,7 @@
1# bollux - a bash Gemini server
2
3inspired by
4
5- [birch](https://github.com/dylanaraps/birch)
6- [castor](https://git.sr.ht/~julienxx/castor)
7- [gemini](https://gemini.circumlunar.space/)
diff --git a/bollux b/bollux new file mode 100755 index 0000000..50246c9 --- /dev/null +++ b/bollux
@@ -0,0 +1,191 @@
1#!/bin/bash
2# bollux: bash gemini client
3# Author: Case Duckworth <acdw@acdw.net>
4# License: MIT
5# Version: -1
6
7PRGN="${0##*/}"
8
9main() {
10 if [[ -z "$1" ]]; then
11 echo "usage: $PRGN <URL>"
12 return 1
13 fi
14
15 log "$PRGN $*"
16 log address="$(address "$1"|tr '[:space:]' 'x')"
17 log server="$(server "$1")"
18
19 address "$1" |
20 download "$(server "$1")" 2>/dev/null |
21 handle_status "$1"
22}
23
24log() {
25 printf '\e[34m%s:\t%s\e[0m\n' "$PRGN" "$*" >&2
26}
27
28address() {
29 local addr="$1"
30 if [[ "$addr" != gemini://* ]]; then
31 addr="gemini://$addr"
32 fi
33 echo "$addr" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
34}
35
36server() {
37 local serv="${1#*://}"
38 serv="${serv%%/*}"
39 if [[ ! "$serv" = *:* ]]; then
40 serv="$serv:1965"
41 fi
42 echo "$serv"
43}
44
45download() {
46 openssl s_client -crlf -ign_eof -quiet -connect "$1"
47}
48
49display() {
50 addr="$(address "$1")"
51 printf ' 🚀 %s 🚀\n' "$addr"
52 echo
53 cat
54 printf ' 🚀 END: %s 🚀\n' "$addr"
55}
56
57NOT_IMPLEMENTED() {
58 log "NOT IMPLEMENTED!!!" >&2
59 exit 127
60}
61NOT_FULLY_IMPLEMENTED() {
62 log "NOT FULLY IMPLEMENTED!!!" >&2
63}
64
65handle_status() {
66 # cf. https://gemini.circumlunar.space/docs/spec-spec.txt
67 addr="$(address "$1")"
68 IN="$(cat)"
69 head="$(head -n1 <<<"$IN")"
70 stat="$(awk '{print $1}' <<<"$head")"
71 msg="$(awk '{for(i=2;i<=NF;i++)printf "%s ", $i;printf "\n";}' <<<"$head")"
72 log "$stat $msg"
73 case "$stat" in
74 10) # INPUT
75 # As per definition of single-digit code 1 in 1.3.2.
76 NOT_IMPLEMENTED
77 ;;
78 20) # SUCCESS
79 # As per definition of single-digit code 2 in 1.3.2.
80 display "$addr" <<<"$IN"
81 ;;
82 21) # SUCCESS - END OF CLIENT CERTIFICATE SESSION
83 # The request was handled successfully and a response body will
84 # follow the response header. The <META> line is a MIME media
85 # type which applies to the response body. In addition, the
86 # server is signalling the end of a transient client certificate
87 # session which was previously initiated with a status 61
88 # response. The client should immediately and permanently
89 # delete the certificate and accompanying private key which was
90 # used in this request.
91 display "$addr" <<<"$IN"
92 NOT_FULLY_IMPLEMENTED
93 ;;
94 30) # REDIRECT - TEMPORARY
95 # As per definition of single-digit code 3 in 1.3.2.
96 exec "$0" "$msg"
97 ;;
98 31) # REDIRECT - PERMANENT
99 # The requested resource should be consistently requested from
100 # the new URL provided in future. Tools like search engine
101 # indexers or content aggregators should update their
102 # configurations to avoid requesting the old URL, and end-user
103 # clients may automatically update bookmarks, etc. Note that
104 # clients which only pay attention to the initial digit of
105 # status codes will treat this as a temporary redirect. They
106 # will still end up at the right place, they just won't be able
107 # to make use of the knowledge that this redirect is permanent,
108 # so they'll pay a small performance penalty by having to follow
109 # the redirect each time.
110 exec "$0" "$msg"
111 NOT_FULLY_IMPLEMENTED
112 ;;
113 4*) # 40 - TEMPORARY FAILURE
114 # As per definition of single-digit code 4 in 1.3.2.
115 # 41 - SERVER UNAVAILABLE
116 # The server is unavailable due to overload or maintenance.
117 # (cf HTTP 503)
118 # 42 - CGI ERROR
119 # A CGI process, or similar system for generating dynamic
120 # content, died unexpectedly or timed out.
121 # 43 - PROXY ERROR
122 # A proxy request failed because the server was unable to
123 # successfully complete a transaction with the remote host.
124 # (cf HTTP 502, 504)
125 # 44 - SLOW DOWN
126 # Rate limiting is in effect. <META> is an integer number of
127 # seconds which the client must wait before another request is
128 # made to this server.
129 # (cf HTTP 429)
130 printf 'OH SHIT!\n%s\t%s\n' "$stat" "$msg" | display "$addr"
131 NOT_FULLY_IMPLEMENTED
132 ;;
133 5*) # 50 - PERMANENT FAILURE
134 # As per definition of single-digit code 5 in 1.3.2.
135 # 51 - NOT FOUND
136 # The requested resource could not be found but may be available
137 # in the future.
138 # (cf HTTP 404)
139 # (struggling to remember this important status code? Easy:
140 # you can't find things hidden at Area 51!)
141 # 52 - GONE
142 # The resource requested is no longer available and will not be
143 # available again. Search engines and similar tools should
144 # remove this resource from their indices. Content aggregators
145 # should stop requesting the resource and convey to their human
146 # users that the subscribed resource is gone.
147 # (cf HTTP 410)
148 # 53 - PROXY REQUEST REFUSED
149 # The request was for a resource at a domain not served by the
150 # server and the server does not accept proxy requests.
151 # 59 - BAD REQUEST
152 # The server was unable to parse the client's request,
153 # presumably due to a malformed request.
154 # (cf HTTP 400)
155 printf 'OH SHIT!\n%s\t%s\n' "$stat" "$msg" | display "$addr"
156 NOT_FULLY_IMPLEMENTED
157 ;;
158 6*) # 60 - CLIENT CERTIFICATE REQUIRED
159 # As per definition of single-digit code 6 in 1.3.2.
160 # 61 - TRANSIENT CERTIFICATE REQUESTED
161 # The server is requesting the initiation of a transient client
162 # certificate session, as described in 1.4.3. The client should
163 # ask the user if they want to accept this and, if so, generate
164 # a disposable key/cert pair and re-request the resource using it.
165 # The key/cert pair should be destroyed when the client quits,
166 # or some reasonable time after it was last used (24 hours?
167 # Less?)
168 # 62 - AUTHORISED CERTIFICATE REQUIRED
169 # This resource is protected and a client certificate which the
170 # server accepts as valid must be used - a disposable key/cert
171 # generated on the fly in response to this status is not
172 # appropriate as the server will do something like compare the
173 # certificate fingerprint against a white-list of allowed
174 # certificates. The client should ask the user if they want to
175 # use a pre-existing certificate from a stored "key chain".
176 # 63 - CERTIFICATE NOT ACCEPTED
177 # The supplied client certificate is not valid for accessing the
178 # requested resource.
179 # 64 - FUTURE CERTIFICATE REJECTED
180 # The supplied client certificate was not accepted because its
181 # validity start date is in the future.
182 # 65 - EXPIRED CERTIFICTE REJECTED
183 # The supplied client certificate was not accepted because its
184 # expiry date has passed.
185 printf 'OH SHIT!\n%s\t%s\n' "$stat" "$msg" | display "$addr"
186 NOT_FULLY_IMPLEMENTED
187 ;;
188 esac
189}
190
191main "$@"
diff --git a/bollux.sh b/bollux.sh new file mode 100755 index 0000000..84786b6 --- /dev/null +++ b/bollux.sh
@@ -0,0 +1,264 @@
1#!/usr/bin/env bash
2# bollux: bash gemini client
3# Author: Case Duckworth <acdw@acdw.net>
4# License: MIT
5# Version: -0.9
6
7PRGN="${0##*/}"
8PORT=1965
9LOG_LEVEL=3 # higher=more important.
10
11clean() {
12 # '\e[?7h': re-enable line wrapping
13 # '\e[2J': clear the screen
14 # '\e[;r': reset the scroll area
15 # '\e[?1049l': swap back to primary screen
16 printf '\e[?7h\e[2J\e[;r\e[?1049l'
17 exit
18}
19
20refresh() {
21 # grab the terminal size
22 shopt -s checkwinsize
23 (
24 :
25 :
26 )
27 # '\e[?1049h': Swap to the alternate buffer.
28 # '\e[?7l': Disable line wrapping.
29 # '\e[2J': Clear the screen.
30 # '\e[3;%sr': Set the scroll area.
31 # '\e[999H': Move the cursor to the bottom.
32 printf '\e[?1049h\e[?7l\e[2J\e[3;%sr\e[999H' "$((LINES - 1))"
33}
34
35resize() {
36 refresh
37 # '\e7': Save the cursor position.
38 # '\e[?25l': Hide the cursor.
39 # '\r': Move the cursor to column 0.
40 # '\e[999B': Move the cursor to the bottom.
41 # '\e[A': Move the cursor up a line.
42 printf '\e7\e[?25l\r\e[999B\e[A'
43}
44
45bollux() {
46 if [[ -n "$1" ]]; then
47 loc="$1"
48 else
49 read -rp "GO> " loc
50 fi
51
52 log 2 "location: $loc"
53 log 2 "address: $(address "$loc")"
54 log 2 "server: $(server "$loc")"
55
56 address "$loc" |
57 download "$(server "$loc")" |
58 handle
59}
60
61log() {
62 case "$1" in
63 [0-9]*)
64 lvl="$1"
65 shift
66 ;;
67 *) lvl=5 ;;
68 esac
69 if ((lvl >= LOG_LEVEL)); then
70 if [[ "$2" == - ]]; then
71 while IFS= read -r line; do
72 printf '\e[33m%s\e[0m:\t%s\n' "$PRGN" "$line" >&2
73 done
74 else
75 printf '\e[34m%s\e[0m:\t%s\n' "bollux" "$*" >&2
76 fi
77 fi
78}
79
80download() {
81 # usage:
82 # echo REQUEST | download SERVER
83 # download SERVER REQUEST
84 serv="$1"
85 req=
86 if (($# == 2)); then
87 req="$2"
88 else
89 req="$(cat)"
90 fi
91 t="$(mktemp)"
92 openssl s_client -crlf -ign_eof -quiet -connect "$serv" <<<"$req" 2>"$t"
93 log 1 <"$t"
94 rm "$t"
95}
96
97address() {
98 addr="$1"
99 if [[ "$addr" != gemini://* ]]; then
100 addr="gemini://$addr"
101 fi
102 echo "$addr" | trim
103}
104
105server() {
106 serv="${1#*://}"
107 serv="${serv%%/*}"
108 if [[ "$serv" != *:* ]]; then
109 serv="$serv:$PORT"
110 fi
111 echo "$serv" | trim
112}
113
114trim() {
115 sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
116}
117
118display() {
119 echo
120 cat
121 echo
122}
123
124handle() {
125 # cf. gemini://gemini.circumlunar.space/docs/spec-spec.txt
126 resp="$(cat)"
127 head="$(head -n1 <<<"$resp")"
128 body="$(tail -n+2 <<<"$resp")"
129 stat="$(awk '{print $1}' <<<"$head")"
130 smsg="$(
131 awk '{for(i=2;i<=NF;i++)printf "%s ",$i;printf "\n";}' <<<"$head"
132 )"
133
134 log "[$stat] $smsg"
135
136 case "$stat" in
137 10) # INPUT
138 # As per definition of single-digit code 1 in 1.3.2.
139 NOT_IMPLEMENTED
140 ;;
141 20) # SUCCESS
142 # As per definition of single-digit code 2 in 1.3.2.
143 display <<<"$body"
144 ;;
145 21) # SUCCESS - END OF CLIENT CERTIFICATE SESSION
146 # The request was handled successfully and a response body will
147 # follow the response header. The <META> line is a MIME media
148 # type which applies to the response body. In addition, the
149 # server is signalling the end of a transient client certificate
150 # session which was previously initiated with a status 61
151 # response. The client should immediately and permanently
152 # delete the certificate and accompanying private key which was
153 # used in this request.
154 display <<<"$body"
155 NOT_FULLY_IMPLEMENTED
156 ;;
157 30) # REDIRECT - TEMPORARY
158 # As per definition of single-digit code 3 in 1.3.2.
159 exec "$0" "$smsg"
160 ;;
161 31) # REDIRECT - PERMANENT
162 # The requested resource should be consistently requested from
163 # the new URL provided in future. Tools like search engine
164 # indexers or content aggregators should update their
165 # configurations to avoid requesting the old URL, and end-user
166 # clients may automatically update bookmarks, etc. Note that
167 # clients which only pay attention to the initial digit of
168 # status codes will treat this as a temporary redirect. They
169 # will still end up at the right place, they just won't be able
170 # to make use of the knowledge that this redirect is permanent,
171 # so they'll pay a small performance penalty by having to follow
172 # the redirect each time.
173 exec "$0" "$smsg"
174 NOT_FULLY_IMPLEMENTED
175 ;;
176 4*) # 40 - TEMPORARY FAILURE
177 # As per definition of single-digit code 4 in 1.3.2.
178 # 41 - SERVER UNAVAILABLE
179 # The server is unavailable due to overload or maintenance.
180 # (cf HTTP 503)
181 # 42 - CGI ERROR
182 # A CGI process, or similar system for generating dynamic
183 # content, died unexpectedly or timed out.
184 # 43 - PROXY ERROR
185 # A proxy request failed because the server was unable to
186 # successfully complete a transaction with the remote host.
187 # (cf HTTP 502, 504)
188 # 44 - SLOW DOWN
189 # Rate limiting is in effect. <META> is an integer number of
190 # seconds which the client must wait before another request is
191 # made to this server.
192 # (cf HTTP 429)
193 printf 'OH SHIT!\n%s\t%s\n' "$stat" "$smsg" >&2
194 NOT_IMPLEMENTED
195 ;;
196 5*) # 50 - PERMANENT FAILURE
197 # As per definition of single-digit code 5 in 1.3.2.
198 # 51 - NOT FOUND
199 # The requested resource could not be found but may be available
200 # in the future.
201 # (cf HTTP 404)
202 # (struggling to remember this important status code? Easy:
203 # you can't find things hidden at Area 51!)
204 # 52 - GONE
205 # The resource requested is no longer available and will not be
206 # available again. Search engines and similar tools should
207 # remove this resource from their indices. Content aggregators
208 # should stop requesting the resource and convey to their human
209 # users that the subscribed resource is gone.
210 # (cf HTTP 410)
211 # 53 - PROXY REQUEST REFUSED
212 # The request was for a resource at a domain not served by the
213 # server and the server does not accept proxy requests.
214 # 59 - BAD REQUEST
215 # The server was unable to parse the client's request,
216 # presumably due to a malformed request.
217 # (cf HTTP 400)
218 printf 'OH SHIT!\n%s\t%s\n' "$stat" "$smsg" >&2
219 NOT_IMPLEMENTED
220 ;;
221 6*) # 60 - CLIENT CERTIFICATE REQUIRED
222 # As per definition of single-digit code 6 in 1.3.2.
223 # 61 - TRANSIENT CERTIFICATE REQUESTED
224 # The server is requesting the initiation of a transient client
225 # certificate session, as described in 1.4.3. The client should
226 # ask the user if they want to accept this and, if so, generate
227 # a disposable key/cert pair and re-request the resource using it.
228 # The key/cert pair should be destroyed when the client quits,
229 # or some reasonable time after it was last used (24 hours?
230 # Less?)
231 # 62 - AUTHORISED CERTIFICATE REQUIRED
232 # This resource is protected and a client certificate which the
233 # server accepts as valid must be used - a disposable key/cert
234 # generated on the fly in response to this status is not
235 # appropriate as the server will do something like compare the
236 # certificate fingerprint against a white-list of allowed
237 # certificates. The client should ask the user if they want to
238 # use a pre-existing certificate from a stored "key chain".
239 # 63 - CERTIFICATE NOT ACCEPTED
240 # The supplied client certificate is not valid for accessing the
241 # requested resource.
242 # 64 - FUTURE CERTIFICATE REJECTED
243 # The supplied client certificate was not accepted because its
244 # validity start date is in the future.
245 # 65 - EXPIRED CERTIFICTE REJECTED
246 # The supplied client certificate was not accepted because its
247 # expiry date has passed.
248 printf 'OH SHIT!\n%s\t%s\n' "$stat" "$smsg" >&2
249 NOT_IMPLEMENTED
250 ;;
251 esac
252}
253
254NOT_IMPLEMENTED() {
255 log "NOT IMPLEMENTED!!!" >&2
256 exit 127
257}
258NOT_FULLY_IMPLEMENTED() {
259 log "NOT FULLY IMPLEMENTED!!!" >&2
260}
261
262if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
263 bollux "$@"
264fi
diff --git a/page b/page new file mode 100755 index 0000000..c478d7a --- /dev/null +++ b/page
@@ -0,0 +1,48 @@
1#!/bin/bash
2# pager
3
4. ./wrap
5
6cleanup() {
7 tput reset
8 exit
9}
10
11refresh() {
12 shopt -s checkwinsize
13 (
14 :
15 :
16 )
17 printf '\e[?1049h\e?7l\e[2J\e[3;%sr\e[999H' "$((LINES - 1))"
18}
19
20resize() {
21 refresh
22
23 printf '\e7\e[?25l\e[H'
24 _wrap "$file"
25 printf '\e[999H\e[?25h'
26}
27
28_wrap() {
29 wrap "$COLUMNS" <"$1"
30}
31
32main() {
33 refresh
34
35 file="$1"
36 resize
37
38 trap resize WINCH
39 trap cleanup INT
40
41 while :; do
42 :
43 done
44}
45
46if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
47 main "$@"
48fi
diff --git a/test b/test new file mode 100644 index 0000000..adbf833 --- /dev/null +++ b/test
@@ -0,0 +1,16 @@
1Your bones don't break, mine do. That's clear. Your cells react to bacteria and viruses differently than mine. You don't get sick, I do. That's also clear. But for some reason, you and I react the exact same way to water. We swallow it too fast, we choke. We get some in our lungs, we drown. However unreal it may seem, we are connected, you and I. We're on the same curve, just on opposite ends.
2
3Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю Съешь же ещё этих мягких французских булок, да выпей чаю
4
5写ラんごる仏際定諭ムトウヨ検発ホキナシ属沢こずドら除賞ソキイヨ整報ラケト堀生ばす見戦おうたみ意入ネヘ成43差リ旅世がうリト内処輪察るドを。成ラ定迎るに難討事子よまゅや名6汗ミ制要質ぴご日歩ル古捕際くクそを経日そた報底肉くも。入ち大5員ム食激シ負未ミ夜常ヲメヱム拒手ヨヌクユ街先為ち太要ゅ復大ばゅでく安3会就携ヘキ活郵ゆぼ光属がにぐ能表キヨニ覃月スエマ経協たそぽ。
6
7航ーい町9久ょな丸断ぱ見下ら料75載と談作ドけおッ信田チ子初分著ルハユツ統真レ供約的治チシ最板植稿息き。禁ユセカヱ資事オ価界願の安貸めげさす薬記もー掲小リナマ止全たじつ警際著ムタヱス社4質で別五比智ねおこつ。87上え応政でルトは商構ヨカ能件カハ万禁かこり優斜もさばぞ帳渡レマコヱ月委ノメ冬況殿69上ぼょきほ部芝亨拙リ。
8
9川よリっへ政少たにやし済手け安宗ずょ年常だて社行受ニリ問済えゃ前石評リレ属価び州幕なぞてほ知害さら月平エス権芸マ裏臍マ護躍るたス報裁再能労め。起ル著目ちが条舞ばよ黙明イ王王こざ絵49金はっき書牛三県ムア堀47此カ会橋君恵康ほイす。変フアハ省葉禁ト需気わ医42講レヲミア米細ト抗選席ゆ京9料こゆ焼深タフカサ側氷モムワマ応7祭ず。
10
11ლორემ იფსუმ დოლორ სით ამეთ, ეა მედიოცრემ თჰეოფჰრასთუს სით. ნო დუო ჰინც დიცით თამყუამ. ენიმ ზრილ ფერ ეა, ასსუმ ნოსთრუმ ფერციფით ვის იდ. სედ სუმმო ფრიმის თე, ეამ ნო ენიმ მოლესთიაე. ფაცილისის ფერიცულის მეა ინ, ფრი ეა დეცორე ცომფლეცთითურ. ეამ იუვარეთ ვივენდო სცრიფთორემ ეუ, ნამ ეხ იგნოთა ვერთერემ, ეხ დუო ეროს სოლეთ.
12
13უთ უნუმ ფაულო ასსუევერით ჰას, სეა ჰინც სანცთუს ფუისსეთ თე. მეი ნონუმy ნუმყუამ ეთ, თოლლით მუნერე ინსოლენს ეუ დუო. ლაბორე დელეცთუს ირაცუნდია ყუი ეა. ყუო ლეგერე ვერეარ დიცერეთ იდ, ნოვუმ ვიდერერ ოფფიციის ეი სით.
14
15ფრო ნულლამ ფერციფითურ სცრიფსერით ნო. დელეცთუს აცცომმოდარე იდ ნეც, იისყუე ვულფუთათე ნე ჰის. ეროს უბიყუე იმფედით ან ნეც, თიმეამ ადმოდუმ ფრაესენთ მელ ან. თალე ენიმ ყუი ნე, ყუი ფონდერუმ ადიფისცინგ ვითუფერათა ეი. ეუ ფოფულო გრაეცი ფერთინახ ფრი.
16
diff --git a/winch b/winch new file mode 100644 index 0000000..845aa57 --- /dev/null +++ b/winch
@@ -0,0 +1,13 @@
1#!/usr/bin/env bash
2
3winch() {
4shopt -s checkwinsize; (:;:)
5
6printf '\e[?25l\e[H\e[K%s %sx%s\e[8' "winch!" "$COLUMNS" "$LINES"
7}
8
9trap winch WINCH
10
11while : ; do
12 :
13done
diff --git a/wrap b/wrap new file mode 100755 index 0000000..011aa58 --- /dev/null +++ b/wrap
@@ -0,0 +1,31 @@
1#!/usr/bin/env bash
2
3wrap() {
4 local width="$1"
5 local len=0
6
7 while read -r -a line; do
8 for word in "${line[@]}"; do
9 ((len += "${#word}" + 1))
10 #printf '%s' "$len"
11 if ((len >= width)); then
12 printf '\n'
13 # ruler "$width"
14 len=${#word}
15 fi
16 printf '%s ' "$word"
17 done
18 done
19 printf '\n'
20}
21
22ruler() {
23 for ((i = 0; i < $1; i++)); do
24 printf '%s' "${2:--}"
25 done
26 printf '\n'
27}
28
29if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
30 wrap "$@"
31fi