Skip to content

Commit b0d3f17

Browse files
authored
Merge pull request #135 from mbland/search-plugins
Extract @go.search_plugins
2 parents 60f2ec6 + 7cbb239 commit b0d3f17

File tree

3 files changed

+212
-17
lines changed

3 files changed

+212
-17
lines changed

go-core.bash

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ declare -x _GO_CMD_NAME=
114114
# string with the arguments delimited by the ASCII Unit Separator ($'\x1f').
115115
declare -x _GO_CMD_ARGV=
116116

117-
# The directory in which plugins are installed.
117+
# The top-level directory in which plugins are installed.
118+
#
119+
# If a command script is running as a plugin, this value will be the plugins
120+
# directory of the top-level `./go` script.
118121
declare _GO_PLUGINS_DIR=
119122

120123
# Directories containing executable plugin scripts.
@@ -204,6 +207,52 @@ declare _GO_INJECT_MODULE_PATH="$_GO_INJECT_MODULE_PATH"
204207
return "$result"
205208
}
206209

210+
# Searches through plugin directories using a helper function
211+
#
212+
# The search will begin in `_GO_SCRIPTS_DIR/plugins`. As long as `search_func`
213+
# returns nonzero, every parent `/plugins/` directory will be searched, up to
214+
# and including the top-level `_GO_PLUGINS_DIR`. The search will end either when
215+
# `search_func` returns zero, or when all of the plugin paths are exhausted.
216+
#
217+
# The helper function, `search_func`, will receive the current plugin directory
218+
# being searched as its sole argument. The `@go.search_plugins` caller's
219+
# variables will be available in its scope. It should return zero when the
220+
# search criteria are satisfied, nonzero if the search should continue.
221+
#
222+
# For example, to search for a particular item in a particular plugin:
223+
#
224+
# find_plugin_item() {
225+
# [[ -e "$1/item_path" ]] && item_path="$1/item_path"
226+
# }
227+
#
228+
# my_func() {
229+
# local item_path='foo/bar'
230+
# if @go.search_plugins find_plugin_item; then
231+
# # Do something with $item_path
232+
# fi
233+
# }
234+
#
235+
# Arguments:
236+
# search_func: Helper function implementing the search operation
237+
#
238+
# Returns:
239+
# Zero if `search_func` ever returns zero, nonzero otherwise
240+
@go.search_plugins() {
241+
local __gsp_plugins_dir="$_GO_SCRIPTS_DIR/plugins"
242+
243+
# Set `_GO_PLUGINS_DIR` if called from a top-level `./go` script before `@go`.
244+
_GO_PLUGINS_DIR="${_GO_PLUGINS_DIR:-$_GO_SCRIPTS_DIR/plugins}"
245+
246+
while true; do
247+
if "$1" "$__gsp_plugins_dir"; then
248+
return
249+
elif [[ "$__gsp_plugins_dir" == "$_GO_PLUGINS_DIR" ]]; then
250+
return 1
251+
fi
252+
__gsp_plugins_dir="${__gsp_plugins_dir%/plugins/*}/plugins"
253+
done
254+
}
255+
207256
# Main driver of ./go script functionality.
208257
#
209258
# Arguments:

lib/internal/path

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

