Skip to content

Commit 31f8622

Browse files
committed
Add new builtin command to generate scripts
Closes #142.
1 parent bd8994d commit 31f8622

File tree

2 files changed

+719
-0
lines changed

2 files changed

+719
-0
lines changed

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+
_@go.new_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+
_@go.source_builtin 'commands' "$_GO_SCRIPTS_DIR"
59+
else
60+
. "$_GO_CORE_DIR/lib/internal/complete"
61+
_@go.complete_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+
_@go.new_file() {
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+
_@go.new_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+
_@go.new_file "command script" "$cmd_path" '755' "${script_impl[@]}"
159+
}
160+
161+
_@go.new_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 ! _@go.new_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+
_@go.new_module() {
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 ! _@go.new_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+
_@go.new_test() {
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 ! _@go.new_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+
_@go.new() {
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+
_@go.new_tab_completions "$@"
269+
return
270+
;;
271+
--command)
272+
_@go.new_command_scripts "$@"
273+
;;
274+
--internal)
275+
_@go.new_module "$_GO_SCRIPTS_DIR/lib/$1"
276+
;;
277+
--public)
278+
_@go.new_module "$_GO_ROOTDIR/lib/$1"
279+
;;
280+
--test)
281+
_@go.new_test "$_GO_ROOTDIR/$_GO_TEST_DIR/$1"
282+
;;
283+
--type)
284+
if ! _@go.new_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+
_@go.new "$@"

0 commit comments

Comments
 (0)