Skip to content

Instantly share code, notes, and snippets.

@sureshjoshi
Created March 19, 2023 05:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sureshjoshi/cbe50559eb85f21f079e45681d9b486b to your computer and use it in GitHub Desktop.
Save sureshjoshi/cbe50559eb85f21f079e45681d9b486b to your computer and use it in GitHub Desktop.
Dynamic bash completions for Pants.
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