3+
_@go.set_search_paths_add_plugin_paths() {
4+
local plugin_paths=("$1"/*/bin)
5+
if [[ "${plugin_paths[0]}" != "$1/*/bin" ]]; then
6+
_GO_PLUGINS_PATHS+=("${plugin_paths[@]}")
7+
fi
8+
return 1
9+
}
10+
311
_@go.set_search_paths() {
4-
local plugins_dir="$_GO_SCRIPTS_DIR/plugins"
5-
local plugins_paths=()
612
local plugin_path
713

814
if [[ -n "$_GO_INJECT_SEARCH_PATH" ]]; then
@@ -13,21 +19,9 @@ _@go.set_search_paths() {
1319
# A plugin's own local plugin paths will appear before inherited ones. If
1420
# there is a version incompatibility issue with other installed plugins, this
1521
# allows a plugin's preferred version to take precedence.
16-
while [[ "$plugins_dir" =~ ^$_GO_PLUGINS_DIR ]]; do
17-
plugin_paths=("$plugins_dir"/*/bin)
18-
if [[ "${plugin_paths[0]}" != "$plugins_dir/*/bin" ]]; then
19-
_GO_PLUGINS_PATHS+=("${plugin_paths[@]}")
20-
fi
21-
if [[ "$plugins_dir" == "$_GO_PLUGINS_DIR" ]]; then
22-
break
23-
fi
24-
plugins_dir="${plugins_dir%/plugins/*}/plugins"
25-
done
22+
@go.search_plugins '_@go.set_search_paths_add_plugin_paths'
2623

27-
# A plugin's _GO_SCRIPTS_DIR may continue to appear in _GO_PLUGINS_PATHS so
28-
# that it's available to other plugins that depend upon it as a circular
29-
# dependency (though such dependencies are strongly discouraged). However, we
30-
# will ensure its _GO_SCRIPTS_DIR isn't duplicated in _GO_SEARCH_PATHS.
24+
# Ensure a plugin's _GO_SCRIPTS_DIR isn't duplicated in _GO_SEARCH_PATHS.
3125
for plugin_path in "${_GO_PLUGINS_PATHS[@]}"; do
3226
if [[ "$plugin_path" != "$_GO_SCRIPTS_DIR" ]]; then
3327
_GO_SEARCH_PATHS+=("$plugin_path")

tests/core/search-plugins.bats

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#! /usr/bin/env bats
2+
3+
load ../environment
4+
5+
setup() {
6+
test_filter
7+
@go.create_test_go_script \
8+
'collect_dirs_impl() {' \
9+
' [[ -d "$1" ]] && dirs_searched+=("$1")' \
10+
' [[ "$((--COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS))" -eq "0" ]]' \
11+
'}' \
12+
'collect_dirs() {' \
13+
' local dirs_searched=()' \
14+
' @go.search_plugins collect_dirs_impl' \
15+
' local result="$?"' \
16+
' printf "%s\n" "${dirs_searched[@]}"' \
17+
' return "$result"' \
18+
'}' \
19+
'if [[ -z "$COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS" ]]; then' \
20+
' COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS=1' \
21+
'fi' \
22+
'@go "$@"'
23+
}
24+
25+
teardown() {
26+
@go.remove_test_go_rootdir
27+
}
28+
29+
@test "$SUITE: _GO_PLUGINS_DIR doesn't exist" {
30+
@go.create_test_command_script 'top-level' 'collect_dirs'
31+
run "$TEST_GO_SCRIPT" 'top-level'
32+
assert_success ''
33+
}
34+
35+
@test "$SUITE: _GO_PLUGINS_DIR exists" {
36+
mkdir -p "$TEST_GO_PLUGINS_DIR"
37+
@go.create_test_command_script 'top-level' 'collect_dirs'
38+
run "$TEST_GO_SCRIPT" 'top-level'
39+
assert_success "$TEST_GO_PLUGINS_DIR"
40+
}
41+
42+
@test "$SUITE: _GO_PLUGINS_DIR exists, search returns failure" {
43+
mkdir -p "$TEST_GO_PLUGINS_DIR"
44+
@go.create_test_command_script 'top' 'collect_dirs'
45+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='0' run "$TEST_GO_SCRIPT" 'top'
46+
assert_failure "$TEST_GO_PLUGINS_DIR"
47+
}
48+
49+
@test "$SUITE: plugin without plugins dir finds _GO_PLUGINS_DIR" {
50+
@go.create_test_command_script 'plugins/foo/bin/foo' 'collect_dirs'
51+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='2' run "$TEST_GO_SCRIPT" 'foo'
52+
assert_success "$TEST_GO_PLUGINS_DIR"
53+
}
54+
55+
@test "$SUITE: plugin with plugins dir finds both plugins dirs" {
56+
mkdir -p "$TEST_GO_PLUGINS_DIR/foo/bin/plugins"
57+
@go.create_test_command_script 'plugins/foo/bin/foo' 'collect_dirs'
58+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='2' run "$TEST_GO_SCRIPT" 'foo'
59+
assert_success "$TEST_GO_PLUGINS_DIR/foo/bin/plugins" \
60+
"$TEST_GO_PLUGINS_DIR"
61+
}
62+
63+
@test "$SUITE: plugin with plugins dir, return success after first dir" {
64+
mkdir -p "$TEST_GO_PLUGINS_DIR/foo/bin/plugins"
65+
@go.create_test_command_script 'plugins/foo/bin/foo' 'collect_dirs'
66+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='1' run "$TEST_GO_SCRIPT" 'foo'
67+
assert_success "$TEST_GO_PLUGINS_DIR/foo/bin/plugins"
68+
}
69+
70+
@test "$SUITE: plugin finds both plugins dirs, returns failure" {
71+
mkdir -p "$TEST_GO_PLUGINS_DIR/foo/bin/plugins"
72+
@go.create_test_command_script 'plugins/foo/bin/foo' 'collect_dirs'
73+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='3' run "$TEST_GO_SCRIPT" 'foo'
74+
assert_failure "$TEST_GO_PLUGINS_DIR/foo/bin/plugins" \
75+
"$TEST_GO_PLUGINS_DIR"
76+
}
77+
78+
@test "$SUITE: nested_plugin without plugins dir finds parent dirs" {
79+
mkdir -p "$TEST_GO_PLUGINS_DIR/foo/bin/plugins/bar/bin"
80+
@go.create_test_command_script 'plugins/foo/bin/foo' '@go bar'
81+
@go.create_test_command_script 'plugins/foo/bin/plugins/bar/bin/bar' \
82+
'collect_dirs'
83+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='3' run "$TEST_GO_SCRIPT" 'foo'
84+
assert_success "$TEST_GO_PLUGINS_DIR/foo/bin/plugins" \
85+
"$TEST_GO_PLUGINS_DIR"
86+
}
87+
88+
@test "$SUITE: nested_plugin with plugins dir finds all plugin dirs" {
89+
mkdir -p "$TEST_GO_PLUGINS_DIR/foo/bin/plugins/bar/bin/plugins"
90+
@go.create_test_command_script 'plugins/foo/bin/foo' '@go bar'
91+
@go.create_test_command_script 'plugins/foo/bin/plugins/bar/bin/bar' \
92+
'collect_dirs'
93+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='3' run "$TEST_GO_SCRIPT" 'foo'
94+
assert_success "$TEST_GO_PLUGINS_DIR/foo/bin/plugins/bar/bin/plugins" \
95+
"$TEST_GO_PLUGINS_DIR/foo/bin/plugins" \
96+
"$TEST_GO_PLUGINS_DIR"
97+
}
98+
99+
@test "$SUITE: nested_plugin stops after parent plugin dir" {
100+
@go.create_test_command_script 'plugins/foo/bin/foo' '@go bar'
101+
@go.create_test_command_script 'plugins/foo/bin/plugins/bar/bin/bar' \
102+
'collect_dirs'
103+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='2' run "$TEST_GO_SCRIPT" 'foo'
104+
# Note it doesn't have its own plugin dir this time.
105+
assert_success "$TEST_GO_PLUGINS_DIR/foo/bin/plugins"
106+
}
107+
108+
@test "$SUITE: nested_plugin with plugins dir finds all dirs, returns failure" {
109+
mkdir -p "$TEST_GO_PLUGINS_DIR/foo/bin/plugins/bar/bin/plugins"
110+
@go.create_test_command_script 'plugins/foo/bin/foo' '@go bar'
111+
@go.create_test_command_script 'plugins/foo/bin/plugins/bar/bin/bar' \
112+
'collect_dirs'
113+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='4' run "$TEST_GO_SCRIPT" 'foo'
114+
assert_failure "$TEST_GO_PLUGINS_DIR/foo/bin/plugins/bar/bin/plugins" \
115+
"$TEST_GO_PLUGINS_DIR/foo/bin/plugins" \
116+
"$TEST_GO_PLUGINS_DIR"
117+
}
118+
119+
@test "$SUITE: /plugins/ in _GO_ROOTDIR, _GO_SCRIPTS_DIR (pathological)" {
120+
local test_rootdir="$TEST_GO_ROOTDIR/plugins/plugins"
121+
local test_go_script="$test_rootdir/go"
122+
local test_scripts_dir="$test_rootdir/plugins"
123+
local test_plugins_dir="$test_scripts_dir/plugins"
124+
mkdir -p "$test_plugins_dir/foo/bin/plugins/bar/bin/plugins"
125+
126+
local line
127+
while IFS= read -r line; do
128+
line="${line%$'\r'}"
129+
if [[ "$line" =~ go-core\.bash ]]; then
130+
line=". '$_GO_CORE_DIR/go-core.bash' 'plugins'"
131+
fi
132+
printf '%s\n' "$line" >> "$test_go_script"
133+
done < "$TEST_GO_SCRIPT"
134+
chmod 700 "$test_go_script"
135+
136+
local foo_path="${test_plugins_dir#$BATS_TEST_ROOTDIR/}/foo/bin/foo"
137+
local bar_path="${foo_path%/*}/plugins/bar/bin/bar"
138+
139+
# We can't use `@go.create_test_command_script` since we can't change the
140+
# readonly `TEST_GO_*` variables.
141+
create_bats_test_script "$foo_path" '@go bar'
142+
create_bats_test_script "$bar_path" 'collect_dirs'
143+
144+
test_printf 'test_go_script: %s\n' "$test_go_script" >&2
145+
test_printf 'test_plugins_dir: %s\n' "$test_plugins_dir" >&2
146+
test_printf 'foo_path: %s\n' "${foo_path}" >&2
147+
test_printf 'bar_path: %s\n' "${bar_path}" >&2
148+
COLLECT_DIRS_SUCCESS_AFTER_NUM_ITERATIONS='10' run "$test_go_script" 'foo'
149+
assert_failure "$test_plugins_dir/foo/bin/plugins/bar/bin/plugins" \
150+
"$test_plugins_dir/foo/bin/plugins" \
151+
"$test_plugins_dir"
152+
}

0 commit comments

Comments
 (0)