Created
March 19, 2023 05:40
-
-
Save sureshjoshi/cbe50559eb85f21f079e45681d9b486b to your computer and use it in GitHub Desktop.
Dynamic bash completions for Pants.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function pants_completions() | |
{ | |
# Get MD5 hash of pants.toml and create a reliable tmp directory with it | |
local -r PANTS_TOML_MD5=$(md5sum pants.toml | awk '{print $1}') | |
local -r PANTS_TMPFILE="$TMPDIR/pants_completion_$PANTS_TOML_MD5" | |
# Check if the cached Pants help JSON file exists, if not create it | |
if [ ! -f "$PANTS_TMPFILE" ]; then | |
cache_help_json "$PANTS_TMPFILE" | |
fi | |
# Read the cached help-all output | |
local -r HELP_JSON=$(cat "$PANTS_TMPFILE") | |
local -r PANTS_GOALS=$(parse_goals "$HELP_JSON") | |
local current_word previous_word previous_goal | |
current_word=${COMP_WORDS[COMP_CWORD]} | |
previous_word=${COMP_WORDS[COMP_CWORD-1]} | |
# Show goals, unless the tab completion has a hyphen - then show global/goal options instead | |
if [[ $current_word == -* ]]; then | |
# Parse the goal options for the previous goal, or the global scope if there is no previous goal | |
previous_goal=$(get_previous_goal) | |
complete_options=$(parse_goal_options "$HELP_JSON" "$previous_goal") | |
COMPREPLY=( $( compgen -W "$complete_options" -- $current_word )) | |
else | |
COMPREPLY=( $( compgen -W "$PANTS_GOALS" -- $current_word )) | |
fi | |
return 0 | |
} | |
# Get the most recent goal in the command arguments, so options can be correctly applied | |
# This function will ignore hyphenated options when looking for the goal | |
# If this is empty, we're at the top-level Pants command | |
function get_previous_goal() | |
{ | |
local previous_goal i current_word | |
previous_goal= | |
for (( i=$COMP_CWORD; i > 0; --i )); do | |
current_word=${COMP_WORDS[i]} | |
if [[ $current_word != -* ]]; then | |
previous_goal=$current_word | |
break | |
fi | |
done | |
echo $previous_goal | |
} | |
# Cache the `pants help-all` output to a file, so it's not called on every tab completion. | |
# This is a performance improvement, as `pants help-all` can take a few seconds to run. | |
# The cache file is stored in a temp directory, and is named based on the MD5 hash of the pants.toml file. | |
# This means that if the pants.toml file changes, the cache file will be regenerated. | |
# This function accepts a single argument, which is the path to the cache file. | |
function cache_help_json() | |
{ | |
local pants_help_all=$(pants help-all) | |
echo "$pants_help_all" > $1 | |
} | |
# Parse the help-all JSON output to get a list of Pants goals. | |
# This function accepts a single argument, which is the help-all JSON output. | |
function parse_goals() | |
{ | |
local -r HELP_JSON="$1" | |
echo $(echo "$HELP_JSON" | jq -r '.name_to_goal_info | keys[]') | |
} | |
# Parse the help-all JSON output to get a list of Pants options for a given goal. | |
# This function accepts a two arguments, the first is the help-all JSON output, and the second is the goal name (or blank for the global scope). | |
function parse_goal_options() | |
{ | |
# If the goal is empty, we're at the top-level Pants command, so show global options | |
if [ -z "$2" ]; then | |
echo $(parse_global_options "$1") | |
else | |
echo $(parse_unscoped_goal_options "$1" "$2") | |
fi | |
} | |
# Parse the help-all JSON output to get a list of Pants options for a given goal. | |
# This function accepts a two arguments, the first is the help-all JSON output, and the second is the goal name (or blank for the global scope). | |
function parse_unscoped_goal_options() | |
{ | |
local -r HELP_JSON="$1" | |
local -r GOAL_NAME="$2" | |
# Get the basic and advanced scoped options for the goal, and combine them into a single list | |
local -r BASIC_OPTIONS=$(echo "$HELP_JSON" | jq -r '.scope_to_help_info | .["'$GOAL_NAME'"] | .basic | .[] | .unscoped_cmd_line_args[]') | |
local -r ADVANCED_OPTIONS=$(echo "$HELP_JSON" | jq -r '.scope_to_help_info | .["'$GOAL_NAME'"] | .advanced | .[] | .unscoped_cmd_line_args[]') | |
local -r ALL_OPTIONS="$BASIC_OPTIONS $ADVANCED_OPTIONS" | |
echo "$ALL_OPTIONS" | |
} | |
# Parse the help-all JSON output to get a list of Pants options at the global namespace. | |
# This includes all scoped options (global, goal, subsystem, etc). | |
function parse_global_options() | |
{ | |
local -r HELP_JSON="$1" | |
local -r GLOBAL_OPTIONS=$(parse_unscoped_goal_options "$HELP_JSON") | |
echo "$GLOBAL_OPTIONS" | |
# TODO: This is too slow to run on every tab completion, so we should cache the results | |
# local -r ALL_SCOPES=$(echo "$HELP_JSON" | jq -r '.scope_to_help_info | keys[]') | |
# local global_options basic_options advanced_options | |
# For each goal, get the basic and advanced scoped options, and combine them into a single list | |
# for scope in $ALL_SCOPES; do | |
# basic_options=$(echo "$HELP_JSON" | jq -r '.scope_to_help_info | .["'$scope'"] | .basic | .[] | .scoped_cmd_line_args[]') | |
# advanced_options=$(echo "$HELP_JSON" | jq -r '.scope_to_help_info | .["'$scope'"] | .advanced | .[] | .scoped_cmd_line_args[]') | |
# # Add the scoped options to the global options | |
# global_options="$global_options $basic_options $advanced_options" | |
# done | |
# echo "$global_options" | |
} | |
complete -o default -o bashdefault -F pants_completions pants |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment