Skip to content

Commit

Permalink
WIP: Add modules builtin command
Browse files Browse the repository at this point in the history
  • Loading branch information
mbland committed Oct 29, 2016
1 parent 584c637 commit 044e490
Show file tree
Hide file tree
Showing 4 changed files with 387 additions and 2 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,7 @@ plugins, or your own project's scripts directory by sourcing the
. $_GO_USE_MODULES 'log'
```

See the header comment in [lib/internal/use](lib/internal/use) for more
information.
Run `./go help modules` and `./go modules help` for more information.

### Feedback and contributions

Expand Down
8 changes: 8 additions & 0 deletions lib/internal/use
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@
# Module loading is idempotent, as the names of imported modules are added to
# the `_GO_IMPORTED_MODULES` array and are not sourced again if their names are
# already in the array.
#
# To see what modules are currently imported and their corresponding files, use:
#
# for modules imported by the top-level `./go` script:
# {{go}} modules --imported
#
# for modules imported within a Bash command script or function:
# @go modules --imported

declare __go_module_name
declare __go_loaded_module
Expand Down
334 changes: 334 additions & 0 deletions libexec/modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
#! /bin/bash
#
# List optional Bash modules available for import via `. $_GO_USE_MODULES`
#
# Usage:
# A list of all available plugins by class (core, plugin, project):
# {{go}} {{cmd}} [--paths|--summaries]
#
# The paths or one-line summaries for all or individual modules:
# {{go}} {{cmd}} [--paths|--summaries] <module-glob...>
#
# The modules currently imported by the script and their source paths:
# {{go}} {{cmd}} --imported
#
# Detailed help for an individual module:
# {{go}} {{cmd}} [-h|-help|--help|help] <module-name>
#
# Detailed help for the module system itself:
# {{go}} {{cmd}} [-h|-help|--help|help]
#
# Options:
# -h,--help Show the help message for a specific module
# --paths List the path of each module
# --summaries List the summary of each module
#
# Where:
# <module-name> Name of one of the installed modules
# <module-glob> Module name, plugin package (trailing /), or glob pattern
#
# Modules are reusable libraries of Bash code that may be sourced by the
# top-level `./go` script, by individual Bash command scripts, and individual
# Bash functions by executing `. $_GO_USE_MODULES` followed by one or more
# module names.
#
# For detailed information about the module system, run `{{go}} {{cmd}} help`
# without a `<module-name>` argument.

_@go.modules_help() {
local module_name="$1"
local __go_module_path

if [[ "$#" -eq '0' ]]; then
module_name='$_GO_USE_MODULES'
__go_module_path="$_GO_USE_MODULES"
elif [[ "$#" -ne '1' ]]; then
@go.printf "Please specify only one module name.\n" >&2
return 1
elif ! _@go.modules_path "$module_name"; then
@go.printf "Unknown module: $1\n" >&2
return 1
fi

local __go_cmd_desc

. "$_GO_CORE_DIR/lib/internal/command_descriptions"

if ! _@go.command_description "$__go_module_path"; then
@go.printf "ERROR: failed to parse description from %s\n" \
"$__go_cmd_path" >&2
return 1
fi
@go.printf "$module_name - $__go_cmd_desc\n"
}

_@go.modules_path() {
local module_name="$1"

__go_module_path="$_GO_CORE_DIR/lib/$module_name"
if [[ -f "$__go_module_path" ]]; then
return
fi

# Convert <plugin>/<module> to _GO_PLUGINS_DIR/<plugin>/lib/<module>
__go_module_path="$_GO_PLUGINS_DIR/${module_name/\///lib/}"
if [[ -n "$_GO_PLUGINS_DIR" && -f "$__go_module_path" ]]; then
return
fi

__go_module_path="$_GO_SCRIPTS_DIR/lib/$module_name"
if [[ -f "$__go_module_path" ]]; then
return
fi
return 1
}

_@go.modules_find_all_in_dir() {
local module_dir="$1"
local glob="${2:-*}"
local module_path

for module_path in "$module_dir"/lib/$glob; do
if [[ -f "$module_path" ]]; then
__go_modules+=("$module_path")
fi
done
}

_@go.modules_summaries() {
local module_path
local __go_cmd_desc

. "$_GO_CORE_DIR/lib/internal/command_descriptions"

for module_path in "${__go_modules[@]}"; do
if ! _@go.command_summary "$module_path"; then
@go.printf "ERROR: failed to parse summary from %s\n" "$module_path" >&2
return 1
fi
__go_modules_summaries+=("$__go_cmd_desc")
done
}

_@go.modules_produce_listing() {
local action="$1"
local modules=("${__go_modules[@]#$_GO_CORE_DIR/lib/}")
modules=("${modules[@]#$_GO_SCRIPTS_DIR/lib/}")

if [[ -n "$_GO_PLUGINS_DIR" ]]; then
modules=("${modules[@]#$_GO_PLUGINS_DIR/}")
modules=("${modules[@]/lib\//}")
fi

if [[ -z "$action" ]]; then
__go_modules=("${modules[@]}")
return
fi

. "$_GO_CORE_DIR/lib/internal/formatting"

local __go_padded_items=()
local __go_zipped_items=()

_@go.pad_items 'modules'
modules=("${__go_padded_items[@]}")

case "$action" in
paths)
__go_modules=("${__go_modules[@]#$_GO_ROOTDIR/}")
_@go.zip_items 'modules' '__go_modules'
;;
summaries)
local __go_modules_summaries=()
_@go.modules_summaries
_@go.zip_items 'modules' '__go_modules_summaries'
;;
*)
@go.printf 'ERROR: Unknown action: %s\n' "$action" >&2
return 1
esac
__go_modules=("${__go_zipped_items[@]}")
}

_@go.modules_search() {
local glob="${1:-*}"
local plugin_glob=("*" "$glob")
local plugin

if [[ "$glob" =~ / ]]; then
plugin_glob[0]="${glob%/*}"
plugin_glob[1]="${glob#*/}"
# Since it's plugin glob, prevent deep core/lib and project/lib matches.
glob='/'
fi

_@go.modules_find_all_in_dir "$_GO_CORE_DIR" "$glob"
__go_core_modules_end="${#__go_modules[@]}"

if [[ -n "$_GO_PLUGINS_DIR" ]]; then
for plugin in "$_GO_PLUGINS_DIR/"${plugin_glob[0]:-*}; do
_@go.modules_find_all_in_dir "$plugin" "${plugin_glob[1]:-*}"
done
fi
__go_plugin_modules_end="${#__go_modules[@]}"

_@go.modules_find_all_in_dir "$_GO_SCRIPTS_DIR" "$glob"
__go_project_modules_end="${#__go_modules[@]}"
}

_@go.modules_emit_class() {
local class="$1"
local action="$2"
local begin="$3"
local end="$4"
local delimiter=$'\n'' '
local __go_modules=("${__go_all_modules[@]:$begin:$((end - begin))}")

if [[ "${#__go_modules[@]}" -ne '0' ]]; then
_@go.modules_produce_listing "$action"
printf "From the %s:%s\n\n" "$class" "${__go_modules[*]/#/$delimiter}"
fi
}

_@go.modules_list_by_class() {
local action="$1"
local __go_modules=()
local __go_core_modules_end=0
local __go_plugin_modules_end=0
local __go_project_modules_end=0
local __go_all_modules

_@go.modules_search
__go_all_modules=("${__go_modules[@]}")
_@go.modules_emit_class 'core framework library' "$action" \
0 "$__go_core_modules_end"
_@go.modules_emit_class 'installed plugin libraries' "$action" \
"$__go_core_modules_end" "$__go_plugin_modules_end"
_@go.modules_emit_class 'project library' "$action" \
"$__go_plugin_modules_end" "$__go_project_modules_end"
}

_@go.modules_list() {
local action="$1"
shift
local module_specs=("$@")
local __go_modules=()
local __go_module_path
local module_spec

for module_spec in "${module_specs[@]}"; do
if [[ "$module_spec" == '*' ]]; then
if [[ "${#module_specs[@]}" -ne 1 ]]; then
@go.printf "Do not specify other arguments when '*' is present.\n" >&2
return 1
fi
_@go.modules_search
elif [[ "$module_spec" =~ \*|/$ ]]; then
_@go.modules_search "$module_spec"
elif ! _@go.modules_path "$module_spec"; then
@go.printf "Unknown module: $module_spec\n" >&2
return 1
else
__go_modules+=("$__go_module_path")
fi
done

_@go.modules_produce_listing "$action"
local IFS=$'\n'
echo "${__go_modules[*]}"
}

_@go.modules_tab_completion() {
local word_index="$1"
shift
local args=("$@")
local first="${args[0]}"
local word=("${args[$word_index]}")
local glob="${word}*"
local flags=('-h' '-help' '--help' 'help' '--paths' '--summaries')
local origIFS="$IFS"
local plugins=''
local flags_pattern

if [[ "$#" -le '1' ]]; then
flags+=('--imported')
fi

local IFS='|'
flags_pattern="^(${flags[*]})$"
IFS="$origIFS"

if [[ "$#" -eq '0' || "$word_index" -eq '0' ]]; then
echo "${flags[*]}"
return
elif [[ ! "$first" =~ $flags_pattern || "$first" == '--imported' ]]; then
return
fi

local __go_modules
local plugin
local module

if [[ -n "$_GO_PLUGINS_DIR" ]]; then
if [[ "$glob" =~ / ]]; then
plugin="${glob%/*}"
_@go.modules_find_all_in_dir "$_GO_PLUGINS_DIR/$plugin" "${glob#*/}*"
_@go.modules_produce_listing
echo "${__go_modules[*]}"
return
fi

for plugin in "$_GO_PLUGINS_DIR/"$glob; do
for module in "$plugin/lib/"*; do
if [[ -f "$module" ]]; then
__go_modules+=("$plugin/")
break
fi
done
done
fi

_@go.modules_find_all_in_dir "$_GO_CORE_DIR" "$glob"
_@go.modules_find_all_in_dir "$_GO_SCRIPTS_DIR" "$glob"
_@go.modules_produce_listing
echo "${__go_modules[*]}"
}

_@go.modules() {
local action="$1"
shift

case "$action" in
--complete)
# Tab completions
_@go.modules_tab_completion "$@"
;;
-h|-help|--help|help)
_@go.modules_help "$@"
;;
''|--paths|--summaries)
action="${action#--}"
if [[ "$#" -eq '0' ]]; then
_@go.modules_list_by_class "$action"
else
_@go.modules_list "$action" "$@"
fi
;;
--imported)
if [[ "$#" -ne 0 ]]; then
@go.printf 'The --imported option takes no other arguments.\n' >&2
return 1
elif [[ "${#_GO_IMPORTED_MODULES[@]}" -ne '0' ]]; then
_@go.modules_list 'paths' "${_GO_IMPORTED_MODULES[@]}"
fi
;;
-*)
@go.printf "Unknown option: $action\n" >&2
return 1
;;
*)
_@go.modules_list '' "$action" "$@"
esac
}

_@go.modules "$@"

0 comments on commit 044e490

Please sign in to comment.