Skip to content

Commit 4de3d6a

Browse files
authored
Merge pull request #136 from mbland/plugin-module-search
Add parent-recursive plugin module search
2 parents b0d3f17 + 524682b commit 4de3d6a

File tree

6 files changed

+399
-74
lines changed

6 files changed

+399
-74
lines changed

go-core.bash

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ declare -r -x _GO_USE_MODULES="$_GO_CORE_DIR/lib/internal/use"
9292
# See `./go vars` and `./go help vars`.
9393
declare _GO_IMPORTED_MODULES=()
9494

95+
# Tracks the locations of files corresponding to _GO_IMPORTED_MODULES
96+
# Used to detect and warn about plugin module namespace collisions.
97+
declare _GO_IMPORTED_MODULE_FILES=()
98+
99+
# Tracks where each module _GO_IMPORTED_MODULES was first imported
100+
# Used in the plugin module namespace collision warning message.
101+
declare _GO_IMPORTED_MODULE_CALLERS=()
102+
95103
# Path to the project's script directory
96104
declare _GO_SCRIPTS_DIR=
97105

lib/internal/use

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,44 +76,99 @@
7676
declare __go_module_name
7777
declare __go_module_file
7878
declare __go_loaded_module
79+
declare __go_loaded_file
80+
declare __go_module_index
81+
declare __go_use_caller
82+
declare __go_use_prev_caller
7983

80-
for __go_module_name in "$@"; do
81-
for __go_loaded_module in "${_GO_IMPORTED_MODULES[@]}"; do
82-
if [[ "$__go_module_name" == "$__go_loaded_module" ]]; then
83-
continue 2
84+
# Wrapper function necessary due to BASH_SOURCE and FUNCNAME bug in Bash <4.3.
85+
_@go.set_use_caller() {
86+
__go_use_caller="${BASH_SOURCE[2]}:${BASH_LINENO[1]} ${FUNCNAME[2]}"
87+
}
88+
89+
_@go.find_plugin_module() {
90+
[[ -f "$1/$__go_module_file" ]] && __go_module_file="$1/$__go_module_file"
91+
}
92+
93+
_@go.find_module() {
94+
# If a script imports a plugin module, and that module (`__go_use_caller`)
95+
# tries to import another module from the same plugin, this block will adjust
96+
# the search parameters accordingly.
97+
if [[ -n "$_GO_PLUGINS_DIR" &&
98+
"$__go_use_caller" =~ ^$_GO_PLUGINS_DIR/.*/lib/ ]]; then
99+
local _GO_SCRIPTS_DIR="${__go_use_caller%/lib/*}/bin"
100+
local _GO_ROOTDIR="${_GO_SCRIPTS_DIR%/bin}"
101+
fi
102+
__go_module_file="$_GO_SCRIPTS_DIR/lib/$__go_module_name"
103+
104+
if [[ ! -f "$__go_module_file" ]]; then
105+
__go_module_file="$_GO_ROOTDIR/lib/$__go_module_name"
106+
107+
if [[ ! -f "$__go_module_file" ]]; then
108+
# Convert <plugin>/<module> to plugins/<plugin>/lib/<module>
109+
__go_module_file="${__go_module_name/\///lib/}"
110+
@go.search_plugins '_@go.find_plugin_module'
84111
fi
85-
done
112+
fi
113+
}
86114

87-
# Prevent self- and circular importing by registering name before sourcing.
88-
_GO_IMPORTED_MODULES+=("$__go_module_name")
89-
__go_module_file=''
115+
for __go_module_name in "$@"; do
116+
# Since every import is in the same scope, setting the caller each time is
117+
# necessary in case any imported modules import other modules.
118+
_@go.set_use_caller
119+
__go_module_file="$_GO_CORE_DIR/lib/$__go_module_name"
90120

91121
if [[ -n "$_GO_INJECT_MODULE_PATH" ]]; then
92122
__go_module_file="$_GO_INJECT_MODULE_PATH/$__go_module_name"
93123
if [[ ! -f "$__go_module_file" ]]; then
94-
__go_module_file=''
124+
__go_module_file="$_GO_CORE_DIR/lib/$__go_module_name"
95125
fi
96126
fi
97-
__go_module_file="${__go_module_file:-$_GO_CORE_DIR/lib/$__go_module_name}"
98127

