Skip to content

Commit a14b704

Browse files
committed
Simplify deps mgmt; add events DSL
1 parent a0af621 commit a14b704

File tree

4 files changed

+202
-34
lines changed

4 files changed

+202
-34
lines changed

.dkrc

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
#!/usr/bin/env bash
22

33
# Use cram and watch for testing, and provide a shell console
4-
dk use: cram reflex-watch shell-console bash32 shellcheck
4+
dk use: cram modd-watch shell-console bash32 shellcheck
55

6-
on boot require mdsh github bashup/mdsh master bin/mdsh
7-
on boot require loco github bashup/loco master bin/loco
8-
on boot require realpaths github bashup/realpaths master realpaths
9-
on boot require bashup.events github bashup/events master bashup.events
10-
11-
on build mdsh --out dk --compile dk.md
12-
on build chmod +x dk
6+
~ on build && {{
7+
- mdsh --out dk --compile dk.md
8+
- chmod +x dk
9+
}}
1310

1411
unwatch dk '**/.~*.md' '**/*.err'
1512
watch+ -- dk build

dk

+165-17
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@
1919
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
2020
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

22+
#!/bin/bash
23+
dsl:(){ ((!$#))||local __dsl__=("$@");${__blk__:+::};}
24+
shopt -q expand_aliases||{ unalias -a;shopt -s expand_aliases;};builtin alias\
25+
+='{ ::__;::(){ ((!$#))||{ local __blarg__=("$@");shift;"${__dsl__[@]-::no-dsl}" + '\
26+
~='{ ::__;::(){ ((!$#))||{ local __blarg__=("$@");shift; '\
27+
'{{=return;return;};__blk__=;set -- "${__blarg__[@]:1}"; ' '}}=};__blk__=1;:: . "$@";__::;}'\
28+
-='"${__dsl__[@]-::no-dsl}" - '
29+
::__(){ __bstk__[__bsp__++]="${__blk__:+__blk__=1;$(declare -f ::)}";}
30+
__::(){ local s=$?;__blk__=;${__bstk__[--__bsp__]:+eval "${__bstk__[__bsp__]}"};return $s;}
31+
__bsp__=;:${__dsl__+}
2232
#!/usr/bin/env bash
2333

2434
realpath.location(){ realpath.follow "$1"; realpath.absolute "$REPLY" ".."; }
@@ -163,41 +173,169 @@ done
163173
for lv in ${!LOCO_@}; do unset "$lv"; done
164174
165175
LOCO_SCRIPT=$0
176+
{ if [[ $OSTYPE != cygwin && $OSTYPE != msys && -e /dev/fd/0 ]]; then source /dev/fd/0; else source <(cat); fi; } <<'# --- EOF dotenv ---'
166177
#!/usr/bin/env bash
167-
event(){ case $1 in error|quote|encode);; *)
168-
__ev.encode "${2-}";local f n='' e=bashup_event_$REPLY;f=${e/event/flag}
178+
179+
__dotenv=
180+
__dotenv_file=
181+
__dotenv_cmd=.env
182+
183+
.env() {
184+
REPLY=()
185+
[[ $__dotenv_file || ${1-} == -* ]] || .env.--file .env || return
186+
if declare -F -- ".env.${1-}" >/dev/null; then .env."$@"; return ; fi
187+
.env --help >&2; return 64
188+
}
189+
190+
.env.-f() { .env.--file "$@"; }
191+
.env.-h() { .env.--help "$@"; }
192+
.env.--help() {
193+
echo "Usage:
194+
$__dotenv_cmd [-f|--file FILE] COMMAND [ARGS...]
195+
$__dotenv_cmd -h|--help
196+
197+
Options:
198+
-f, --file FILE Use a file other than .env
199+
200+
Read Commands:
201+
get KEY Get raw value of KEY (or fail)
202+
parse [KEY...] Get trimmed KEY=VALUE lines for named keys (or all)
203+
export [KEY...] Export the named keys (or all) in shell format
204+
205+
Write Commands:
206+
set [+]KEY[=VALUE]... Set or unset values (in-place w/.bak); + sets default
207+
puts STRING Append STRING to the end of the file
208+
generate KEY [CMD...] Set KEY to the output of CMD unless it already exists;
209+
return the new or existing value."
210+
}
211+
212+
.env.get() {
213+
.env::arg "get requires a key" "$@" &&
214+
[[ "$__dotenv" =~ ^(.*(^|$'\n'))([ ]*)"$1="(.*)$ ]] &&
215+
REPLY=${BASH_REMATCH[4]%%$'\n'*} && REPLY=${REPLY%"${REPLY##*[![:space:]]}"}
216+
}
217+
218+
.env.parse() {
219+
local line key
220+
while IFS= read -r line; do
221+
line=${line#"${line%%[![:space:]]*}"} # trim leading whitespace
222+
line=${line%"${line##*[![:space:]]}"} # trim trailing whitespace
223+
if [[ ! "$line" || "$line" == '#'* ]]; then continue ; fi
224+
if (($#)); then
225+
for key; do
226+
if [[ $key == "${line%%=*}" ]]; then REPLY+=("$line"); break;
227+
fi
228+
done
229+
else
230+
REPLY+=("$line")
231+
fi
232+
done <<<"$__dotenv"
233+
((${#REPLY[@]}))
234+
}
235+
236+
.env.export() { ! .env.parse "$@" || export "${REPLY[@]}"; }
237+
238+
.env.set() {
239+
.env::file load || return ; local key saved=$__dotenv
240+
while (($#)); do
241+
key=${1#+}; key=${key%%=*}
242+
if .env.get "$key"; then
243+
REPLY=()
244+
if [[ $1 == +* ]]; then shift; continue # skip if already found
245+
elif [[ $1 == *=* ]]; then
246+
__dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[3]}$1$'\n'${BASH_REMATCH[4]#*$'\n'}
247+
else
248+
__dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[4]#*$'\n'}
249+
continue # delete all occurrences
250+
fi
251+
elif [[ $1 == *=* ]]; then
252+
__dotenv+="${1#+}"$'\n'
253+
fi
254+
shift
255+
done
256+
[[ $__dotenv == "$saved" ]] || .env::file save
257+
}
258+
259+
.env.puts() { echo "${1-}">>"$__dotenv_file" && __dotenv+="$1"$'\n'; }
260+
261+
.env.generate() {
262+
.env::arg "key required for generate" "$@" || return
263+
.env.get "$1" && return || REPLY=$("${@:2}") || return
264+
.env::one "generate: ouptut of '${*:2}' has more than one line" "$REPLY" || return
265+
.env.puts "$1=$REPLY"
266+
}
267+
268+
.env.--file() {
269+
.env::arg "filename required for --file" "$@" || return
270+
__dotenv_file=$1; .env::file load || return
271+
(($#<2)) || .env "${@:2}"
272+
}
273+
274+
.env::arg() { [[ "${2-}" ]] || { echo "$__dotenv_cmd: $1" >&2; return 64; }; }
275+
276+
.env::one() { [[ "$2" != *$'\n'* ]] || .env::arg "$1"; }
277+
278+
.env::file() {
279+
local REPLY=$__dotenv_file
280+
case "$1" in
281+
load)
282+
__dotenv=; ! [[ -f "$REPLY" ]] || __dotenv="$(<"$REPLY")"$'\n' || return ;;
283+
save)
284+
if [[ -L "$REPLY" ]] && declare -F -- realpath.resolved >/dev/null; then
285+
realpath.resolved "$REPLY"
286+
fi
287+
{ [[ ! -f "$REPLY" ]] || cp -p "$REPLY" "$REPLY.bak"; } &&
288+
printf %s "$__dotenv" >"$REPLY.bak" && mv "$REPLY.bak" "$REPLY"
289+
esac
290+
}
291+
292+
if [[ $BASH_SOURCE == "$0" ]]; then
293+
set -eu
294+
__dotenv_cmd=${0##*/}
295+
.env.export() { .env.parse "$@" || return 0; printf 'export %q\n' "${REPLY[@]}"; exit; }
296+
.env "$@" || exit $?
297+
${REPLY[@]+printf '%s\n' "${REPLY[@]}"}
298+
fi
299+
# --- EOF dotenv ---
300+
#!/usr/bin/env bash
301+
event(){ case $1 in error|quote|encode|decode);; *)
302+
__ev.encode "${2-}";local f n='' e=bashup_event_$REPLY'[1]';f=${e/event/flag}
169303
case $1 in emit) shift;${!f-};eval "${!e-}"; return ;;on|once|off|has)
170304
case "${3-}" in @_) n='$#';; @*[^0-9]*);; @[0-9]*) n=$((${3#@}));; esac; ${n:+
171305
set -- "$1" "$2" "${@:4}" }
172306
case $1/$# in
173-
has/[12]) REPLY=;; */[12]) set -- error "${2-}: missing callback";;
307+
on*/[12]) set -- error "${2-}: missing callback";; */[12]) REPLY=;;
174308
*) __ev.quote "${@:3}";((${n/\$#/1}))&&REPLY+=' "${@:2:'"$n"'}"';REPLY+=$'\n'
175309
esac
176310
esac
177311
esac ;__ev."$@";}
178312
__ev.error(){ echo "$1">&2;return "${2:-64}";}
179313
__ev.quote(){ REPLY=; ${@+printf -v REPLY ' %q' "$@"}; REPLY=${REPLY# };}
180314
__ev.has(){ [[ ${!e-} && $'\n'"${!e}" == *$'\n'"$REPLY"* && ! ${!f-} ]];}
315+
__ev.get(){ ${!f-};REPLY=${!e-};}
181316
__ev.on(){ __ev.has && return;if [[ ! ${!f-} ]];then eval "$e"+='$REPLY';else eval "${!e-};$REPLY";fi;}
182-
__ev.off(){ __ev.has||return 0; n="${!e}"; n=${n#"$REPLY"}; eval "$e"=$'"${n//\n"$REPLY"/\n}"';}
183-
__ev.fire(){ ${!f-};set -- "$e" "${@:2}"; while [[ ${!1-} ]];do eval "$1=;${!1}"; done ;}
317+
__ev.off(){ __ev.has||return 0; n="${!e}"; n=${REPLY:+"${n#"$REPLY"}"}; eval "$e"=$'"${n//\n"$REPLY"/\n}"';[[ ${!e} ]]||unset "${e%\[1]}";}
318+
__ev.fire(){ ${!f-};set -- "$e" "${@:2}"; while [[ ${!1-} ]];do eval "unset ${1%\[1]};${!1}"; done ;}
184319
__ev.all(){ ${!f-};e=${!e-};eval "${e//$'\n'/||return; }";}
185320
__ev.any(){ ${!f-};e=${!e-};eval "${e//$'\n'/&&return|| } ! :";}
186321
__ev.resolve(){
187322
${!f-};__ev.fire "$@";__ev.quote "$@"
188-
readonly "$f=eval __ev.error 'event \"'$1'\" already resolved' 70;return" "$e=set -- $REPLY"
323+
printf -v n "eval __ev.error 'event \"%s\" already resolved' 70;return" "$1"; eval "${f}"='$n'
324+
printf -v n 'set -- %s' "$REPLY"; eval "${e}"='$n';readonly "${f%\[1]}" "${e%\[1]}"
189325
}
190326
__ev.resolved(){ [[ ${!f-} ]];}
191327
__ev.once(){ n=${n:-0} n=${n/\$#/_}; event on "$1" "@$n" __ev_once $# "@$n" "$@";}
192328
__ev_once(){ event off "$3" "$2" __ev_once "${@:1:$1+2}"; "${@:4}";}
193329
__ev_jit(){
194-
local r=${__ev_jit-} s=$1;((${#r}<250))||__ev_jit=
330+
local q r=${__ev_jit-} s=$1;((${#r}<250))||__ev_jit=
195331
while [[ "$s" ]]; do
196-
r=${s::1};s=${s//$r/};printf -v r 'REPLY=${REPLY//%q/_%02x_};' "$r" "'$r";eval "$r";__ev_jit+=$r
332+
r=${s::1};s=${s:1};printf -v q %q "$r";eval 's=${s//'"$q}";printf -v r 'REPLY=${REPLY//%s/_%02x};' "${q/#[~]/[~]}" "'$r";eval "$r";__ev_jit+="$r"
197333
done
198-
eval '__ev.encode(){ local LC_ALL=C;REPLY=${1//_/__};'\
334+
eval '__ev.encode(){ local LC_ALL=C;REPLY=${1//_/_5f};'\
199335
"${__ev_jit-}"' [[ $REPLY != *[^_[:alnum:]]* ]] || __ev_jit "${REPLY//[_[:alnum:]]/}";}'
200336
};__ev_jit ''
337+
__ev.decode(){ REPLY=();while (($#));do printf -v n %b "${1//_/\\x}";REPLY+=("$n");shift;done;}
338+
__ev.list(){ eval 'set -- "${!'"${e%\[1]}"'@}"';__ev.decode "${@#bashup_event_}";}
201339
run() {
202340
if [[ ! ${DEVKIT_IS_PAGING-} ]] && event has "before_$1" paged-command; then
203341
dk use: tty
@@ -228,12 +366,13 @@ log() { echo "$1" >&2; }
228366
229367
paged-command() { while (($#)); do before "$1" paged-command; shift; done; }
230368
231-
on() { event on "$@"; }
232-
off() { event off "$@"; }
369+
on() { if (($#==1)); then dsl: event-dsl on "$1"; else event on "$@"; fi; }
370+
off(){ if (($#==1)); then dsl: event-dsl off "$1"; else event off "$@"; fi; }
233371
234-
before() { event on "before_$@"; }
235-
after() { event on "after_$@"; }
372+
before() { on "before_$@"; }
373+
after() { on "after_$@"; }
236374
375+
event-dsl() { [[ $3 != + ]] || abort "Can't nest event blocks" 64; event "$1" "$2" "${@:4}"; }
237376
trap 'event emit "EXIT"' EXIT
238377
239378
# Commands that should have a bootstrap first:
@@ -261,16 +400,25 @@ on "clean" clean-deps
261400
after "clean" hash -r
262401
after "clean" linkbin .devkit/dk
263402
403+
on boot dk-fetch-deps
404+
dk-fetch-deps() {
405+
local BUILD_DEPS; .env -f "package.sh" export BUILD_DEPS
406+
IFS=: read -ra BUILD_DEPS <<<"${BUILD_DEPS-}"; set -- ${BUILD_DEPS[@]+"${BUILD_DEPS[@]}"}
407+
for REPLY; do github "$REPLY"; done
408+
}
264409
basher() {
265410
require basher github basherpm/basher master bin/basher
266411
"$BASHER_INSTALL_BIN/basher" "$@"
267412
}
268413
269414
github() {
270-
[[ -d "$BASHER_PACKAGES_PATH/$1/.git" ]] && return
271-
mkdir -p "$BASHER_PACKAGES_PATH/$1"
272-
git clone -q --depth=1 -b "${2:-master}" "https://github.com/$1" "$BASHER_PACKAGES_PATH/$1"
273-
local bin; for bin in "${@:3}"; do linkbin "$BASHER_PACKAGES_PATH/$1/$bin"; done
415+
[[ $1 != *@* ]] || set -- "${1%%@*}" "${1#*@}" "${@:2}"
416+
[[ -d "$BASHER_PACKAGES_PATH/$1/.git" ]] && return
417+
mkdir -p "$BASHER_PACKAGES_PATH/$1"
418+
git clone -q --depth=1 ${2:+-b "$2"} "https://github.com/$1" "$BASHER_PACKAGES_PATH/$1"
419+
local BINS; .env -f "$BASHER_PACKAGES_PATH/$1/package.sh" export BINS
420+
IFS=: read -ra BINS <<<"${BINS-}"; set -- "$1" "${2-}" ${BINS[@]+"${BINS[@]}"} "${@:3}"
421+
for REPLY in "${@:3}"; do ${REPLY:+linkbin "$BASHER_PACKAGES_PATH/$1/$REPLY"}; done
274422
}
275423
go() { require-any go; unset -f go; command go "$@"; }
276424
linkbin() {

dk.md

+31-9
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
@main loco_main # use loco's main as our main
88

99
@require pjeby/license @comment LICENSE
10+
@require bashup/scale-dsl cat "$BASHER_PACKAGES_PATH/bashup/scale-dsl/scale-dsl"
1011
@require bashup/loco mdsh-source "$BASHER_PACKAGES_PATH/bashup/loco/loco.md"
12+
@require bashup/dotenv mdsh-embed "$BASHER_PACKAGES_PATH/bashup/dotenv/dotenv"
1113
@require bashup/events cat "$BASHER_PACKAGES_PATH/bashup/events/bashup.events"
14+
echo
1215
```
1316

1417
# dk - the devkit CLI
@@ -85,12 +88,13 @@ paged-command() { while (($#)); do before "$1" paged-command; shift; done; }
8588
To make .dkrc files more compact, and clearer in intent, we also define some shorthand functions for registering or unregistering event handlers, and before/after events. We also register an `EXIT` trap that fires an `EXIT` event, so that multiple exit handlers can safely be registered.
8689

8790
```shell
88-
on() { event on "$@"; }
89-
off() { event off "$@"; }
91+
on() { if (($#==1)); then dsl: event-dsl on "$1"; else event on "$@"; fi; }
92+
off(){ if (($#==1)); then dsl: event-dsl off "$1"; else event off "$@"; fi; }
9093

91-
before() { event on "before_$@"; }
92-
after() { event on "after_$@"; }
94+
before() { on "before_$@"; }
95+
after() { on "after_$@"; }
9396

97+
event-dsl() { [[ $3 != + ]] || abort "Can't nest event blocks" 64; event "$1" "$2" "${@:4}"; }
9498
trap 'event emit "EXIT"' EXIT
9599

96100
```
@@ -129,6 +133,21 @@ after "clean" linkbin .devkit/dk
129133

130134
## Dependency Management Functions
131135

136+
### Automatic Dependency Fetching
137+
138+
When `dk` starts, it fetches any github dependencies from `BUILD_DEPS` in `package.sh`, if applicable. The file must be in dotenv, and dependencies are `:`-separated `user/repo@ref` strings, where the `@ref` is optional.
139+
140+
```shell
141+
on boot dk-fetch-deps
142+
dk-fetch-deps() {
143+
local BUILD_DEPS; .env -f "package.sh" export BUILD_DEPS
144+
IFS=: read -ra BUILD_DEPS <<<"${BUILD_DEPS-}"; set -- ${BUILD_DEPS[@]+"${BUILD_DEPS[@]}"}
145+
for REPLY; do github "$REPLY"; done
146+
}
147+
```
148+
149+
150+
132151
### basher
133152

134153
For use in `.dkrc` commands, we provide an auto-installing wrapper function for `basher`, that installs it locally if needed.
@@ -143,14 +162,17 @@ basher() {
143162

144163
### github
145164

146-
Not everything is installable with basher, of course, and basher itself needs to be installed via github. So we have a `github` *user/repo [ref [bin1 bin2...]]* function, which clones the desired repo under `.deps` (if it's not already there) and links the named files to `.deps/bin`. The *ref* can be a branch or tag; it defaults to `master` if unspecified.
165+
Not everything is installable with basher, of course, and basher itself needs to be installed via github. So we have a `github` *user/repo[@ref]\[ref [bin1 bin2...]]* function, which clones the desired repo under `.deps` (if it's not already there) and links the named files to `.deps/bin`. The *ref* can be a branch or tag; it defaults to the repository's default branch if unspecified. Any binaries specified will be linked in *addition* to those specified by the repo's `package.sh`, if it exists.
147166

148167
```shell
149168
github() {
150-
[[ -d "$BASHER_PACKAGES_PATH/$1/.git" ]] && return
151-
mkdir -p "$BASHER_PACKAGES_PATH/$1"
152-
git clone -q --depth=1 -b "${2:-master}" "https://github.com/$1" "$BASHER_PACKAGES_PATH/$1"
153-
local bin; for bin in "${@:3}"; do linkbin "$BASHER_PACKAGES_PATH/$1/$bin"; done
169+
[[ $1 != *@* ]] || set -- "${1%%@*}" "${1#*@}" "${@:2}"
170+
[[ -d "$BASHER_PACKAGES_PATH/$1/.git" ]] && return
171+
mkdir -p "$BASHER_PACKAGES_PATH/$1"
172+
git clone -q --depth=1 ${2:+-b "$2"} "https://github.com/$1" "$BASHER_PACKAGES_PATH/$1"
173+
local BINS; .env -f "$BASHER_PACKAGES_PATH/$1/package.sh" export BINS
174+
IFS=: read -ra BINS <<<"${BINS-}"; set -- "$1" "${2-}" ${BINS[@]+"${BINS[@]}"} "${@:3}"
175+
for REPLY in "${@:3}"; do ${REPLY:+linkbin "$BASHER_PACKAGES_PATH/$1/$REPLY"}; done
154176
}
155177
```
156178

package.sh

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
BINS=dk
2+
BUILD_DEPS=bashup/mdsh:bashup/loco:bashup/realpaths:bashup/events:bashup/scale-dsl:bashup/dotenv

0 commit comments

Comments
 (0)