From 8edef1331687528316f9665ec8b16556ad33ed0b Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 30 Jun 2021 14:13:25 +0000 Subject: [PATCH 1/7] - Add support for extensible commands --- examples/catch_all_advanced/cli | 1 + examples/command-default/ftp | 1 + examples/command-groups/ftp | 1 + examples/commands-nested/cli | 3 ++ examples/commands/cli | 1 + examples/config-ini/configly | 1 + examples/dependencies/cli | 1 + examples/docker-like/docker | 3 ++ examples/environment-variables/cli | 1 + examples/git-like/git | 1 + examples/multiline/multi | 1 + lib/bashly/models/base.rb | 1 + lib/bashly/views/command/command_fallback.erb | 45 +++++++++++++++++++ lib/bashly/views/command/command_filter.erb | 20 +-------- 14 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 lib/bashly/views/command/command_fallback.erb diff --git a/examples/catch_all_advanced/cli b/examples/catch_all_advanced/cli index 351288dc..bd765083 100644 --- a/examples/catch_all_advanced/cli +++ b/examples/catch_all_advanced/cli @@ -221,6 +221,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) cli_usage exit 1 diff --git a/examples/command-default/ftp b/examples/command-default/ftp index 5c91dcdc..66ac3489 100644 --- a/examples/command-default/ftp +++ b/examples/command-default/ftp @@ -191,6 +191,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback "" ) ftp_usage exit 1 diff --git a/examples/command-groups/ftp b/examples/command-groups/ftp index e6cefbad..c18c8343 100644 --- a/examples/command-groups/ftp +++ b/examples/command-groups/ftp @@ -270,6 +270,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) ftp_usage exit 1 diff --git a/examples/commands-nested/cli b/examples/commands-nested/cli index 2f42c14f..1f84867b 100644 --- a/examples/commands-nested/cli +++ b/examples/commands-nested/cli @@ -340,6 +340,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) cli_usage exit 1 @@ -409,6 +410,7 @@ cli_dir_parse_requirements() { shift $# ;; + # :command.command_fallback * ) cli_dir_usage exit 1 @@ -596,6 +598,7 @@ cli_file_parse_requirements() { shift $# ;; + # :command.command_fallback * ) cli_file_usage exit 1 diff --git a/examples/commands/cli b/examples/commands/cli index 8025b596..1965aaa9 100644 --- a/examples/commands/cli +++ b/examples/commands/cli @@ -230,6 +230,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) cli_usage exit 1 diff --git a/examples/config-ini/configly b/examples/config-ini/configly index 36cb312c..3dafbddf 100644 --- a/examples/config-ini/configly +++ b/examples/config-ini/configly @@ -392,6 +392,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) configly_usage exit 1 diff --git a/examples/dependencies/cli b/examples/dependencies/cli index 25a54465..bb58df91 100644 --- a/examples/dependencies/cli +++ b/examples/dependencies/cli @@ -169,6 +169,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) cli_usage exit 1 diff --git a/examples/docker-like/docker b/examples/docker-like/docker index 889db90b..fcd019e8 100644 --- a/examples/docker-like/docker +++ b/examples/docker-like/docker @@ -288,6 +288,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) docker_usage exit 1 @@ -357,6 +358,7 @@ docker_container_parse_requirements() { shift $# ;; + # :command.command_fallback * ) docker_container_usage exit 1 @@ -531,6 +533,7 @@ docker_image_parse_requirements() { shift $# ;; + # :command.command_fallback * ) docker_image_usage exit 1 diff --git a/examples/environment-variables/cli b/examples/environment-variables/cli index ebf25139..a07d7f55 100644 --- a/examples/environment-variables/cli +++ b/examples/environment-variables/cli @@ -149,6 +149,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) cli_usage exit 1 diff --git a/examples/git-like/git b/examples/git-like/git index da610401..63b228b5 100644 --- a/examples/git-like/git +++ b/examples/git-like/git @@ -185,6 +185,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) git_usage exit 1 diff --git a/examples/multiline/multi b/examples/multiline/multi index 78e3c10a..81dcff2c 100644 --- a/examples/multiline/multi +++ b/examples/multiline/multi @@ -206,6 +206,7 @@ parse_requirements() { shift $# ;; + # :command.command_fallback * ) multi_usage exit 1 diff --git a/lib/bashly/models/base.rb b/lib/bashly/models/base.rb index 85fca657..49af98f2 100644 --- a/lib/bashly/models/base.rb +++ b/lib/bashly/models/base.rb @@ -14,6 +14,7 @@ class Base description environment_variables examples + extensible flags group help diff --git a/lib/bashly/views/command/command_fallback.erb b/lib/bashly/views/command/command_fallback.erb new file mode 100644 index 00000000..86dfe5ca --- /dev/null +++ b/lib/bashly/views/command/command_fallback.erb @@ -0,0 +1,45 @@ +# :command.command_fallback +<%- if default_command -%> +"" ) + <%= function_name %>_usage + exit 1 + ;; + +* ) + action="<%= default_command.name %>" + <%= default_command.function_name %>_parse_requirements "$@" + shift $# + ;; +<%- elsif extensible.is_a? String -%> +"" ) + <%= function_name %>_usage + exit 1 + ;; + +* ) + if [[ -x "$(command -v "<%= extensible %>")" ]]; then + exec <%= extensible %> "$@" + else + <%= function_name %>_usage + exit 1 + fi +<%- elsif extensible -%> +"" ) + <%= function_name %>_usage + exit 1 + ;; + +* ) + if [[ -x "$(command -v "<%= function_name %>-$action")" ]]; then + shift + exec "<%= function_name %>-$action" "$@" + else + <%= function_name %>_usage + exit 1 + fi +<%- else -%> +* ) + <%= function_name %>_usage + exit 1 + ;; +<%- end -%> diff --git a/lib/bashly/views/command/command_filter.erb b/lib/bashly/views/command/command_filter.erb index 0d464b36..02c67ec0 100644 --- a/lib/bashly/views/command/command_filter.erb +++ b/lib/bashly/views/command/command_filter.erb @@ -15,25 +15,7 @@ case $action in ;; <%- end -%> -<%- if default_command -%> -"" ) - <%= function_name %>_usage - exit 1 - ;; - -* ) - action="<%= default_command.name %>" - <%= default_command.function_name %>_parse_requirements "$@" - shift $# - ;; - -<%- else -%> -* ) - <%= function_name %>_usage - exit 1 - ;; - -<%- end -%> +<%= render :command_fallback %> esac <%- else -%> action="<%= action_name %>" From c8b5695447c39ae95a55428888a781d9f6ea1824 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 30 Jun 2021 14:51:35 +0000 Subject: [PATCH 2/7] add extensible examples --- examples/extensible-delegate/README.md | 7 + examples/extensible-delegate/mygit | 348 ++++++++++++++++ examples/extensible-delegate/src/bashly.yml | 13 + .../extensible-delegate/src/initialize.sh | 6 + .../extensible-delegate/src/pull_command.sh | 4 + .../extensible-delegate/src/push_command.sh | 4 + examples/extensible-delegate/test.sh | 13 + examples/extensible/README.md | 7 + examples/extensible/cli | 389 ++++++++++++++++++ examples/extensible/cli-status | 7 + examples/extensible/src/bashly.yml | 23 ++ examples/extensible/src/download_command.sh | 4 + examples/extensible/src/initialize.sh | 6 + examples/extensible/src/upload_command.sh | 4 + examples/extensible/test.sh | 13 + spec/approvals/examples/extensible | 26 ++ spec/approvals/examples/extensible-delegate | 34 ++ 17 files changed, 908 insertions(+) create mode 100644 examples/extensible-delegate/README.md create mode 100644 examples/extensible-delegate/mygit create mode 100644 examples/extensible-delegate/src/bashly.yml create mode 100644 examples/extensible-delegate/src/initialize.sh create mode 100644 examples/extensible-delegate/src/pull_command.sh create mode 100644 examples/extensible-delegate/src/push_command.sh create mode 100644 examples/extensible-delegate/test.sh create mode 100644 examples/extensible/README.md create mode 100644 examples/extensible/cli create mode 100644 examples/extensible/cli-status create mode 100644 examples/extensible/src/bashly.yml create mode 100644 examples/extensible/src/download_command.sh create mode 100644 examples/extensible/src/initialize.sh create mode 100644 examples/extensible/src/upload_command.sh create mode 100644 examples/extensible/test.sh create mode 100644 spec/approvals/examples/extensible create mode 100644 spec/approvals/examples/extensible-delegate diff --git a/examples/extensible-delegate/README.md b/examples/extensible-delegate/README.md new file mode 100644 index 00000000..49e6081b --- /dev/null +++ b/examples/extensible-delegate/README.md @@ -0,0 +1,7 @@ +Extensible Delegate Command Example +================================================== + +This example was generated with: + + $ bashly init + $ bashly generate diff --git a/examples/extensible-delegate/mygit b/examples/extensible-delegate/mygit new file mode 100644 index 00000000..751c943e --- /dev/null +++ b/examples/extensible-delegate/mygit @@ -0,0 +1,348 @@ +#!/usr/bin/env bash +# This script was generated by bashly (https://github.com/DannyBen/bashly) +# Modifying it manually is not recommended + +# :command.version_command +version_command() { + echo "$version" +} + +# :command.usage +mygit_usage() { + if [[ -n $long_usage ]]; then + printf "mygit - Sample application that delegates unknown commands to a different executable\n" + echo + else + printf "mygit - Sample application that delegates unknown commands to a different executable\n" + echo + fi + + printf "Usage:\n" + printf " mygit [command]\n" + printf " mygit [command] --help | -h\n" + printf " mygit --version | -v\n" + echo + # :command.usage_commands + printf "Commands:\n" + echo " push Push to my repository" + echo " pull Pull from my repository" + echo + + if [[ -n $long_usage ]]; then + printf "Options:\n" + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + echo " --version, -v" + printf " Show version number\n" + echo + + fi +} + +# :command.usage +mygit_push_usage() { + if [[ -n $long_usage ]]; then + printf "mygit push - Push to my repository\n" + echo + else + printf "mygit push - Push to my repository\n" + echo + fi + + printf "Shortcut: p\n" + echo + + printf "Usage:\n" + printf " mygit push\n" + printf " mygit push --help | -h\n" + echo + + if [[ -n $long_usage ]]; then + printf "Options:\n" + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + + fi +} + +# :command.usage +mygit_pull_usage() { + if [[ -n $long_usage ]]; then + printf "mygit pull - Pull from my repository\n" + echo + else + printf "mygit pull - Pull from my repository\n" + echo + fi + + printf "Shortcut: l\n" + echo + + printf "Usage:\n" + printf " mygit pull\n" + printf " mygit pull --help | -h\n" + echo + + if [[ -n $long_usage ]]; then + printf "Options:\n" + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + + fi +} + +# :command.inspect_args +inspect_args() { + readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort) + if (( ${#args[@]} )); then + echo args: + for k in "${sorted_keys[@]}"; do echo "- \${args[$k]} = ${args[$k]}"; done + else + echo args: none + fi + + if (( ${#other_args[@]} )); then + echo + echo other_args: + echo "- \${other_args[*]} = ${other_args[*]}" + for i in "${!other_args[@]}"; do + echo "- \${other_args[$i]} = ${other_args[$i]}" + done + fi +} + +# :command.command_functions +# :command.function +mygit_push_command() { + # :src/push_command.sh + echo "# this file is located in 'src/push_command.sh'" + echo "# code for 'mygit push' goes here" + echo "# you can edit it freely and regenerate (it will not be overwritten)" + inspect_args +} + +# :command.function +mygit_pull_command() { + # :src/pull_command.sh + echo "# this file is located in 'src/pull_command.sh'" + echo "# code for 'mygit pull' goes here" + echo "# you can edit it freely and regenerate (it will not be overwritten)" + inspect_args +} + +# :command.parse_requirements +parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version | -v ) + version_command + exit + ;; + + --help | -h ) + long_usage=yes + mygit_usage + exit 1 + ;; + + esac + # :command.environment_variables_filter + # :command.dependencies_filter + # :command.command_filter + action=$1 + + case $action in + -* ) + ;; + + push | p ) + action="push" + shift + mygit_push_parse_requirements "$@" + shift $# + ;; + + pull | l ) + action="pull" + shift + mygit_pull_parse_requirements "$@" + shift $# + ;; + + # :command.command_fallback + "" ) + mygit_usage + exit 1 + ;; + + * ) + if [[ -x "$(command -v "git")" ]]; then + exec git "$@" + else + mygit_usage + exit 1 + fi + + esac + # :command.required_args_filter + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + printf "invalid argument: %s\n" "$key" + exit 1 + ;; + + esac + done + # :command.default_assignments + # :command.whitelist_filter +} + +# :command.parse_requirements +mygit_push_parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version | -v ) + version_command + exit + ;; + + --help | -h ) + long_usage=yes + mygit_push_usage + exit 1 + ;; + + esac + # :command.environment_variables_filter + # :command.dependencies_filter + # :command.command_filter + action="push" + # :command.required_args_filter + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + printf "invalid argument: %s\n" "$key" + exit 1 + ;; + + esac + done + # :command.default_assignments + # :command.whitelist_filter +} + +# :command.parse_requirements +mygit_pull_parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version | -v ) + version_command + exit + ;; + + --help | -h ) + long_usage=yes + mygit_pull_usage + exit 1 + ;; + + esac + # :command.environment_variables_filter + # :command.dependencies_filter + # :command.command_filter + action="pull" + # :command.required_args_filter + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + printf "invalid argument: %s\n" "$key" + exit 1 + ;; + + esac + done + # :command.default_assignments + # :command.whitelist_filter +} + +# :command.initialize +initialize() { + version="0.1.0" + long_usage='' + set -e + + # :src/initialize.sh + # Code here runs inside the initialize() function + # Use it for anything that you need to run before any other function, like + # setting environment vairables: + # CONFIG_FILE=settings.ini + # + # Feel free to empty (but not delete) this file. +} + +# :command.run +run() { + declare -A args + declare -a other_args + parse_requirements "$@" + + if [[ $action == "push" ]]; then + if [[ ${args[--help]} ]]; then + long_usage=yes + mygit_push_usage + else + mygit_push_command + fi + + elif [[ $action == "pull" ]]; then + if [[ ${args[--help]} ]]; then + long_usage=yes + mygit_pull_usage + else + mygit_pull_command + fi + + elif [[ $action == "root" ]]; then + root_command + fi +} + +initialize +run "$@" diff --git a/examples/extensible-delegate/src/bashly.yml b/examples/extensible-delegate/src/bashly.yml new file mode 100644 index 00000000..9b3b6bdc --- /dev/null +++ b/examples/extensible-delegate/src/bashly.yml @@ -0,0 +1,13 @@ +name: mygit +help: Sample application that delegates unknown commands to a different executable +version: 0.1.0 +extensible: git + +commands: +- name: push + short: p + help: Push to my repository + +- name: pull + short: l + help: Pull from my repository diff --git a/examples/extensible-delegate/src/initialize.sh b/examples/extensible-delegate/src/initialize.sh new file mode 100644 index 00000000..f2dbc52c --- /dev/null +++ b/examples/extensible-delegate/src/initialize.sh @@ -0,0 +1,6 @@ +# Code here runs inside the initialize() function +# Use it for anything that you need to run before any other function, like +# setting environment vairables: +# CONFIG_FILE=settings.ini +# +# Feel free to empty (but not delete) this file. diff --git a/examples/extensible-delegate/src/pull_command.sh b/examples/extensible-delegate/src/pull_command.sh new file mode 100644 index 00000000..8a5f0834 --- /dev/null +++ b/examples/extensible-delegate/src/pull_command.sh @@ -0,0 +1,4 @@ +echo "# this file is located in 'src/pull_command.sh'" +echo "# code for 'mygit pull' goes here" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/extensible-delegate/src/push_command.sh b/examples/extensible-delegate/src/push_command.sh new file mode 100644 index 00000000..e9353497 --- /dev/null +++ b/examples/extensible-delegate/src/push_command.sh @@ -0,0 +1,4 @@ +echo "# this file is located in 'src/push_command.sh'" +echo "# code for 'mygit push' goes here" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/extensible-delegate/test.sh b/examples/extensible-delegate/test.sh new file mode 100644 index 00000000..fc2410d1 --- /dev/null +++ b/examples/extensible-delegate/test.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +rm -f ./src/*.sh + +set -x + +bashly generate + +./mygit +./mygit push + +# any unknown command will go to the git executable +./mygit rm diff --git a/examples/extensible/README.md b/examples/extensible/README.md new file mode 100644 index 00000000..8f148185 --- /dev/null +++ b/examples/extensible/README.md @@ -0,0 +1,7 @@ +Extensible Command Example +================================================== + +This example was generated with: + + $ bashly init + $ bashly generate diff --git a/examples/extensible/cli b/examples/extensible/cli new file mode 100644 index 00000000..77a68efe --- /dev/null +++ b/examples/extensible/cli @@ -0,0 +1,389 @@ +#!/usr/bin/env bash +# This script was generated by bashly (https://github.com/DannyBen/bashly) +# Modifying it manually is not recommended + +# :command.version_command +version_command() { + echo "$version" +} + +# :command.usage +cli_usage() { + if [[ -n $long_usage ]]; then + printf "cli - Sample application that can be externally extended\n" + echo + else + printf "cli - Sample application that can be externally extended\n" + echo + fi + + printf "Usage:\n" + printf " cli [command]\n" + printf " cli [command] --help | -h\n" + printf " cli --version | -v\n" + echo + # :command.usage_commands + printf "Commands:\n" + echo " upload Upload a file" + echo " download Download a file" + echo + + if [[ -n $long_usage ]]; then + printf "Options:\n" + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + echo " --version, -v" + printf " Show version number\n" + echo + + fi +} + +# :command.usage +cli_upload_usage() { + if [[ -n $long_usage ]]; then + printf "cli upload - Upload a file\n" + echo + else + printf "cli upload - Upload a file\n" + echo + fi + + printf "Shortcut: u\n" + echo + + printf "Usage:\n" + printf " cli upload SOURCE\n" + printf " cli upload --help | -h\n" + echo + + if [[ -n $long_usage ]]; then + printf "Options:\n" + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + + # :command.usage_args + printf "Arguments:\n" + + # :argument.usage + echo " SOURCE" + printf " File to upload\n" + echo + + fi +} + +# :command.usage +cli_download_usage() { + if [[ -n $long_usage ]]; then + printf "cli download - Download a file\n" + echo + else + printf "cli download - Download a file\n" + echo + fi + + printf "Shortcut: d\n" + echo + + printf "Usage:\n" + printf " cli download SOURCE\n" + printf " cli download --help | -h\n" + echo + + if [[ -n $long_usage ]]; then + printf "Options:\n" + # :command.usage_fixed_flags + echo " --help, -h" + printf " Show this help\n" + echo + + # :command.usage_args + printf "Arguments:\n" + + # :argument.usage + echo " SOURCE" + printf " File to download\n" + echo + + fi +} + +# :command.inspect_args +inspect_args() { + readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort) + if (( ${#args[@]} )); then + echo args: + for k in "${sorted_keys[@]}"; do echo "- \${args[$k]} = ${args[$k]}"; done + else + echo args: none + fi + + if (( ${#other_args[@]} )); then + echo + echo other_args: + echo "- \${other_args[*]} = ${other_args[*]}" + for i in "${!other_args[@]}"; do + echo "- \${other_args[$i]} = ${other_args[$i]}" + done + fi +} + +# :command.command_functions +# :command.function +cli_upload_command() { + # :src/upload_command.sh + echo "# this file is located in 'src/upload_command.sh'" + echo "# code for 'cli upload' goes here" + echo "# you can edit it freely and regenerate (it will not be overwritten)" + inspect_args +} + +# :command.function +cli_download_command() { + # :src/download_command.sh + echo "# this file is located in 'src/download_command.sh'" + echo "# code for 'cli download' goes here" + echo "# you can edit it freely and regenerate (it will not be overwritten)" + inspect_args +} + +# :command.parse_requirements +parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version | -v ) + version_command + exit + ;; + + --help | -h ) + long_usage=yes + cli_usage + exit 1 + ;; + + esac + # :command.environment_variables_filter + # :command.dependencies_filter + # :command.command_filter + action=$1 + + case $action in + -* ) + ;; + + upload | u ) + action="upload" + shift + cli_upload_parse_requirements "$@" + shift $# + ;; + + download | d ) + action="download" + shift + cli_download_parse_requirements "$@" + shift $# + ;; + + # :command.command_fallback + "" ) + cli_usage + exit 1 + ;; + + * ) + if [[ -x "$(command -v "cli-$action")" ]]; then + shift + exec "cli-$action" "$@" + else + cli_usage + exit 1 + fi + + esac + # :command.required_args_filter + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + printf "invalid argument: %s\n" "$key" + exit 1 + ;; + + esac + done + # :command.default_assignments + # :command.whitelist_filter +} + +# :command.parse_requirements +cli_upload_parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version | -v ) + version_command + exit + ;; + + --help | -h ) + long_usage=yes + cli_upload_usage + exit 1 + ;; + + esac + # :command.environment_variables_filter + # :command.dependencies_filter + # :command.command_filter + action="upload" + # :command.required_args_filter + if [[ $1 && $1 != -* ]]; then + args[source]=$1 + shift + else + printf "missing required argument: SOURCE\nusage: cli upload SOURCE\n" + exit 1 + fi + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + if [[ ! ${args[source]} ]]; then + args[source]=$1 + shift + else + printf "invalid argument: %s\n" "$key" + exit 1 + fi + ;; + + esac + done + # :command.default_assignments + # :command.whitelist_filter +} + +# :command.parse_requirements +cli_download_parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version | -v ) + version_command + exit + ;; + + --help | -h ) + long_usage=yes + cli_download_usage + exit 1 + ;; + + esac + # :command.environment_variables_filter + # :command.dependencies_filter + # :command.command_filter + action="download" + # :command.required_args_filter + if [[ $1 && $1 != -* ]]; then + args[source]=$1 + shift + else + printf "missing required argument: SOURCE\nusage: cli download SOURCE\n" + exit 1 + fi + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + printf "invalid option: %s\n" "$key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + if [[ ! ${args[source]} ]]; then + args[source]=$1 + shift + else + printf "invalid argument: %s\n" "$key" + exit 1 + fi + ;; + + esac + done + # :command.default_assignments + # :command.whitelist_filter +} + +# :command.initialize +initialize() { + version="0.1.0" + long_usage='' + set -e + + # :src/initialize.sh + # Code here runs inside the initialize() function + # Use it for anything that you need to run before any other function, like + # setting environment vairables: + # CONFIG_FILE=settings.ini + # + # Feel free to empty (but not delete) this file. +} + +# :command.run +run() { + declare -A args + declare -a other_args + parse_requirements "$@" + + if [[ $action == "upload" ]]; then + if [[ ${args[--help]} ]]; then + long_usage=yes + cli_upload_usage + else + cli_upload_command + fi + + elif [[ $action == "download" ]]; then + if [[ ${args[--help]} ]]; then + long_usage=yes + cli_download_usage + else + cli_download_command + fi + + elif [[ $action == "root" ]]; then + root_command + fi +} + +initialize +run "$@" diff --git a/examples/extensible/cli-status b/examples/extensible/cli-status new file mode 100644 index 00000000..dd97a060 --- /dev/null +++ b/examples/extensible/cli-status @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +echo "This is an external executable that serves as an extension to" +echo "the cli app." +echo "If placed in the path, it will be executed whenever someone runs:" +echo "$ cli status " +echo "" +echo "Received args: $@" \ No newline at end of file diff --git a/examples/extensible/src/bashly.yml b/examples/extensible/src/bashly.yml new file mode 100644 index 00000000..a2c7bffd --- /dev/null +++ b/examples/extensible/src/bashly.yml @@ -0,0 +1,23 @@ +name: cli +help: Sample application that can be externally extended +version: 0.1.0 +extensible: true + +commands: +- name: upload + short: u + help: Upload a file + args: + - name: source + required: true + help: File to upload + +- name: download + short: d + help: Download a file + + args: + - name: source + required: true + help: File to download + diff --git a/examples/extensible/src/download_command.sh b/examples/extensible/src/download_command.sh new file mode 100644 index 00000000..950ed370 --- /dev/null +++ b/examples/extensible/src/download_command.sh @@ -0,0 +1,4 @@ +echo "# this file is located in 'src/download_command.sh'" +echo "# code for 'cli download' goes here" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/extensible/src/initialize.sh b/examples/extensible/src/initialize.sh new file mode 100644 index 00000000..f2dbc52c --- /dev/null +++ b/examples/extensible/src/initialize.sh @@ -0,0 +1,6 @@ +# Code here runs inside the initialize() function +# Use it for anything that you need to run before any other function, like +# setting environment vairables: +# CONFIG_FILE=settings.ini +# +# Feel free to empty (but not delete) this file. diff --git a/examples/extensible/src/upload_command.sh b/examples/extensible/src/upload_command.sh new file mode 100644 index 00000000..755e02ff --- /dev/null +++ b/examples/extensible/src/upload_command.sh @@ -0,0 +1,4 @@ +echo "# this file is located in 'src/upload_command.sh'" +echo "# code for 'cli upload' goes here" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/extensible/test.sh b/examples/extensible/test.sh new file mode 100644 index 00000000..1c14a009 --- /dev/null +++ b/examples/extensible/test.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +rm -f ./src/*.sh + +# Fool the terminal to think our cli-status file is in the path +export PATH="$PWD:$PATH" + +set -x + +bashly generate + +./cli +./cli status --some --flags diff --git a/spec/approvals/examples/extensible b/spec/approvals/examples/extensible new file mode 100644 index 00000000..3de3ad75 --- /dev/null +++ b/spec/approvals/examples/extensible @@ -0,0 +1,26 @@ ++ bashly generate +creating user files in src +created src/initialize.sh +created src/upload_command.sh +created src/download_command.sh +created ./cli +run ./cli --help to test your bash script ++ ./cli +cli - Sample application that can be externally extended + +Usage: + cli [command] + cli [command] --help | -h + cli --version | -v + +Commands: + upload Upload a file + download Download a file + ++ ./cli status --some --flags +This is an external executable that serves as an extension to +the cli app. +If placed in the path, it will be executed whenever someone runs: +$ cli status + +Received args: --some --flags diff --git a/spec/approvals/examples/extensible-delegate b/spec/approvals/examples/extensible-delegate new file mode 100644 index 00000000..d58ed0d4 --- /dev/null +++ b/spec/approvals/examples/extensible-delegate @@ -0,0 +1,34 @@ ++ bashly generate +creating user files in src +created src/initialize.sh +created src/push_command.sh +created src/pull_command.sh +created ./mygit +run ./mygit --help to test your bash script ++ ./mygit +mygit - Sample application that delegates unknown commands to a different executable + +Usage: + mygit [command] + mygit [command] --help | -h + mygit --version | -v + +Commands: + push Push to my repository + pull Pull from my repository + ++ ./mygit push +# this file is located in 'src/push_command.sh' +# code for 'mygit push' goes here +# you can edit it freely and regenerate (it will not be overwritten) +args: none ++ ./mygit rm +usage: git rm [] [--] ... + + -n, --dry-run dry run + -q, --quiet do not list removed files + --cached only remove from the index + -f, --force override the up-to-date check + -r allow recursive removal + --ignore-unmatch exit with a zero status even if nothing matched + From 18fba2cdbf2c73594cf8ebe8b484597320dabdcf Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 30 Jun 2021 14:55:03 +0000 Subject: [PATCH 3/7] fix spec --- examples/extensible-delegate/test.sh | 2 +- spec/approvals/examples/extensible-delegate | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/examples/extensible-delegate/test.sh b/examples/extensible-delegate/test.sh index fc2410d1..6037907e 100644 --- a/examples/extensible-delegate/test.sh +++ b/examples/extensible-delegate/test.sh @@ -10,4 +10,4 @@ bashly generate ./mygit push # any unknown command will go to the git executable -./mygit rm +./mygit rm some-file-that-doesnt-exist diff --git a/spec/approvals/examples/extensible-delegate b/spec/approvals/examples/extensible-delegate index d58ed0d4..e8205d90 100644 --- a/spec/approvals/examples/extensible-delegate +++ b/spec/approvals/examples/extensible-delegate @@ -22,13 +22,5 @@ Commands: # code for 'mygit push' goes here # you can edit it freely and regenerate (it will not be overwritten) args: none -+ ./mygit rm -usage: git rm [] [--] ... - - -n, --dry-run dry run - -q, --quiet do not list removed files - --cached only remove from the index - -f, --force override the up-to-date check - -r allow recursive removal - --ignore-unmatch exit with a zero status even if nothing matched - ++ ./mygit rm some-file-that-doesnt-exist +fatal: pathspec 'some-file-that-doesnt-exist' did not match any files From 8927885847316efbd418d8963b25580071ce2aad Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 30 Jun 2021 14:57:42 +0000 Subject: [PATCH 4/7] chmod spec executable --- examples/extensible/cli-status | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 examples/extensible/cli-status diff --git a/examples/extensible/cli-status b/examples/extensible/cli-status old mode 100644 new mode 100755 From b6733963471930fea6ba5db153b8c868930323e1 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 30 Jun 2021 15:20:39 +0000 Subject: [PATCH 5/7] readme update --- README.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/README.md b/README.md index 46a79ea1..f1f08baa 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Create beautiful bash scripts from simple YAML configuration - [Argument options](#argument-options) - [Flag options](#flag-options) - [Environment Variable options](#environment-variable-options) +- [Extensible Commands](#extensible-commands) - [Real World Examples](#real-world-examples) - [Contributing / Support](#contributing--support) @@ -203,6 +204,7 @@ command and subcommands (under the `commands` definition). `help` | The header text to display when using `--help`. This option can have multiple lines. In this case, the first line will be used as summary wherever appropriate. `version` | The string to display when using `--version`. *Applicable only in the main command*. `default` | Setting this to `true` on any command, will cause any unrecognized command line to be passed to this command. *Applicable only in subcommands*. +`extensible` | Specify that this command can be [externally extended](#extensible-commands). `examples` | Specify an array of examples to show when using `--help`. Each example can have multiple lines. `environment_variables` | Specify an array of [environment variables](#environment-variable-options) needed by your script. `commands` | Specify the array of [commands](#command-options). Each command will have its own args and flags. Note: if `commands` is provided, you cannot specify flags or args at the same level. @@ -262,6 +264,82 @@ set. `required` | Specify if this variable is required. +## Extensible Commands + +You may configure your generated bash script to delegate any unknown command +to an external executable, by setting the `extensible` option to either `true`, +or to a different external command. + +This is simiilar to how `git` works. When you execute `git whatever`, the `git` +command will look for a file named `git-whatever` in the path, and execute it. + +Note that this option cannot be specified together with the `default` option, +since both specify a handler for unknown commands. + +Bashly supports two operation modes. + +### Extension Mode (`extensible: true`) + +By setting `extensible` to `true`, a specially named executable will be called +when an unknown command is called by the user. + +Given this `bashly.yml` configuration: + +```yaml +name: myscript +help: Example +version: 0.1.0 +extensible: true + +commands: +- name: upload + help: Upload a file +``` + +And this user command: + +``` +$ myscript something + +``` + +The generated script will look for an executable named `myscript-something` +in the path. If found, it will be called. + +See the [extensible example](examples/extensible) + + +### Delegate Mode (`extensible: `) + +By setting `extensible` to any string, unknown command calls by the user will +be delegated to the executable with that name. + +Given this `bashly.yml` configuration: + +```yaml +name: mygit +help: Example +version: 0.1.0 +extensible: git + +commands: +- name: push + help: Push to my repository +``` + +And this user command: + +``` +$ mygit status + +``` + +The generated script will execute `git status`. + +See the [extensible-delegate example](examples/extensible-delegate) + + + ## Real World Examples - [Rush][rush] - a Personal Package Manager From 6eb6538b21353069c83784d0a5768adf55852991 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 30 Jun 2021 15:30:45 +0000 Subject: [PATCH 6/7] shellcheck all examples --- Runfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Runfile b/Runfile index 7bd4df13..c1eac525 100644 --- a/Runfile +++ b/Runfile @@ -33,21 +33,30 @@ end def examples [ + "examples/catch_all/download", + "examples/catch_all_advanced/cli", "examples/colors/colorly", "examples/command-default/ftp", "examples/command-groups/ftp", + "examples/commands-nested/cli", "examples/commands/cli", "examples/config-ini/configly", "examples/custom-includes/download", "examples/custom-strings/download", + "examples/default-values/convert", "examples/dependencies/cli", "examples/docker-like/docker", "examples/environment-variables/cli", + "examples/extensible-delegate/mygit", + "examples/extensible/cli", "examples/git-like/git", "examples/minimal/download", "examples/minus-v/cli", "examples/multiline/multi", + "examples/whitelist/login", "examples/yaml/yaml", + "spec/fixtures/workspaces/catch-all-no-args/download", + "spec/fixtures/workspaces/flag-args-with-dash/argflag", "spec/fixtures/workspaces/short-command-code/rush", ] end From a049a6449fede86013eb00c3546ed815bb4773e5 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Wed, 30 Jun 2021 15:34:22 +0000 Subject: [PATCH 7/7] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1f08baa..ad9adb2c 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ You may configure your generated bash script to delegate any unknown command to an external executable, by setting the `extensible` option to either `true`, or to a different external command. -This is simiilar to how `git` works. When you execute `git whatever`, the `git` +This is similar to how `git` works. When you execute `git whatever`, the `git` command will look for a file named `git-whatever` in the path, and execute it. Note that this option cannot be specified together with the `default` option,