99-
if [[ ! -f "$__go_module_file" ]]; then
100-
__go_module_file="$_GO_SCRIPTS_DIR/lib/$__go_module_name"
128+
if [[ ! -f "$__go_module_file" ]] && ! _@go.find_module; then
129+
@go.printf 'ERROR: Module %s not found at:\n' "$__go_module_name" >&2
130+
@go.print_stack_trace 1 >&2
131+
exit 1
132+
fi
101133

102-
if [[ ! -f "$__go_module_file" ]]; then
103-
__go_module_file="$_GO_ROOTDIR/lib/$__go_module_name"
134+
# If we found the module in our project, but we're installed as a plugin,
135+
# change the loaded module name to reflect that.
136+
#
137+
# Using `##` to trim the string instead of `#` flattens the module name
138+
# namespace. We could use `#` to keep it hierarchical, but since the Bash
139+
# namespace itself is flat, this might lead to hard-to-debug collisions if
140+
# functions and variables get redefined. Keeping a flat module name namespace
141+
# allows us to detect such potential collisions and issue a warning below.
142+
if [[ -n "$_GO_PLUGINS_DIR" &&
143+
"$__go_module_file" =~ ^$_GO_PLUGINS_DIR ]]; then
144+
__go_module_name="${__go_module_file##*/plugins/}"
145+
__go_module_name="${__go_module_name/\/bin\///}"
146+
__go_module_name="${__go_module_name/\/lib\///}"
147+
fi
104148

105-
if [[ ! -f "$__go_module_file" ]]; then
106-
# Convert <plugin>/<module> to plugins/<plugin>/lib/<module>
107-
__go_module_file="$_GO_SCRIPTS_DIR/plugins/${__go_module_name/\///lib/}"
149+
__go_module_index=0
150+
for __go_loaded_module in "${_GO_IMPORTED_MODULES[@]}"; do
151+
if [[ "$__go_module_name" == "$__go_loaded_module" ]]; then
152+
__go_loaded_file="${_GO_IMPORTED_MODULE_FILES[$__go_module_index]}"
153+
__go_use_prev_caller="${_GO_IMPORTED_MODULE_CALLERS[$__go_module_index]}"
108154

109-
if [[ ! -f "$__go_module_file" ]]; then
110-
@go.printf 'ERROR: Module %s not found at:\n' "$__go_module_name" >&2
111-
@go.print_stack_trace 1 >&2
112-
exit 1
113-
fi
155+
# This may happen if a plugin appears more than once in a project tree.
156+
if [[ "$__go_module_file" != "$__go_loaded_file" ]]; then
157+
@go.printf '%s\n' "WARNING: Module: $__go_module_name" \
158+
"imported at: $__go_use_caller" \
159+
"from file: $__go_module_file" \
160+
"previously imported at: $__go_use_prev_caller" \
161+
"from file: $__go_loaded_file" >&2
114162
fi
163+
continue 2
115164
fi
116-
fi
165+
((++__go_module_index))
166+
done
167+
168+
# Prevent self- and circular importing by registering info before sourcing.
169+
_GO_IMPORTED_MODULES+=("$__go_module_name")
170+
_GO_IMPORTED_MODULE_FILES+=("$__go_module_file")
171+
_GO_IMPORTED_MODULE_CALLERS+=("$__go_use_caller")
117172

118173
if ! . "$__go_module_file"; then
119174
@go.printf 'ERROR: Failed to import %s module from %s at:\n' \
@@ -123,6 +178,10 @@ for __go_module_name in "$@"; do
123178
fi
124179
done
125180

181+
unset __go_use_prev_caller
182+
unset __go_use_caller
183+
unset __go_module_index
184+
unset __go_loaded_file
126185
unset __go_loaded_module
127186
unset __go_module_file
128187
unset __go_module_name

tests/modules/main.bats

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ FIRST_CORE_MOD_SUMMARY=
1010
LAST_CORE_MOD_SUMMARY=
1111

