From e947dba1b65273e2c6e07f73dafcb240aa63b4f0 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 19 Aug 2026 14:31:07 +0300 Subject: [PATCH 1/2] - Drop support for Ruby 3.2 --- .github/workflows/test.yml | 2 +- .rubocop.yml | 2 +- bashly.gemspec | 2 +- examples/completions-advanced/cli | 794 ++++++++++++++++++++++++++++++ 4 files changed, 797 insertions(+), 3 deletions(-) create mode 100644 examples/completions-advanced/cli diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63574e7f..35fc4177 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: LC_ALL: en_US.UTF-8 strategy: - matrix: { ruby: ['3.2', '3.3', '3.4', '4.0'] } + matrix: { ruby: ['3.3', '3.4', '4.0'] } steps: - name: Checkout code diff --git a/.rubocop.yml b/.rubocop.yml index c918e5c4..ae4877b0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,7 +8,7 @@ inherit_gem: - rspec.yml AllCops: - TargetRubyVersion: 3.2 + TargetRubyVersion: 3.3 SuggestExtensions: false Exclude: - dev/**/* diff --git a/bashly.gemspec b/bashly.gemspec index 902ddfa3..374f3a95 100644 --- a/bashly.gemspec +++ b/bashly.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.executables = ['bashly'] s.homepage = 'https://github.com/bashly-framework/bashly' s.license = 'MIT' - s.required_ruby_version = '>= 3.2' + s.required_ruby_version = '>= 3.3' s.add_dependency 'colsole', '~> 1.0' s.add_dependency 'completely', '~> 0.8.0' diff --git a/examples/completions-advanced/cli b/examples/completions-advanced/cli new file mode 100644 index 00000000..3296cb72 --- /dev/null +++ b/examples/completions-advanced/cli @@ -0,0 +1,794 @@ +#!/usr/bin/env bash +# This script was generated by bashly 1.4.0 (https://bashly.dev) +# Modifying it manually is not recommended + +# :wrapper.bash3_bouncer +if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 2))); then + printf "bash version 4.2 or higher is required\n" >&2 + exit 1 +fi + +# :command.master_script + +# :command.version_command +version_command() { + echo "$version" +} + +# :command.usage +cli_usage() { + printf "cli - Advanced runtime completions example\n\n" + + printf "%s\n" "Usage:" + printf " cli COMMAND\n" + printf " cli [COMMAND] --help | -h\n" + printf " cli --version | -v\n" + echo + # :command.usage_commands + printf "%s\n" "Commands:" + printf " %s Generate a shell completion script\n" "completions" + printf " %s Deploy a branch\n" "deploy " + echo + + # :command.long_usage + if [[ -n "$long_usage" ]]; then + # :command.usage_options + printf "%s\n" "Options:" + + # :command.usage_fixed_flags + printf " %s\n" "--help, -h" + printf " Show this help\n" + echo + printf " %s\n" "--version, -v" + printf " Show version number\n" + echo + + fi +} + +# :command.usage +cli_completions_usage() { + printf "cli completions - Generate a shell completion script\n\n" + + printf "%s\n" "Usage:" + printf " cli completions [SHELL]\n" + printf " cli completions --help | -h\n" + echo + + # :command.long_usage + if [[ -n "$long_usage" ]]; then + # :command.usage_options + printf "%s\n" "Options:" + + # :command.usage_fixed_flags + printf " %s\n" "--help, -h" + printf " Show this help\n" + echo + + # :command.usage_args + printf "%s\n" "Arguments:" + + # :argument.usage + printf " %s\n" "SHELL" + printf " Shell to generate completions for\n" + printf " %s\n" "Allowed: bash" + printf " %s\n" "Default: bash" + echo + + fi +} + +# :command.usage +cli_deploy_usage() { + printf "cli deploy - Deploy a branch\n\n" + + printf "%s\n" "Usage:" + printf " cli deploy BRANCH [ENVIRONMENT] [OPTIONS]\n" + printf " cli deploy --help | -h\n" + echo + + # :command.long_usage + if [[ -n "$long_usage" ]]; then + # :command.usage_options + printf "%s\n" "Options:" + + # :command.usage_flags + # :flag.usage + printf " %s\n" "--config FILE" + printf " Deployment configuration file\n" + echo + + # :flag.usage + printf " %s\n" "--directory PATH" + printf " Deployment directory\n" + echo + + # :command.usage_fixed_flags + printf " %s\n" "--help, -h" + printf " Show this help\n" + echo + + # :command.usage_args + printf "%s\n" "Arguments:" + + # :argument.usage + printf " %s\n" "BRANCH" + printf " Branch to deploy\n" + echo + + # :argument.usage + printf " %s\n" "ENVIRONMENT" + printf " Environment to deploy to\n" + echo + + fi +} + +# :command.normalize_input +# :command.normalize_input_function +normalize_input() { + local arg passthru flags + passthru=false + + while [[ $# -gt 0 ]]; do + arg="$1" + if [[ $passthru == true ]]; then + input+=("$arg") + elif [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then + input+=("${BASH_REMATCH[1]}") + input+=("${BASH_REMATCH[2]}") + elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then + input+=("${BASH_REMATCH[1]}") + input+=("${BASH_REMATCH[2]}") + elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then + flags="${BASH_REMATCH[1]}" + for ((i = 0; i < ${#flags}; i++)); do + input+=("-${flags:i:1}") + done + elif [[ "$arg" == "--" ]]; then + passthru=true + input+=("$arg") + else + input+=("$arg") + fi + + shift + done +} + +# :command.inspect_args +inspect_args() { + local k + + if ((${#args[@]})); then + readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort) + echo args: + for k in "${sorted_keys[@]}"; do + echo "- \${args[$k]} = ${args[$k]}" + done + else + echo args: none + fi + + if ((${#deps[@]})); then + readarray -t sorted_keys < <(printf '%s\n' "${!deps[@]}" | sort) + echo + echo deps: + for k in "${sorted_keys[@]}"; do + echo "- \${deps[$k]} = ${deps[$k]}" + done + fi + + if ((${#env_var_names[@]})); then + readarray -t sorted_names < <(printf '%s\n' "${env_var_names[@]}" | sort) + echo + echo "environment variables:" + for k in "${sorted_names[@]}"; do + echo "- \$$k = ${!k:-}" + done + fi +} + +# :command.user_lib +# src/lib/completions.sh +completion_environments() { + printf 'development\nstaging\n' +} + +# :command.command_functions +# :command.function +cli_completions_command() { + + # src/completions_command.sh + send_completions "${args[shell]}" + +} + +# :command.function +cli_deploy_command() { + + # src/deploy_command.sh + inspect_args + +} + +# :command.parse_requirements +parse_requirements() { + local key + + # :command.fixed_flags_filter + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + --version | -v) + version_command + exit + ;; + + --help | -h) + long_usage=yes + cli_usage + exit + ;; + + *) + break + ;; + + esac + done + + # :command.command_filter + action=${1:-} + + case $action in + -*) ;; + + completions) + action="completions" + shift + cli_completions_parse_requirements "$@" + shift $# + ;; + + deploy) + action="deploy" + shift + cli_deploy_parse_requirements "$@" + shift $# + ;; + + # :command.command_fallback + "") + cli_usage >&2 + exit 1 + ;; + + *) + printf "invalid command: %s\n" "$action" >&2 + exit 1 + ;; + + esac + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -?*) + printf "invalid option: %s\n" "$key" >&2 + exit 1 + ;; + + *) + # :command.parse_requirements_case + # :command.parse_requirements_case_simple + printf "invalid argument: %s\n" "$key" >&2 + exit 1 + + ;; + + esac + done + +} + +# :command.parse_requirements +cli_completions_parse_requirements() { + local key + + # :command.fixed_flags_filter + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + --help | -h) + long_usage=yes + cli_completions_usage + exit + ;; + + *) + break + ;; + + esac + done + + # :command.command_filter + action="completions" + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -?*) + printf "invalid option: %s\n" "$key" >&2 + exit 1 + ;; + + *) + # :command.parse_requirements_case + # :command.parse_requirements_case_simple + # :argument.case + if [[ -z ${args['shell']+x} ]]; then + args['shell']=$1 + shift + else + printf "invalid argument: %s\n" "$key" >&2 + exit 1 + fi + + ;; + + esac + done + + # :command.default_assignments + [[ -n ${args['shell']:-} ]] || args['shell']="bash" + + # :command.whitelist_filter + if [[ -n ${args['shell']:-} ]] && [[ ! ${args['shell']:-} =~ ^(bash)$ ]]; then + printf "%s\n" "shell must be one of: bash" >&2 + exit 1 + fi + +} + +# :command.parse_requirements +cli_deploy_parse_requirements() { + local key + + # :command.fixed_flags_filter + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + --help | -h) + long_usage=yes + cli_deploy_usage + exit + ;; + + *) + break + ;; + + esac + done + + # :command.command_filter + action="deploy" + + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + # :flag.case + --config) + + # :flag.case_arg + if [[ -n ${2+x} ]]; then + args['--config']="$2" + shift + shift + else + printf "%s\n" "--config requires an argument: --config FILE" >&2 + exit 1 + fi + ;; + + # :flag.case + --directory) + + # :flag.case_arg + if [[ -n ${2+x} ]]; then + args['--directory']="$2" + shift + shift + else + printf "%s\n" "--directory requires an argument: --directory PATH" >&2 + exit 1 + fi + ;; + + -?*) + printf "invalid option: %s\n" "$key" >&2 + exit 1 + ;; + + *) + # :command.parse_requirements_case + # :command.parse_requirements_case_simple + # :argument.case + if [[ -z ${args['branch']+x} ]]; then + args['branch']=$1 + shift + # :argument.case + elif [[ -z ${args['environment']+x} ]]; then + args['environment']=$1 + shift + else + printf "invalid argument: %s\n" "$key" >&2 + exit 1 + fi + + ;; + + esac + done + + # :command.required_args_filter + if [[ -z ${args['branch']+x} ]]; then + printf "missing required argument: BRANCH\nusage: cli deploy BRANCH [ENVIRONMENT] [OPTIONS]\n" >&2 + + exit 1 + fi + +} + +# :command.completions +completion_run() { + local -a completion_words=("$@") + local -A completion_blocked_flags=() + local -A completion_emitted=() + local -a completion_options=() + local -A completion_options_seen=() + local completion_count=${#completion_words[@]} + local completion_current="" + + if [[ $completion_count -gt 0 ]]; then + completion_current="${completion_words[$((completion_count - 1))]}" + unset 'completion_words[completion_count - 1]' + fi + + cli_completion "$completion_current" "${completion_words[@]}" + + local completion_options_string="" + if [[ ${#completion_options[@]} -gt 0 ]]; then + completion_options_string="$( + IFS=, + printf '%s' "${completion_options[*]}" + )" + fi + printf ':options=%s\n' "$completion_options_string" +} + +completion_candidates() { + local completion_current="$1" + shift + + local completion_candidate + for completion_candidate in "$@"; do + if [[ $completion_candidate == "$completion_current"* ]] && + [[ -z "${completion_emitted[$completion_candidate]:-}" ]]; then + printf '%s\n' "$completion_candidate" + completion_emitted["$completion_candidate"]=1 + fi + done +} + +completion_flag_candidates() { + local completion_flag="$1" + local completion_current="$2" + shift 2 + + if [[ -z "${completion_blocked_flags[$completion_flag]:-}" ]]; then + completion_candidates "$completion_current" "$@" + fi +} + +completion_add_options() { + local completion_option + for completion_option in "$@"; do + if [[ -z "${completion_options_seen[$completion_option]:-}" ]]; then + completion_options+=("$completion_option") + completion_options_seen["$completion_option"]=1 + fi + done +} + +completion_dynamic_candidates() { + local completion_current="$1" + local completion_output="$2" + + [[ -n $completion_output ]] || return + + local completion_candidate + while IFS= read -r completion_candidate; do + if [[ -n $completion_candidate ]]; then + completion_candidates "$completion_current" "$completion_candidate" + fi + done <<<"$completion_output" +} + +# :command.completion_script +send_completions() { + local completion_shell="${1:-bash}" + + case "$completion_shell" in + bash) send_completions_bash ;; + *) + printf 'unsupported shell: %s\n' "$completion_shell" >&2 + return 1 + ;; + esac +} + +# :command.completion_script_bash +send_completions_bash() { + cat <<'BASHLY_COMPLETIONS' +_cli_completions() { + local completion_command="${COMP_WORDS[0]}" + local completion_current="${COMP_WORDS[COMP_CWORD]:-}" + local completion_word_count=$((COMP_CWORD - 1)) + local -a completion_words=() + local -a completion_response=() + local -a completion_options=() + local -A completion_seen=() + + if [[ $completion_word_count -gt 0 ]]; then + completion_words=("${COMP_WORDS[@]:1:completion_word_count}") + fi + + COMPREPLY=() + while IFS= read -r completion_line; do + completion_response+=("$completion_line") + done < <( + "$completion_command" __complete \ + "${completion_words[@]}" \ + "$completion_current" + ) + + local completion_directive="${completion_response[-1]:-}" + if [[ $completion_directive == :options=* ]]; then + unset 'completion_response[-1]' + IFS=, read -r -a completion_options <<< "${completion_directive#:options=}" + fi + + COMPREPLY=("${completion_response[@]}") + local completion_candidate + for completion_candidate in "${COMPREPLY[@]}"; do + completion_seen["$completion_candidate"]=1 + done + + local completion_option + local completion_files=false + local completion_directories=false + for completion_option in "${completion_options[@]}"; do + case "$completion_option" in + files) completion_files=true ;; + directories) completion_directories=true ;; + no-space) compopt -o nospace ;; + esac + done + + if [[ $completion_files == true ]]; then + while IFS= read -r completion_candidate; do + if [[ -z "${completion_seen[$completion_candidate]:-}" ]]; then + COMPREPLY+=("$completion_candidate") + completion_seen["$completion_candidate"]=1 + fi + done < <(compgen -f -- "$completion_current") + elif [[ $completion_directories == true ]]; then + while IFS= read -r completion_candidate; do + if [[ -z "${completion_seen[$completion_candidate]:-}" ]]; then + COMPREPLY+=("$completion_candidate") + completion_seen["$completion_candidate"]=1 + fi + done < <(compgen -d -- "$completion_current") + fi +} + +complete -F _cli_completions cli +BASHLY_COMPLETIONS +} + +# :command.completion_function +cli_completion() { + local completion_current="$1" + shift + + while [[ $# -gt 0 ]]; do + case "$1" in + completions) + shift + cli_completions_completion "$completion_current" "$@" + return + ;; + deploy) + shift + cli_deploy_completion "$completion_current" "$@" + return + ;; + --help | -h) + completion_blocked_flags["--help"]=1 + shift + ;; + --version | -v) + completion_blocked_flags["--version"]=1 + shift + ;; + *) + return + ;; + esac + done + completion_candidates "$completion_current" completions deploy + + if [[ $completion_current == -* ]]; then + completion_flag_candidates --help "$completion_current" --help -h + completion_flag_candidates --version "$completion_current" --version -v + fi + +} + +# :command.completion_function +cli_completions_completion() { + local completion_current="$1" + local completion_arg_index=0 + shift + + while [[ $# -gt 0 ]]; do + case "$1" in + --help | -h) + completion_blocked_flags["--help"]=1 + shift + ;; + *) + if [[ $completion_arg_index -ge 1 ]]; then + return + fi + completion_arg_index=$((completion_arg_index + 1)) + shift + + ;; + esac + done + + if [[ $completion_current == -* ]]; then + completion_flag_candidates --help "$completion_current" --help -h + fi + + if [[ $completion_current != -* ]]; then + case "$completion_arg_index" in + 0) + local -a completion_values=( + "bash" + ) + completion_candidates "$completion_current" "${completion_values[@]}" + + ;; + esac + fi + +} + +# :command.completion_function +cli_deploy_completion() { + local completion_current="$1" + local completion_arg_index=0 + shift + + while [[ $# -gt 0 ]]; do + case "$1" in + --help | -h) + completion_blocked_flags["--help"]=1 + shift + ;; + --config) + shift + if [[ $# -eq 0 ]]; then + completion_add_options files + + return + fi + shift + completion_blocked_flags["--config"]=1 + + ;; + --directory) + shift + if [[ $# -eq 0 ]]; then + completion_add_options directories + + return + fi + shift + completion_blocked_flags["--directory"]=1 + + ;; + *) + if [[ $completion_arg_index -ge 2 ]]; then + return + fi + completion_arg_index=$((completion_arg_index + 1)) + shift + + ;; + esac + done + + if [[ $completion_current == -* ]]; then + completion_flag_candidates --help "$completion_current" --help -h + completion_flag_candidates --config "$completion_current" --config + completion_flag_candidates --directory "$completion_current" --directory + fi + + if [[ $completion_current != -* ]]; then + case "$completion_arg_index" in + 0) + if completion_output="$({ git branch --format='%(refname:short)'; } 2>/dev/null)"; then + completion_dynamic_candidates "$completion_current" "$completion_output" + fi + + ;; + 1) + completion_candidates "$completion_current" staging production + if completion_output="$({ completion_environments; } 2>/dev/null)"; then + completion_dynamic_candidates "$completion_current" "$completion_output" + fi + completion_add_options no-space + + ;; + esac + fi + +} + +# :command.initialize +initialize() { + declare -g version="0.1.0" + set -e + +} + +# :command.run +run() { + # :command.globals + declare -g long_usage='' + declare -g -A args=() + declare -g -A deps=() + declare -g -a env_var_names=() + declare -g -a input=() + + normalize_input "$@" + parse_requirements "${input[@]}" + + case "$action" in + "completions") cli_completions_command ;; + "deploy") cli_deploy_command ;; + esac +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # :command.start + command_line_args=("$@") + if [[ "${command_line_args[0]:-}" == "__complete" ]]; then + completion_run "${command_line_args[@]:1}" + else + initialize + run "${command_line_args[@]}" + fi + +fi From 62ce9f5046b28fc078dfe45a7998ee0e6a85cbb0 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 19 Aug 2026 14:33:33 +0300 Subject: [PATCH 2/2] remove example artifact --- examples/completions-advanced/cli | 794 ------------------------------ 1 file changed, 794 deletions(-) delete mode 100644 examples/completions-advanced/cli diff --git a/examples/completions-advanced/cli b/examples/completions-advanced/cli deleted file mode 100644 index 3296cb72..00000000 --- a/examples/completions-advanced/cli +++ /dev/null @@ -1,794 +0,0 @@ -#!/usr/bin/env bash -# This script was generated by bashly 1.4.0 (https://bashly.dev) -# Modifying it manually is not recommended - -# :wrapper.bash3_bouncer -if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 2))); then - printf "bash version 4.2 or higher is required\n" >&2 - exit 1 -fi - -# :command.master_script - -# :command.version_command -version_command() { - echo "$version" -} - -# :command.usage -cli_usage() { - printf "cli - Advanced runtime completions example\n\n" - - printf "%s\n" "Usage:" - printf " cli COMMAND\n" - printf " cli [COMMAND] --help | -h\n" - printf " cli --version | -v\n" - echo - # :command.usage_commands - printf "%s\n" "Commands:" - printf " %s Generate a shell completion script\n" "completions" - printf " %s Deploy a branch\n" "deploy " - echo - - # :command.long_usage - if [[ -n "$long_usage" ]]; then - # :command.usage_options - printf "%s\n" "Options:" - - # :command.usage_fixed_flags - printf " %s\n" "--help, -h" - printf " Show this help\n" - echo - printf " %s\n" "--version, -v" - printf " Show version number\n" - echo - - fi -} - -# :command.usage -cli_completions_usage() { - printf "cli completions - Generate a shell completion script\n\n" - - printf "%s\n" "Usage:" - printf " cli completions [SHELL]\n" - printf " cli completions --help | -h\n" - echo - - # :command.long_usage - if [[ -n "$long_usage" ]]; then - # :command.usage_options - printf "%s\n" "Options:" - - # :command.usage_fixed_flags - printf " %s\n" "--help, -h" - printf " Show this help\n" - echo - - # :command.usage_args - printf "%s\n" "Arguments:" - - # :argument.usage - printf " %s\n" "SHELL" - printf " Shell to generate completions for\n" - printf " %s\n" "Allowed: bash" - printf " %s\n" "Default: bash" - echo - - fi -} - -# :command.usage -cli_deploy_usage() { - printf "cli deploy - Deploy a branch\n\n" - - printf "%s\n" "Usage:" - printf " cli deploy BRANCH [ENVIRONMENT] [OPTIONS]\n" - printf " cli deploy --help | -h\n" - echo - - # :command.long_usage - if [[ -n "$long_usage" ]]; then - # :command.usage_options - printf "%s\n" "Options:" - - # :command.usage_flags - # :flag.usage - printf " %s\n" "--config FILE" - printf " Deployment configuration file\n" - echo - - # :flag.usage - printf " %s\n" "--directory PATH" - printf " Deployment directory\n" - echo - - # :command.usage_fixed_flags - printf " %s\n" "--help, -h" - printf " Show this help\n" - echo - - # :command.usage_args - printf "%s\n" "Arguments:" - - # :argument.usage - printf " %s\n" "BRANCH" - printf " Branch to deploy\n" - echo - - # :argument.usage - printf " %s\n" "ENVIRONMENT" - printf " Environment to deploy to\n" - echo - - fi -} - -# :command.normalize_input -# :command.normalize_input_function -normalize_input() { - local arg passthru flags - passthru=false - - while [[ $# -gt 0 ]]; do - arg="$1" - if [[ $passthru == true ]]; then - input+=("$arg") - elif [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then - input+=("${BASH_REMATCH[1]}") - input+=("${BASH_REMATCH[2]}") - elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then - input+=("${BASH_REMATCH[1]}") - input+=("${BASH_REMATCH[2]}") - elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then - flags="${BASH_REMATCH[1]}" - for ((i = 0; i < ${#flags}; i++)); do - input+=("-${flags:i:1}") - done - elif [[ "$arg" == "--" ]]; then - passthru=true - input+=("$arg") - else - input+=("$arg") - fi - - shift - done -} - -# :command.inspect_args -inspect_args() { - local k - - if ((${#args[@]})); then - readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort) - echo args: - for k in "${sorted_keys[@]}"; do - echo "- \${args[$k]} = ${args[$k]}" - done - else - echo args: none - fi - - if ((${#deps[@]})); then - readarray -t sorted_keys < <(printf '%s\n' "${!deps[@]}" | sort) - echo - echo deps: - for k in "${sorted_keys[@]}"; do - echo "- \${deps[$k]} = ${deps[$k]}" - done - fi - - if ((${#env_var_names[@]})); then - readarray -t sorted_names < <(printf '%s\n' "${env_var_names[@]}" | sort) - echo - echo "environment variables:" - for k in "${sorted_names[@]}"; do - echo "- \$$k = ${!k:-}" - done - fi -} - -# :command.user_lib -# src/lib/completions.sh -completion_environments() { - printf 'development\nstaging\n' -} - -# :command.command_functions -# :command.function -cli_completions_command() { - - # src/completions_command.sh - send_completions "${args[shell]}" - -} - -# :command.function -cli_deploy_command() { - - # src/deploy_command.sh - inspect_args - -} - -# :command.parse_requirements -parse_requirements() { - local key - - # :command.fixed_flags_filter - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - --version | -v) - version_command - exit - ;; - - --help | -h) - long_usage=yes - cli_usage - exit - ;; - - *) - break - ;; - - esac - done - - # :command.command_filter - action=${1:-} - - case $action in - -*) ;; - - completions) - action="completions" - shift - cli_completions_parse_requirements "$@" - shift $# - ;; - - deploy) - action="deploy" - shift - cli_deploy_parse_requirements "$@" - shift $# - ;; - - # :command.command_fallback - "") - cli_usage >&2 - exit 1 - ;; - - *) - printf "invalid command: %s\n" "$action" >&2 - exit 1 - ;; - - esac - - # :command.parse_requirements_while - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - - -?*) - printf "invalid option: %s\n" "$key" >&2 - exit 1 - ;; - - *) - # :command.parse_requirements_case - # :command.parse_requirements_case_simple - printf "invalid argument: %s\n" "$key" >&2 - exit 1 - - ;; - - esac - done - -} - -# :command.parse_requirements -cli_completions_parse_requirements() { - local key - - # :command.fixed_flags_filter - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - --help | -h) - long_usage=yes - cli_completions_usage - exit - ;; - - *) - break - ;; - - esac - done - - # :command.command_filter - action="completions" - - # :command.parse_requirements_while - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - - -?*) - printf "invalid option: %s\n" "$key" >&2 - exit 1 - ;; - - *) - # :command.parse_requirements_case - # :command.parse_requirements_case_simple - # :argument.case - if [[ -z ${args['shell']+x} ]]; then - args['shell']=$1 - shift - else - printf "invalid argument: %s\n" "$key" >&2 - exit 1 - fi - - ;; - - esac - done - - # :command.default_assignments - [[ -n ${args['shell']:-} ]] || args['shell']="bash" - - # :command.whitelist_filter - if [[ -n ${args['shell']:-} ]] && [[ ! ${args['shell']:-} =~ ^(bash)$ ]]; then - printf "%s\n" "shell must be one of: bash" >&2 - exit 1 - fi - -} - -# :command.parse_requirements -cli_deploy_parse_requirements() { - local key - - # :command.fixed_flags_filter - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - --help | -h) - long_usage=yes - cli_deploy_usage - exit - ;; - - *) - break - ;; - - esac - done - - # :command.command_filter - action="deploy" - - # :command.parse_requirements_while - while [[ $# -gt 0 ]]; do - key="$1" - case "$key" in - # :flag.case - --config) - - # :flag.case_arg - if [[ -n ${2+x} ]]; then - args['--config']="$2" - shift - shift - else - printf "%s\n" "--config requires an argument: --config FILE" >&2 - exit 1 - fi - ;; - - # :flag.case - --directory) - - # :flag.case_arg - if [[ -n ${2+x} ]]; then - args['--directory']="$2" - shift - shift - else - printf "%s\n" "--directory requires an argument: --directory PATH" >&2 - exit 1 - fi - ;; - - -?*) - printf "invalid option: %s\n" "$key" >&2 - exit 1 - ;; - - *) - # :command.parse_requirements_case - # :command.parse_requirements_case_simple - # :argument.case - if [[ -z ${args['branch']+x} ]]; then - args['branch']=$1 - shift - # :argument.case - elif [[ -z ${args['environment']+x} ]]; then - args['environment']=$1 - shift - else - printf "invalid argument: %s\n" "$key" >&2 - exit 1 - fi - - ;; - - esac - done - - # :command.required_args_filter - if [[ -z ${args['branch']+x} ]]; then - printf "missing required argument: BRANCH\nusage: cli deploy BRANCH [ENVIRONMENT] [OPTIONS]\n" >&2 - - exit 1 - fi - -} - -# :command.completions -completion_run() { - local -a completion_words=("$@") - local -A completion_blocked_flags=() - local -A completion_emitted=() - local -a completion_options=() - local -A completion_options_seen=() - local completion_count=${#completion_words[@]} - local completion_current="" - - if [[ $completion_count -gt 0 ]]; then - completion_current="${completion_words[$((completion_count - 1))]}" - unset 'completion_words[completion_count - 1]' - fi - - cli_completion "$completion_current" "${completion_words[@]}" - - local completion_options_string="" - if [[ ${#completion_options[@]} -gt 0 ]]; then - completion_options_string="$( - IFS=, - printf '%s' "${completion_options[*]}" - )" - fi - printf ':options=%s\n' "$completion_options_string" -} - -completion_candidates() { - local completion_current="$1" - shift - - local completion_candidate - for completion_candidate in "$@"; do - if [[ $completion_candidate == "$completion_current"* ]] && - [[ -z "${completion_emitted[$completion_candidate]:-}" ]]; then - printf '%s\n' "$completion_candidate" - completion_emitted["$completion_candidate"]=1 - fi - done -} - -completion_flag_candidates() { - local completion_flag="$1" - local completion_current="$2" - shift 2 - - if [[ -z "${completion_blocked_flags[$completion_flag]:-}" ]]; then - completion_candidates "$completion_current" "$@" - fi -} - -completion_add_options() { - local completion_option - for completion_option in "$@"; do - if [[ -z "${completion_options_seen[$completion_option]:-}" ]]; then - completion_options+=("$completion_option") - completion_options_seen["$completion_option"]=1 - fi - done -} - -completion_dynamic_candidates() { - local completion_current="$1" - local completion_output="$2" - - [[ -n $completion_output ]] || return - - local completion_candidate - while IFS= read -r completion_candidate; do - if [[ -n $completion_candidate ]]; then - completion_candidates "$completion_current" "$completion_candidate" - fi - done <<<"$completion_output" -} - -# :command.completion_script -send_completions() { - local completion_shell="${1:-bash}" - - case "$completion_shell" in - bash) send_completions_bash ;; - *) - printf 'unsupported shell: %s\n' "$completion_shell" >&2 - return 1 - ;; - esac -} - -# :command.completion_script_bash -send_completions_bash() { - cat <<'BASHLY_COMPLETIONS' -_cli_completions() { - local completion_command="${COMP_WORDS[0]}" - local completion_current="${COMP_WORDS[COMP_CWORD]:-}" - local completion_word_count=$((COMP_CWORD - 1)) - local -a completion_words=() - local -a completion_response=() - local -a completion_options=() - local -A completion_seen=() - - if [[ $completion_word_count -gt 0 ]]; then - completion_words=("${COMP_WORDS[@]:1:completion_word_count}") - fi - - COMPREPLY=() - while IFS= read -r completion_line; do - completion_response+=("$completion_line") - done < <( - "$completion_command" __complete \ - "${completion_words[@]}" \ - "$completion_current" - ) - - local completion_directive="${completion_response[-1]:-}" - if [[ $completion_directive == :options=* ]]; then - unset 'completion_response[-1]' - IFS=, read -r -a completion_options <<< "${completion_directive#:options=}" - fi - - COMPREPLY=("${completion_response[@]}") - local completion_candidate - for completion_candidate in "${COMPREPLY[@]}"; do - completion_seen["$completion_candidate"]=1 - done - - local completion_option - local completion_files=false - local completion_directories=false - for completion_option in "${completion_options[@]}"; do - case "$completion_option" in - files) completion_files=true ;; - directories) completion_directories=true ;; - no-space) compopt -o nospace ;; - esac - done - - if [[ $completion_files == true ]]; then - while IFS= read -r completion_candidate; do - if [[ -z "${completion_seen[$completion_candidate]:-}" ]]; then - COMPREPLY+=("$completion_candidate") - completion_seen["$completion_candidate"]=1 - fi - done < <(compgen -f -- "$completion_current") - elif [[ $completion_directories == true ]]; then - while IFS= read -r completion_candidate; do - if [[ -z "${completion_seen[$completion_candidate]:-}" ]]; then - COMPREPLY+=("$completion_candidate") - completion_seen["$completion_candidate"]=1 - fi - done < <(compgen -d -- "$completion_current") - fi -} - -complete -F _cli_completions cli -BASHLY_COMPLETIONS -} - -# :command.completion_function -cli_completion() { - local completion_current="$1" - shift - - while [[ $# -gt 0 ]]; do - case "$1" in - completions) - shift - cli_completions_completion "$completion_current" "$@" - return - ;; - deploy) - shift - cli_deploy_completion "$completion_current" "$@" - return - ;; - --help | -h) - completion_blocked_flags["--help"]=1 - shift - ;; - --version | -v) - completion_blocked_flags["--version"]=1 - shift - ;; - *) - return - ;; - esac - done - completion_candidates "$completion_current" completions deploy - - if [[ $completion_current == -* ]]; then - completion_flag_candidates --help "$completion_current" --help -h - completion_flag_candidates --version "$completion_current" --version -v - fi - -} - -# :command.completion_function -cli_completions_completion() { - local completion_current="$1" - local completion_arg_index=0 - shift - - while [[ $# -gt 0 ]]; do - case "$1" in - --help | -h) - completion_blocked_flags["--help"]=1 - shift - ;; - *) - if [[ $completion_arg_index -ge 1 ]]; then - return - fi - completion_arg_index=$((completion_arg_index + 1)) - shift - - ;; - esac - done - - if [[ $completion_current == -* ]]; then - completion_flag_candidates --help "$completion_current" --help -h - fi - - if [[ $completion_current != -* ]]; then - case "$completion_arg_index" in - 0) - local -a completion_values=( - "bash" - ) - completion_candidates "$completion_current" "${completion_values[@]}" - - ;; - esac - fi - -} - -# :command.completion_function -cli_deploy_completion() { - local completion_current="$1" - local completion_arg_index=0 - shift - - while [[ $# -gt 0 ]]; do - case "$1" in - --help | -h) - completion_blocked_flags["--help"]=1 - shift - ;; - --config) - shift - if [[ $# -eq 0 ]]; then - completion_add_options files - - return - fi - shift - completion_blocked_flags["--config"]=1 - - ;; - --directory) - shift - if [[ $# -eq 0 ]]; then - completion_add_options directories - - return - fi - shift - completion_blocked_flags["--directory"]=1 - - ;; - *) - if [[ $completion_arg_index -ge 2 ]]; then - return - fi - completion_arg_index=$((completion_arg_index + 1)) - shift - - ;; - esac - done - - if [[ $completion_current == -* ]]; then - completion_flag_candidates --help "$completion_current" --help -h - completion_flag_candidates --config "$completion_current" --config - completion_flag_candidates --directory "$completion_current" --directory - fi - - if [[ $completion_current != -* ]]; then - case "$completion_arg_index" in - 0) - if completion_output="$({ git branch --format='%(refname:short)'; } 2>/dev/null)"; then - completion_dynamic_candidates "$completion_current" "$completion_output" - fi - - ;; - 1) - completion_candidates "$completion_current" staging production - if completion_output="$({ completion_environments; } 2>/dev/null)"; then - completion_dynamic_candidates "$completion_current" "$completion_output" - fi - completion_add_options no-space - - ;; - esac - fi - -} - -# :command.initialize -initialize() { - declare -g version="0.1.0" - set -e - -} - -# :command.run -run() { - # :command.globals - declare -g long_usage='' - declare -g -A args=() - declare -g -A deps=() - declare -g -a env_var_names=() - declare -g -a input=() - - normalize_input "$@" - parse_requirements "${input[@]}" - - case "$action" in - "completions") cli_completions_command ;; - "deploy") cli_deploy_command ;; - esac -} - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - # :command.start - command_line_args=("$@") - if [[ "${command_line_args[0]:-}" == "__complete" ]]; then - completion_run "${command_line_args[@]:1}" - else - initialize - run "${command_line_args[@]}" - fi - -fi