Skip to content

Commit 71d729f

Browse files
authored
Merge pull request #168 from mbland/generator
Add `new` builtin command to generate scripts, fix latent bugs
2 parents bd8994d + f530a60 commit 71d729f

File tree

8 files changed

+810
-36
lines changed

8 files changed

+810
-36
lines changed

go-core.bash

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
# https://mike-bland.com/
2727
# https://github.com/mbland
2828

29-
if [[ "${BASH_VERSINFO[0]}" -lt '3' || "${BASH_VERSINFO[1]}" -lt '2' ]]; then
29+
if [[ "${BASH_VERSINFO[0]}" -lt '3' ||
30+
( "${BASH_VERSINFO[0]}" -eq '3' && "${BASH_VERSINFO[1]}" -lt '2' ) ]]; then
3031
printf "This module requires bash version 3.2 or greater:\n %s %s\n" \
3132
"$BASH" "$BASH_VERSION"
3233
exit 1

lib/bats/helpers

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,11 +313,11 @@ stub_program_in_path() {
313313
in_process='true'
314314
shift
315315
fi
316+
create_bats_test_script "${BATS_TEST_BINDIR#$BATS_TEST_ROOTDIR/}/$1" "${@:2}"
316317

317318
if [[ ! "$PATH" =~ $bindir_pattern ]]; then
318319
export PATH="$BATS_TEST_BINDIR:$PATH"
319320
fi
320-
create_bats_test_script "${BATS_TEST_BINDIR#$BATS_TEST_ROOTDIR/}/$1" "${@:2}"
321321

322322
if [[ -n "$in_process" ]]; then
323323
hash "$1"

lib/testing/environment

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,19 @@ test-go() {
120120
# ...: Arguments passed directly to `@go.compgen`
121121
@go.test_compgen() {
122122
set "$DISABLE_BATS_SHELL_OPTIONS"
123+
local completions
124+
local err_args
125+
123126
. "$_GO_USE_MODULES" 'complete' 'strings'
124-
@go.split $'\n' "$(@go.compgen "${@:2}")" "$1"
127+
completions="$(@go.compgen "${@:2}")"
128+
129+
if [[ "$?" -ne '0' || -z "$completions" ]]; then
130+
printf -v 'err_args' ' "%s"' "${@:2}"
131+
printf 'compgen failed or results were empty:%s\n' "$err_args" >&2
132+
restore_bats_shell_options '1'
133+
return
134+
fi
135+
@go.split $'\n' "$completions" "$1"
125136
restore_bats_shell_options "$?"
126137
}
127138

libexec/new

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
#! /usr/bin/env bash
2+
#
3+
# Generate a new command script, module, test, or other text file
4+
#
5+
# Usage:
6+
# To generate an arbitrary text file:
7+
# {{go}} {{cmd}} --type <file_type> <file_path> <permissions> [lines...]
8+
#
9+
# To generate a new command script in `_GO_SCRIPTS_DIR`:
10+
# {{go}} {{cmd}} --command <command_name> [<subcommand_name>...]
11+
#
12+
# To generate a new internal module in `_GO_SCRIPTS_DIR/lib`:
13+
# {{go}} {{cmd}} --internal <module_path>
14+
#
15+
# To generate a new public module in `_GO_ROOTDIR/lib`:
16+
# {{go}} {{cmd}} --public <module_path>
17+
#
18+
# To generate a new Bats test file in `_GO_TEST_DIR`:
19+
# {{go}} {{cmd}} --test <test_path>
20+
#
21+
# Where:
22+
#
23+
# <file_type> Very brief description of the file type (can be empty)
24+
# <file_path> Path to the new file
25+
# [lines...] Optional list of lines to add to the file
26+
# <permissions> Permissions to set for the new file
27+
# <command_name> Command script name
28+
# <subcommand_name> Subcommand script name
29+
# <module_path> Path to module file relative to `_GO_*DIR/lib`
30+
# <test_path> Path to module file relative to `_GO_TEST_DIR`
31+
#
32+
# Any component of the target file path that does not yet exist will be created.
33+
#
34+
# If the `EDITOR` environment variable is defined, the newly-generated file (or
35+
# files, possible with `--command`) will be opened for editing with `--command`,
36+
# `--internal`, `--public`, or `--test`. It will be opened for other files when
37+
# the list of `lines...` is empty.
38+
#
39+
# When invoking the `--command` form, this command will also generate a new
40+
# script for any name preceding the final `<subcommand_name>` that does not yet
41+
# correspond to an existing parent command script. These parent commands will
42+
# invoke `@go.show_subcommands` from the core `subcommands` module by default.
43+
44+
[email protected]_tab_completions() {
45+
local word_index="$1"
46+
local mode="$2"
47+
shift 2
48+
49+
if [[ "$word_index" -eq '0' ]]; then
50+
printf -- '--command --internal --public --test --type'
51+
return
52+
fi
53+
((--word_index))
54+
55+
case "$mode" in
56+
--command)
57+
if [[ "$word_index" -eq '0' ]]; then
58+
[email protected]_builtin 'commands' "$_GO_SCRIPTS_DIR"
59+
else
60+
. "$_GO_CORE_DIR/lib/internal/complete"
61+
[email protected]_command_path "$word_index" "$@"
62+
fi
63+
return
64+
;;
65+
--internal)
66+
if [[ "$word_index" -ne '0' ]] || ! cd "$_GO_SCRIPTS_DIR/lib"; then
67+
return 1
68+
fi
69+
;;
70+
--public)
71+
if [[ "$word_index" -ne '0' ]] || ! cd "$_GO_ROOTDIR/lib"; then
72+
return 1
73+
fi
74+
;;
75+
--test)
76+
if [[ "$word_index" -ne '0' ]] || ! cd "$_GO_ROOTDIR/$_GO_TEST_DIR"; then
77+
return 1
78+
fi
79+
;;
80+
--type)
81+
if [[ "$word_index" -ne '1' ]]; then
82+
return 1
83+
fi
84+
shift
85+
;;
86+
*)
87+
return 1
88+
;;
89+
esac
90+
@go.compgen -f -- "$1"
91+
}
92+
93+
94+
local file_type="$1"
95+
local file_path="$2"
96+
local permissions="$3"
97+
shift 3
98+
local relpath="$file_path"
99+
local parent_dir
100+
local permissions_pattern='([0-7][0-7][0-7]|[ugo]{1,3}[+-][rwx]{1,3})'
101+
102+
if [[ -z "$_GO_STANDALONE" ]]; then
103+
relpath="${relpath#$_GO_ROOTDIR/}"
104+
fi
105+
106+
parent_dir="${relpath%/*}"
107+
if [[ "$parent_dir" == "$relpath" ]]; then
108+
parent_dir="$PWD"
109+
fi
110+
111+
if [[ -n "$file_type" ]]; then
112+
file_type+=' '
113+
fi
114+
115+
if [[ -z "$file_path" ]]; then
116+
@go.printf 'No %sfile path specified.\n' "$file_type" >&2
117+
return 1
118+
elif [[ ! "$permissions" =~ $permissions_pattern ]]; then
119+
@go.printf 'Invalid permissions specification "%s" for %sfile: %s\n' \
120+
"$permissions" "$file_type" "$relpath" >&2
121+
return 1
122+
elif [[ ! -d "$parent_dir" ]] && ! mkdir -p "$parent_dir"; then
123+
@go.printf "Couldn't create parent directory for new %sfile: %s\n" \
124+
"$file_type" "$relpath" >&2
125+
return 1
126+
elif [[ -f "$file_path" ]]; then
127+
@go.printf '%sfile already exists: %s\n' "$file_type" "$relpath" >&2
128+
return 1
129+
elif ! printf -- '%s\n' "$@" >"$file_path"; then
130+
@go.printf 'Failed to create new %sfile: %s\n' "$file_type" "$relpath" >&2
131+
return 1
132+
elif ! chmod "$permissions" "$file_path"; then
133+
@go.printf 'Failed to set permissions for new %sfile to "%s": %s\n' \
134+
"$file_type" "$permissions" "$relpath" >&2
135+
return 1
136+
fi
137+
}
138+
139+
[email protected]_command_script() {
140+
local cmd="$1"
141+
local cmd_path="$2"
142+
local is_last_cmd="$3"
143+
local script_impl=('#! /usr/bin/env bash'
144+
'#'
145+
'# Short description of the {{cmd}} command' '')
146+
147+
if [[ -n "$is_last_cmd" ]]; then
148+
script_impl+=("_$cmd() {"
149+
' :'
150+
'}'
151+
''
152+
"_$cmd \"\$@\"")
153+
else
154+
script_impl+=(". \"\$_GO_USE_MODULES\" 'subcommands'"
155+
''
156+
'@go.show_subcommands')
157+
fi
158+
[email protected]_file "command script" "$cmd_path" '755' "${script_impl[@]}"
159+
}
160+
161+
[email protected]_command_scripts() {
162+
local cmd
163+
local cmd_path
164+
local parent_dir="$_GO_SCRIPTS_DIR"
165+
local new_scripts=()
166+
local is_last_cmd
167+
local i=0
168+
169+
if [[ "$#" -eq '0' ]]; then
170+
printf 'No command script name specified.\n' >&2
171+
return 1
172+
fi
173+
174+
for cmd in "$@"; do
175+
cmd_path="$parent_dir/$cmd"
176+
parent_dir="$cmd_path.d"
177+
178+
if [[ "$((++i))" -eq "$#" ]]; then
179+
is_last_cmd='true'
180+
elif [[ -f "$cmd_path" ]]; then
181+
continue
182+
fi
183+
new_scripts+=("$cmd_path")
184+
185+
if ! [email protected]_command_script "$cmd" "$cmd_path" "$is_last_cmd"; then
186+
return 1
187+
fi
188+
done
189+
190+
if command -v "$EDITOR" >/dev/null; then
191+
"$EDITOR" "${new_scripts[@]}"
192+
fi
193+
}
194+
195+
196+
local module_path="$1"
197+
local module_relpath="${module_path#*/lib/}"
198+
local module_type
199+
local impl=('#! /usr/bin/env bash'
200+
'#'
201+
"# Short description of the $module_relpath module"
202+
'#'
203+
'# Exports:'
204+
'# func_name'
205+
'# Short description of the func_name function')
206+
207+
case "${module_path%%/lib/*}" in
208+
$_GO_SCRIPTS_DIR)
209+
module_type='internal module'
210+
;;
211+
$_GO_ROOTDIR)
212+
module_type='public module'
213+
;;
214+
esac
215+
216+
if ! [email protected]_file "$module_type" "$module_path" '644' "${impl[@]}"; then
217+
return 1
218+
elif command -v "$EDITOR" >/dev/null; then
219+
"$EDITOR" "$module_path"
220+
fi
221+
}
222+
223+
224+
local test_path="${1%.bats}.bats"
225+
local test_relpath="${test_path#$_GO_ROOTDIR/$_GO_TEST_DIR/}"
226+
local parent_dir="${test_relpath%/*}"
227+
local impl
228+
229+
if [[ -n "$parent_dir" ]]; then
230+
parent_dir="${parent_dir//[^\/]}/"
231+
fi
232+
233+
impl=('#! /usr/bin/env bats'
234+
''
235+
"load ${parent_dir//\//../}environment"
236+
''
237+
'setup() {'
238+
' test_filter'
239+
' @go.create_test_go_script'
240+
'}'
241+
''
242+
'teardown() {'
243+
' @go.remove_test_go_rootdir'
244+
'}'
245+
''
246+
'@test "$SUITE: short description of your first test case" {'
247+
'}')
248+
249+
if ! [email protected]_file "Bats test" "$test_path" '644' "${impl[@]}"; then
250+
return 1
251+
elif command -v "$EDITOR" >/dev/null; then
252+
"$EDITOR" "$test_path"
253+
fi
254+
}
255+
256+
257+
local mode="$1"
258+
259+
if [[ "$#" -eq '0' ]]; then
260+
@go 'help' "${_GO_CMD_NAME[@]}" >&2
261+
return 1
262+
fi
263+
shift
264+
265+
case "$mode" in
266+
--complete)
267+
# Tab completions
268+
[email protected]_tab_completions "$@"
269+
return
270+
;;
271+
--command)
272+
[email protected]_command_scripts "$@"
273+
;;
274+
--internal)
275+
[email protected]_module "$_GO_SCRIPTS_DIR/lib/$1"
276+
;;
277+
--public)
278+
[email protected]_module "$_GO_ROOTDIR/lib/$1"
279+
;;
280+
--test)
281+
[email protected]_test "$_GO_ROOTDIR/$_GO_TEST_DIR/$1"
282+
;;
283+
--type)
284+
if ! [email protected]_file "$1" "$2" "${@:3}"; then
285+
return 1
286+
elif [[ "$#" -le '3' ]] && command -v "$EDITOR" >/dev/null; then
287+
"$EDITOR" "$2"
288+
fi
289+
;;
290+
*)
291+
printf 'The first argument is "%s", but must be one of:\n %s\n' \
292+
"$mode" '--command --internal --public --test --type' >&2
293+
return 1
294+
esac
295+
}
296+
297+

tests/bats-helpers.bats

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,11 @@ __check_dirs_exist() {
240240
local bats_bindir_pattern="^${BATS_TEST_BINDIR}:"
241241
fail_if matches "$bats_bindir_pattern" "$PATH"
242242

243-
stub_program_in_path 'git' 'echo "$@"'
243+
stub_program_in_path 'chmod' 'echo "$@"'
244244
assert_matches "$bats_bindir_pattern" "$PATH"
245245

246-
run git Hello, World!
247-
assert_success 'Hello, World!'
246+
run chmod ugo+rwx foo.txt
247+
assert_success 'ugo+rwx foo.txt'
248248
}
249249

250250
@test "$SUITE: {stub,restore}_program_in_path for testing in-process function" {

0 commit comments

Comments
 (0)