1212
setup() {
13+
test_filter
1314
@go.create_test_go_script '@go "$@"'
1415
setup_test_modules
1516

@@ -78,8 +79,7 @@ get_first_and_last_core_module_summaries() {
7879
)
7980

8081
run "$TEST_GO_SCRIPT" modules --imported
81-
local IFS=$'\n'
82-
assert_success "${expected[*]}"
82+
assert_success "${expected[@]}"
8383
}
8484

8585
@test "$SUITE: list by class, all modules" {
@@ -94,8 +94,7 @@ get_first_and_last_core_module_summaries() {
9494
)
9595

9696
run "$TEST_GO_SCRIPT" modules
97-
local IFS=$'\n'
98-
assert_success "${expected[*]}"
97+
assert_success "${expected[@]}"
9998
}
10099

101100
@test "$SUITE: list using glob, all modules" {
@@ -105,8 +104,7 @@ get_first_and_last_core_module_summaries() {
105104
)
106105

107106
run "$TEST_GO_SCRIPT" modules '*'
108-
local IFS=$'\n'
109-
assert_success "${expected[*]}"
107+
assert_success "${expected[@]}"
110108
}
111109

112110
@test "$SUITE: list by class, only core modules present" {
@@ -117,16 +115,14 @@ get_first_and_last_core_module_summaries() {
117115
rm "${TEST_PLUGIN_MODULES_PATHS[@]/#/$TEST_GO_ROOTDIR/}" \
118116
"${TEST_PROJECT_MODULES_PATHS[@]/#/$TEST_GO_ROOTDIR/}"
119117
run "$TEST_GO_SCRIPT" modules
120-
local IFS=$'\n'
121-
assert_success "${expected[*]}"
118+
assert_success "${expected[@]}"
122119
}
123120

124121
@test "$SUITE: list using glob, only core modules present" {
125122
rm "${TEST_PLUGIN_MODULES_PATHS[@]/#/$TEST_GO_ROOTDIR/}" \
126123
"${TEST_PROJECT_MODULES_PATHS[@]/#/$TEST_GO_ROOTDIR/}"
127124
run "$TEST_GO_SCRIPT" modules '*'
128-
local IFS=$'\n'
129-
assert_success "${CORE_MODULES[*]}"
125+
assert_success "${CORE_MODULES[@]}"
130126
}
131127

132128
@test "$SUITE: paths by class" {
@@ -224,21 +220,18 @@ get_first_and_last_core_module_summaries() {
224220

225221
@test "$SUITE: list only test modules" {
226222
run "$TEST_GO_SCRIPT" modules '_*'
227-
local IFS=$'\n'
228-
assert_success "${TEST_PLUGIN_MODULES[*]}"$'\n'"${TEST_PROJECT_MODULES[*]}"
223+
assert_success "${TEST_PLUGIN_MODULES[@]}" "${TEST_PROJECT_MODULES[@]}"
229224
}
230225

231226
@test "$SUITE: list only test project modules" {
232227
run "$TEST_GO_SCRIPT" modules '_fr*'
233-
local IFS=$'\n'
234-
assert_success "${TEST_PROJECT_MODULES[*]}"
228+
assert_success "${TEST_PROJECT_MODULES[@]}"
235229
}
236230

237231
@test "$SUITE: list only modules in the _bar and _baz plugins" {
238232
run "$TEST_GO_SCRIPT" modules '_ba*/_*u*'
239233
local expected=('_bar/_plugh' '_bar/_quux' '_baz/_plugh' '_baz/_quux')
240-
local IFS=$'\n'
241-
assert_success "${expected[*]}"
234+
assert_success "${expected[@]}"
242235
}
243236

244237
@test "$SUITE: list test modules using multiple globs" {
@@ -253,6 +246,5 @@ get_first_and_last_core_module_summaries() {
253246
'_bar/_quux'
254247
'_bar/_xyzzy'
255248
)
256-
local IFS=$'\n'
257-
assert_success "${expected[*]}"
249+
assert_success "${expected[@]}"
258250
}

0 commit comments

Comments
 (0)