Skip to content

Commit 6fd0eab

Browse files
authored
Merge pull request #151 from mbland/standalone
core: Implement `_GO_STANDALONE` mode
2 parents e2bd270 + 33c5eab commit 6fd0eab

File tree

12 files changed

+170
-35
lines changed

12 files changed

+170
-35
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,22 @@ under Bash 3.2, 4.2, 4.3, and 4.4 across OS X, Ubuntu Linux, Arch Linux, Alpine
160160
Linux, FreeBSD 9.3, FreeBSD 10.3, and Windows 10 (using all the environments
161161
described in the "Will this work on Windows?" section above).
162162

163+
#### Can I use it to write standalone programs that aren't project scripts?
164+
165+
Actually, yes. See the [Standalone mode](#standalone-mode) section below.
166+
167+
Also see the following question...
168+
169+
#### Can I have more than one ./go script in the same project source tree?
170+
171+
Yes. You can share one copy of the go-bash-framework sources, and even have
172+
common code in the `lib/` directory, but set each script to use its own command
173+
scripts dir.
174+
175+
This may be especially useful if you're writing a [standalone](#standalone-mode)
176+
program, in which one script provides the actual program interface, and the
177+
other provides the development-only interface.
178+
163179
#### How is it tested?
164180

165181
The project's own `./go test` command does it all. Combined with automatic
@@ -298,6 +314,20 @@ To learn the API for adding tab completion to your own command scripts, run
298314
`./go help complete`. You can also learn by reading the scripts for the builtin
299315
commands.
300316

317+
#### Standalone mode
318+
319+
If you wish to use the framework to write a standalone program, rather than a
320+
project-specific development script, set `_GO_STANDALONE` in your top-level
321+
script to prevent alias commands, builtin commands, and plugin commands from
322+
showing up in `help` output or from being offered as tab completions. (`help`
323+
will still appear as a top-level tab completion.) All of these commands will
324+
still be available, but users won't be presented with them directly.
325+
326+
`_GO_STANDALONE` also prevents the script from setting `PWD` to `_GO_ROOTDIR`,
327+
enabling the script to process relative file path arguments anywhere in the file
328+
system. Note that then you'll have to add `_GO_ROOTDIR` manually to any
329+
`_GO_ROOTDIR`-relative paths in your own scripts.
330+
301331
#### Including common code
302332

303333
There are a number of possible methods available for sharing code between

go-core.bash

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,28 @@ cd "${0%/*}" || exit 1
5050
# Path to the project's root directory
5151
#
5252
# This is directory containing the main ./go script. All functions, commands,
53-
# and scripts are invoked relative to this directory.
53+
# and scripts are invoked relative to this directory (unless `_GO_STANDALONE`
54+
# is set).
5455
declare -x _GO_ROOTDIR="$PWD"
5556

5657
if [[ "${BASH_SOURCE[0]:0:1}" != '/' ]]; then
5758
cd "$__go_orig_dir/${BASH_SOURCE[0]%/*}" || exit 1
5859
else
5960
cd "${BASH_SOURCE[0]%/*}" || exit 1
6061
fi
61-
unset __go_orig_dir
6262

6363
# Path to the ./go script framework's directory
6464
declare -r -x _GO_CORE_DIR="$PWD"
65-
cd "$_GO_ROOTDIR" || exit 1
65+
66+
# Set _GO_STANDALONE if your script is a standalone program.
67+
#
68+
# See the "Standalone mode" section of README.md for more information.
69+
if [[ -z "$_GO_STANDALONE" ]]; then
70+
cd "$_GO_ROOTDIR" || exit 1
71+
else
72+
cd "$__go_orig_dir" || exit 1
73+
fi
74+
unset __go_orig_dir
6675

6776
# Path to the script used to import optional library modules.
6877
#

lib/internal/commands

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,15 @@ _@go.find_commands() {
5656
scripts+=("$script")
5757
fi
5858
done
59-
_@go.merge_scripts_into_list "${scripts[@]#$_GO_ROOTDIR/}"
59+
_@go.merge_scripts_into_list "${scripts[@]}"
6060
done
6161

6262
if [[ "${#__go_command_scripts[@]}" -eq '0' ]]; then
6363
return 1
64+
elif [[ -z "$_GO_STANDALONE" ]]; then
65+
__go_command_scripts=("${__go_command_scripts[@]#$_GO_ROOTDIR/}")
66+
else
67+
__go_command_scripts=("${__go_command_scripts[@]#$PWD/}")
6468
fi
6569

6670
__go_command_names=("${__go_command_scripts[@]##*/}")

lib/internal/complete

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
#! /bin/bash
22

33
_@go.complete_top_level_commands() {
4-
_@go.source_builtin 'aliases'
5-
_@go.source_builtin 'commands'
4+
if [[ -z "$_GO_STANDALONE" ]]; then
5+
_@go.source_builtin 'aliases'
6+
_@go.source_builtin 'commands'
7+
else
8+
printf 'help\n'
9+
_@go.source_builtin 'commands' "$_GO_SCRIPTS_DIR"
10+
fi
611
}
712

813
# A successful return status from this function is a signal that __go_cmd_path

libexec/complete

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
# <command> is the command supporting argument completion
1616
#
1717
# Aliases and some builtin commands will complete file and directory paths
18-
# relative to {{root}}. Other commands may implement their own specific
19-
# completion schemes.
18+
# relative to {{root}} (unless `_GO_STANDALONE` is set, in which case they'll be
19+
# relative to the caller's `PWD`). Other commands may implement their own
20+
# specific completion schemes.
2021
#
2122
# This behavior is normally accessible by using `{{go}} env` to set up your
2223
# shell environment for argument completion. Running `{{go}} {{cmd}}` or `{{go}}

libexec/help

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,14 @@
5555
# enforced.
5656

5757
_@go.usage() {
58-
local cmd_paths=("${_GO_PLUGINS_PATHS[@]}" "$_GO_SCRIPTS_DIR")
58+
local cmd_paths=("$_GO_SCRIPTS_DIR")
5959
local summaries
6060
local summaries_status=0
6161

62+
if [[ -z "$_GO_STANDALONE" ]]; then
63+
cmd_paths+=("${_GO_PLUGINS_PATHS[@]}")
64+
fi
65+
6266
. "$_GO_USE_MODULES" 'strings'
6367
@go.join ':' 'cmd_paths' "${cmd_paths[@]}"
6468
summaries="$(_@go.source_builtin 'commands' --summaries "$cmd_paths")"
@@ -69,14 +73,18 @@ _@go.usage() {
6973
summaries_status=1
7074
fi
7175

72-
@go.printf "%s\n\n%s\n\n%s\n\n%s %s\n\n%s %s\n\n" \
76+
@go.printf "%s\n\n%s\n\n%s\n\n%s %s\n\n" \
7377
"Usage: $_GO_CMD <command> [arguments...]" \
7478
"Where <command> is one of:" \
7579
"$summaries" \
7680
"Use \"$_GO_CMD help <command>\" for more information about that command," \
77-
"if available." \
78-
"Use \"$_GO_CMD help builtins\" for help on builtin commands," \
79-
"and \"$_GO_CMD help aliases\" for information on shell alias commands."
81+
"if available."
82+
83+
if [[ -z "$_GO_STANDALONE" ]]; then
84+
@go.printf '%s %s\n\n' \
85+
"Use \"$_GO_CMD help builtins\" for help on builtin commands," \
86+
"and \"$_GO_CMD help aliases\" for information on shell alias commands."
87+
fi
8088
[[ "$?" -eq '0' && "$summaries_status" -eq '0' ]]
8189
}
8290

tests/commands/find.bats

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ teardown() {
2727
assert_command_scripts_equal() {
2828
set "$BATS_ASSERTION_DISABLE_SHELL_OPTIONS"
2929
unset 'lines[0]' 'lines[1]'
30-
lines=("${lines[@]#$_GO_ROOTDIR/}")
30+
lines=("${lines[@]}")
3131
assert_lines_equal "$@"
3232
return_from_bats_assertion "$?"
3333
}
@@ -73,7 +73,7 @@ assert_command_scripts_equal() {
7373

7474
assert_line_equals 0 "LONGEST NAME LEN: ${#longest_name}"
7575
assert_line_equals 1 "COMMAND_NAMES: ${__all_scripts[*]##*/}"
76-
assert_command_scripts_equal "${__all_scripts[@]}"
76+
assert_command_scripts_equal "${__all_scripts[@]#$TEST_GO_ROOTDIR/}"
7777
}
7878

7979
@test "$SUITE: return builtins, plugins, and user scripts" {
@@ -91,7 +91,28 @@ assert_command_scripts_equal() {
9191

9292
assert_line_equals 0 "LONGEST NAME LEN: ${#longest_name}"
9393
assert_line_equals 1 "COMMAND_NAMES: ${__all_scripts[*]##*/}"
94-
assert_command_scripts_equal "${__all_scripts[@]}"
94+
assert_command_scripts_equal "${__all_scripts[@]#$TEST_GO_ROOTDIR/}"
95+
}
96+
97+
@test "$SUITE: return paths relative to PWD when _GO_STANDALONE is set" {
98+
local longest_name="super-extra-long-name-that-no-one-would-use"
99+
local __all_scripts=("${BUILTIN_SCRIPTS[@]}")
100+
101+
# Command script and plugin script names must remain hand-sorted.
102+
add_scripts 'bar' 'baz' 'foo' \
103+
'plugins/plugh/bin/plugh' \
104+
'plugins/quux/bin/quux' \
105+
"plugins/$longest_name/bin/$longest_name" \
106+
'plugins/xyzzy/bin/xyzzy'
107+
cd "$HOME"
108+
_GO_STANDALONE='true' run "$TEST_GO_SCRIPT"
109+
assert_success
110+
111+
test_printf 'SCRIPT: %s\n' "${__all_scripts[@]}"
112+
113+
assert_line_equals 0 "LONGEST NAME LEN: ${#longest_name}"
114+
assert_line_equals 1 "COMMAND_NAMES: ${__all_scripts[*]##*/}"
115+
assert_command_scripts_equal "${__all_scripts[@]#$HOME/}"
95116
}
96117

97118
@test "$SUITE: commands from earlier paths precede duplicates in later paths" {

tests/commands/helpers.bash

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ find_builtins() {
1010
local cmd_script
1111
local cmd_name
1212

13-
for cmd_script in "$_GO_ROOTDIR"/libexec/*; do
13+
for cmd_script in "$_GO_CORE_DIR"/libexec/*; do
1414
if [[ ! (-f "$cmd_script" && -x "$cmd_script") ]]; then
1515
continue
1616
fi
@@ -22,9 +22,6 @@ find_builtins() {
2222
LONGEST_BUILTIN_NAME="$cmd_name"
2323
fi
2424
done
25-
26-
# Strip the rootdir to make output less noisy.
27-
BUILTIN_SCRIPTS=("${BUILTIN_SCRIPTS[@]#$_GO_ROOTDIR/}")
2825
}
2926

3027
merge_scripts() {
@@ -58,9 +55,9 @@ merge_scripts() {
5855
add_scripts() {
5956
local script_names=("$@")
6057

61-
merge_scripts "${script_names[@]/#/$TEST_GO_SCRIPTS_RELATIVE_DIR/}"
58+
merge_scripts "${script_names[@]/#/$TEST_GO_SCRIPTS_DIR/}"
6259

6360
for script_path in "${script_names[@]}"; do
64-
@go.create_test_command_script "$script_path"
61+
@go.create_test_command_script "${script_path#$TEST_GO_ROOTDIR/}"
6562
done
6663
}

tests/commands/main.bats

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@ setup() {
77
test_filter
88
@go.create_test_go_script '@go "$@"'
99
find_builtins
10-
11-
# We have to add back the _GO_ROOTDIR that was stripped from the beginning of
12-
# each element of BUILTIN_SCRIPTS, because it will be different from the
13-
# _GO_ROOTDIR of the generated test script. Thus, `$TEST_GO_SCRIPT commands`
14-
# will report builtin command paths as absolute.
15-
BUILTIN_SCRIPTS=("${BUILTIN_SCRIPTS[@]/#/$_GO_ROOTDIR/}")
1610
}
1711

1812
teardown() {
@@ -183,9 +177,7 @@ generate_expected_paths() {
183177
done
184178
}
185179

186-
@test "$SUITE: command paths" {
187-
local user_commands=('bar' 'baz' 'foo')
188-
local plugin_commands=('plugh' 'quux' 'xyzzy')
180+
@test "$SUITE: command paths are relative to _GO_ROOTDIR by default" {
189181
local __all_scripts=("${BUILTIN_SCRIPTS[@]}")
190182

191183
# Command script and plugin script names must remain hand-sorted.
@@ -195,12 +187,31 @@ generate_expected_paths() {
195187
'plugins/xyzzy/bin/xyzzy'
196188

197189
local __expected_paths=()
190+
__all_scripts=("${__all_scripts[@]#$TEST_GO_ROOTDIR/}")
198191
generate_expected_paths
199192

200193
run "$TEST_GO_SCRIPT" commands --paths
201194
assert_success "${__expected_paths[@]}"
202195
}
203196

197+
@test "$SUITE: command paths are relative to PWD when _GO_STANDALONE is set" {
198+
local __all_scripts=("${BUILTIN_SCRIPTS[@]}")
199+
200+
# Command script and plugin script names must remain hand-sorted.
201+
add_scripts 'bar' 'baz' 'foo' \
202+
'plugins/plugh/bin/plugh' \
203+
'plugins/quux/bin/quux' \
204+
'plugins/xyzzy/bin/xyzzy'
205+
206+
local __expected_paths=()
207+
__all_scripts=("${__all_scripts[@]#$HOME/}")
208+
generate_expected_paths
209+
210+
cd "$HOME"
211+
_GO_STANDALONE='true' run "$TEST_GO_SCRIPT" commands --paths
212+
assert_success "${__expected_paths[@]}"
213+
}
214+
204215
create_script_with_description() {
205216
local script_path="$1"
206217
local cmd_name="${script_path##*/}"

tests/complete.bats

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ load environment
44
load commands/helpers
55

66
setup() {
7+
test_filter
78
@go.create_test_go_script '@go "$@"'
89
find_builtins
910
. "$_GO_USE_MODULES" 'complete'
@@ -28,11 +29,14 @@ teardown() {
2829
}
2930

3031
@test "$SUITE: all top-level commands for zeroth or first argument" {
31-
# Aliases will get printed before all other commands.
32-
local __all_commands=("$(./go 'aliases')" "${BUILTIN_CMDS[@]}")
32+
local __all_scripts=("${BUILTIN_CMDS[@]}")
33+
34+
# The script name arguments must remain alphabetically sorted by basename.
35+
add_scripts 'bar' 'plugins/baz/bin/baz' 'foo'
3336

37+
# Aliases will get printed before all other commands.
3438
run "$TEST_GO_SCRIPT" complete 0
35-
assert_success "${__all_commands[@]}"
39+
assert_success $(./go 'aliases') "${__all_scripts[@]##*/}"
3640

3741
run "$TEST_GO_SCRIPT" complete 0 complete
3842
assert_success 'complete '
@@ -41,6 +45,15 @@ teardown() {
4145
assert_failure ''
4246
}
4347

48+
@test "$SUITE: _GO_STANDALONE only completes 'help' and project scripts" {
49+
@go.create_test_command_script 'foo'
50+
@go.create_test_command_script 'bar'
51+
@go.create_test_command_script 'plugins/baz/bin/baz'
52+
53+
_GO_STANDALONE='true' run "$TEST_GO_SCRIPT" complete 0
54+
assert_success 'help' 'bar' 'foo'
55+
}
56+
4457
@test "$SUITE: cd and pushd complete directories" {
4558
local subdirs=('bar' 'baz' 'foo')
4659
local files=('plugh' 'quux' 'xyzzy')

0 commit comments

Comments
 (0)