From 08e8033589b1f2fdb46141c9c9695114a7c5f084 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Thu, 28 Nov 2019 22:55:03 +0000 Subject: [PATCH 1/2] add support for default command --- examples/default-command/README.md | 7 + examples/default-command/ftp | 360 ++++++++++++++++++ examples/default-command/src/bashly.yml | 23 ++ .../default-command/src/download_command.sh | 4 + examples/default-command/src/initialize.sh | 6 + .../default-command/src/upload_command.sh | 4 + examples/default-command/test.sh | 13 + lib/bashly/models/base.rb | 1 + lib/bashly/models/command.rb | 6 + lib/bashly/views/command/command_filter.erb | 14 + lib/bashly/views/command/usage_commands.erb | 4 +- spec/approvals/examples/default-command | 56 +++ spec/bashly/models/command_spec.rb | 23 ++ spec/fixtures/models/commands.yml | 16 +- 14 files changed, 535 insertions(+), 2 deletions(-) create mode 100644 examples/default-command/README.md create mode 100644 examples/default-command/ftp create mode 100644 examples/default-command/src/bashly.yml create mode 100644 examples/default-command/src/download_command.sh create mode 100644 examples/default-command/src/initialize.sh create mode 100644 examples/default-command/src/upload_command.sh create mode 100644 examples/default-command/test.sh create mode 100644 spec/approvals/examples/default-command diff --git a/examples/default-command/README.md b/examples/default-command/README.md new file mode 100644 index 00000000..6ad617ce --- /dev/null +++ b/examples/default-command/README.md @@ -0,0 +1,7 @@ +Default Command Example +================================================== + +This example was generated with: + + $ bashly init + $ bashly generate diff --git a/examples/default-command/ftp b/examples/default-command/ftp new file mode 100644 index 00000000..fd52fe6b --- /dev/null +++ b/examples/default-command/ftp @@ -0,0 +1,360 @@ +#!/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 +ftp_usage() { + if [[ -n $long_usage ]]; then + echo -e "ftp - Sample application that uses the default command option" + echo + else + echo -e "ftp - Sample application that uses the default command option" + echo + fi + echo -e "Usage:" + echo -e " ftp [command] [options]" + echo -e " ftp [command] --help | -h" + echo -e " ftp --version" + echo + # :command.usage_commands + echo -e "Commands:" + echo " upload Upload a file (default)" + echo " download Download a file" + echo + + if [[ -n $long_usage ]]; then + echo -e "Options:" + # :command.usage_fixed_flags + echo " --help, -h" + echo -e " Show this help" + echo + echo " --version" + echo -e " Show version number" + echo + + fi +} + +# :command.usage +ftp_upload_usage() { + if [[ -n $long_usage ]]; then + echo -e "ftp upload - Upload a file" + echo + else + echo -e "ftp upload - Upload a file" + echo + fi + echo -e "Usage:" + echo -e " ftp upload SOURCE [options]" + echo -e " ftp upload --help | -h" + echo + + if [[ -n $long_usage ]]; then + echo -e "Options:" + # :command.usage_fixed_flags + echo " --help, -h" + echo -e " Show this help" + echo + + # :command.usage_args + echo -e "Arguments:" + + # :argument.usage + echo " SOURCE" + echo -e " File to upload" + echo + + fi +} + +# :command.usage +ftp_download_usage() { + if [[ -n $long_usage ]]; then + echo -e "ftp download - Download a file" + echo + else + echo -e "ftp download - Download a file" + echo + fi + echo -e "Usage:" + echo -e " ftp download SOURCE [options]" + echo -e " ftp download --help | -h" + echo + + if [[ -n $long_usage ]]; then + echo -e "Options:" + # :command.usage_fixed_flags + echo " --help, -h" + echo -e " Show this help" + echo + + # :command.usage_args + echo -e "Arguments:" + + # :argument.usage + echo " SOURCE" + echo -e " File to download" + echo + + fi +} + +# :command.inspect_args +inspect_args() { + echo args: + for k in "${!args[@]}"; do echo "- \${args[$k]} = ${args[$k]}"; done +} + +# :command.command_functions +# :command.function +ftp_upload_command() { + # :src/upload_command.sh + echo "# this file is located in 'src/upload_command.sh'" + echo "# code for 'ftp upload' goes here" + echo "# you can edit it freely and regenerate (it will not be overwritten)" + inspect_args +} + +# :command.function +ftp_download_command() { + # :src/download_command.sh + echo "# this file is located in 'src/download_command.sh'" + echo "# code for 'ftp 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 ) + version_command + exit 1 + ;; + + --help | -h ) + long_usage=yes + ftp_usage + exit 1 + ;; + + esac + # :command.environment_variables_filter + # :command.dependencies_filter + # :command.command_filter + action=$1 + + case $action in + -* ) + ;; + + upload | u ) + action="upload" + shift + ftp_upload_parse_requirements "$@" + shift $# + ;; + + download | d ) + action="download" + shift + ftp_download_parse_requirements "$@" + shift $# + ;; + + "" ) + ftp_usage + exit 1 + ;; + + * ) + action="upload" + ftp_upload_parse_requirements "$@" + shift $# + ;; + + esac + # :command.required_args_filter + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + echo -e "invalid option: $key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + echo -e "invalid argument: $key" + exit 1 + ;; + + esac + done +} + +# :command.parse_requirements +ftp_upload_parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version ) + version_command + exit 1 + ;; + + --help | -h ) + long_usage=yes + ftp_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 + echo -e "missing required argument: SOURCE\nusage: ftp upload SOURCE [options]" + exit 1 + fi + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + echo -e "invalid option: $key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + if [[ ! ${args[source]} ]]; then + args[source]=$1 + shift + else + echo -e "invalid argument: $key" + exit 1 + fi + ;; + + esac + done +} + +# :command.parse_requirements +ftp_download_parse_requirements() { + # :command.fixed_flag_filter + case "$1" in + --version ) + version_command + exit 1 + ;; + + --help | -h ) + long_usage=yes + ftp_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 + echo -e "missing required argument: SOURCE\nusage: ftp download SOURCE [options]" + exit 1 + fi + # :command.required_flags_filter + # :command.parse_requirements_while + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -* ) + echo -e "invalid option: $key" + exit 1 + ;; + + * ) + # :command.parse_requirements_case + if [[ ! ${args[source]} ]]; then + args[source]=$1 + shift + else + echo -e "invalid argument: $key" + exit 1 + fi + ;; + + esac + done +} + +# :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 + parse_requirements "$@" + + if [[ $action == "upload" ]]; then + if [[ ${args[--help]} ]]; then + long_usage=yes + ftp_upload_usage + else + ftp_upload_command + fi + + elif [[ $action == "download" ]]; then + if [[ ${args[--help]} ]]; then + long_usage=yes + ftp_download_usage + else + ftp_download_command + fi + + elif [[ ${args[--version]} ]]; then + version_command + elif [[ ${args[--help]} ]]; then + long_usage=yes + ftp_usage + elif [[ $action == "root" ]]; then + root_command + fi +} + +initialize +run "$@" diff --git a/examples/default-command/src/bashly.yml b/examples/default-command/src/bashly.yml new file mode 100644 index 00000000..407083aa --- /dev/null +++ b/examples/default-command/src/bashly.yml @@ -0,0 +1,23 @@ +name: ftp +help: Sample application that uses the default command option +version: 0.1.0 + +commands: +- name: upload + short: u + help: Upload a file + default: true + 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/default-command/src/download_command.sh b/examples/default-command/src/download_command.sh new file mode 100644 index 00000000..d6f4bfd9 --- /dev/null +++ b/examples/default-command/src/download_command.sh @@ -0,0 +1,4 @@ +echo "# this file is located in 'src/download_command.sh'" +echo "# code for 'ftp download' goes here" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/default-command/src/initialize.sh b/examples/default-command/src/initialize.sh new file mode 100644 index 00000000..f2dbc52c --- /dev/null +++ b/examples/default-command/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/default-command/src/upload_command.sh b/examples/default-command/src/upload_command.sh new file mode 100644 index 00000000..f6058c2f --- /dev/null +++ b/examples/default-command/src/upload_command.sh @@ -0,0 +1,4 @@ +echo "# this file is located in 'src/upload_command.sh'" +echo "# code for 'ftp upload' goes here" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/default-command/test.sh b/examples/default-command/test.sh new file mode 100644 index 00000000..48e84574 --- /dev/null +++ b/examples/default-command/test.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +rm -f ./src/*.sh + +set -x + +bashly generate + +./ftp +./ftp -h +./ftp download something +./ftp upload something +./ftp something diff --git a/lib/bashly/models/base.rb b/lib/bashly/models/base.rb index 96f57e27..8e7390e6 100644 --- a/lib/bashly/models/base.rb +++ b/lib/bashly/models/base.rb @@ -9,6 +9,7 @@ class Base arg dependencies description + default environment_variables examples flags diff --git a/lib/bashly/models/command.rb b/lib/bashly/models/command.rb index c5d6c740..9ef4fa81 100644 --- a/lib/bashly/models/command.rb +++ b/lib/bashly/models/command.rb @@ -55,6 +55,12 @@ def deep_commands result end + # If any of this command's subcommands has the default option set to + # true, this default command will be returned, nil otherwise. + def default_command + commands.find { |c| c.default } + end + # Returns an array of EnvironmentVariables def environment_variables return [] unless options["environment_variables"] diff --git a/lib/bashly/views/command/command_filter.erb b/lib/bashly/views/command/command_filter.erb index 45dcc4fe..0d464b36 100644 --- a/lib/bashly/views/command/command_filter.erb +++ b/lib/bashly/views/command/command_filter.erb @@ -15,11 +15,25 @@ 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 -%> esac <%- else -%> action="<%= action_name %>" diff --git a/lib/bashly/views/command/usage_commands.erb b/lib/bashly/views/command/usage_commands.erb index 13cb8fa4..c3c908cb 100644 --- a/lib/bashly/views/command/usage_commands.erb +++ b/lib/bashly/views/command/usage_commands.erb @@ -2,6 +2,8 @@ echo -e "<%= strings[:commands] %>" <%- maxlen = command_names.map(&:size).max -%> <%- commands.each do |command| -%> -echo " <%= command.name.ljust maxlen %> <%= command.summary %>" +<%- summary = command.summary -%> +<%- summary = "#{summary} (default)" if command.default -%> +echo " <%= command.name.ljust maxlen %> <%= summary %>" <%- end -%> echo diff --git a/spec/approvals/examples/default-command b/spec/approvals/examples/default-command new file mode 100644 index 00000000..36acb906 --- /dev/null +++ b/spec/approvals/examples/default-command @@ -0,0 +1,56 @@ ++ bashly generate +creating user files in src +created src/initialize.sh +created src/upload_command.sh +created src/download_command.sh +created ./ftp +run ./ftp --help to test your bash script ++ ./ftp +ftp - Sample application that uses the default command option + +Usage: + ftp [command] [options] + ftp [command] --help | -h + ftp --version + +Commands: + upload Upload a file (default) + download Download a file + ++ ./ftp -h +ftp - Sample application that uses the default command option + +Usage: + ftp [command] [options] + ftp [command] --help | -h + ftp --version + +Commands: + upload Upload a file (default) + download Download a file + +Options: + --help, -h + Show this help + + --version + Show version number + ++ ./ftp download something +# this file is located in 'src/download_command.sh' +# code for 'ftp download' goes here +# you can edit it freely and regenerate (it will not be overwritten) +args: +- ${args[source]} = something ++ ./ftp upload something +# this file is located in 'src/upload_command.sh' +# code for 'ftp upload' goes here +# you can edit it freely and regenerate (it will not be overwritten) +args: +- ${args[source]} = something ++ ./ftp something +# this file is located in 'src/upload_command.sh' +# code for 'ftp upload' goes here +# you can edit it freely and regenerate (it will not be overwritten) +args: +- ${args[source]} = something diff --git a/spec/bashly/models/command_spec.rb b/spec/bashly/models/command_spec.rb index 70c4b21f..c89a37f8 100644 --- a/spec/bashly/models/command_spec.rb +++ b/spec/bashly/models/command_spec.rb @@ -90,6 +90,22 @@ end end + describe '#default_command' do + let(:fixture) { :default_command } + + it "returns a Command object of the first default command" do + expect(subject.default_command).to be_a Models::Command + expect(subject.default_command.name).to eq 'get' + end + end + + describe '#environment_cariables' do + it "returns an array of EnvironemntVariable objects" do + expect(subject.environment_variables).to be_an Array + expect(subject.environment_variables.first).to be_a Models::EnvironmentVariable + end + end + describe '#filename' do context "when it is the root command" do it "returns root_command.sh" do @@ -169,6 +185,13 @@ end end + describe '#required_environment_variables' do + it "returns an array of only the required Argument objects" do + expect(subject.required_environment_variables.size).to eq 1 + expect(subject.required_environment_variables.first.name).to eq "secret_key" + end + end + describe '#required_flags' do it "returns an array of only the required Flag objects" do expect(subject.required_flags.size).to eq 1 diff --git a/spec/fixtures/models/commands.yml b/spec/fixtures/models/commands.yml index ac42246d..d6ccf599 100644 --- a/spec/fixtures/models/commands.yml +++ b/spec/fixtures/models/commands.yml @@ -15,6 +15,11 @@ required: true - long: --verbose + environment_variables: + - name: secret_key + required: true + - name: target_folder + :git_status: name: status help: perform git status @@ -56,4 +61,13 @@ commands: - name: sub flags: - - long: --force \ No newline at end of file + - long: --force + +:default_command: + commands: + - name: get + default: true + - name: post + - name: put + + From a01d9b4f44c4fcb6f90d19a4bd8e4d29e79a9e3c Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Thu, 28 Nov 2019 23:29:24 +0000 Subject: [PATCH 2/2] readme update --- README.md | 148 +++++++++++------------------------------------------- 1 file changed, 30 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index e20aa155..36d0c0fc 100644 --- a/README.md +++ b/README.md @@ -133,147 +133,59 @@ The `bashly.yml` configuration file consists of these types: Unless otherwise specified, these definitiona can be used for both the root command and subcommands (under the `commands` definition). -```yaml -# The name of the script or subcommand -name: myscript - -# An additional, optional pattern - usually used to denote a one letter -# variation of the command name. -# You can add '*' as suffix, to denote a "starts with" pattern. -# Applicable only in subcommands -short: m* - -# The header text to display when using --help -# This can have multiple lines. In this case, the first line will be used as -# summary wherever appropriate. -help: a sample script generated with bashly - -# The string to display when using --version -# Applicable only in the main command -version: 0.1.0 - -# Specify an array of examples to show when using --help -# Each example can have multiple lines. -examples: -- myscript download -- myscript download --force - -# Specify an array of environment variables needed by your script. -environment_variables: -- ... see below ... - -# Specify the array of subcommands to generate. -# Each subcommand will have its own args and flags. -# If this is provided, you cannot specify flags or args. -commands: -- ... - -# Specify the array of positional arguments this script needs. -# If this is provided, then you cannot specify commands. -args: -- ... see below ... - -# Specify the array of option flags this script needs. -# If this is provided, then you cannot specify commands. -flags: -- ... see below ... - -# Specify an array of any required external dependencies (commands). -# The script execution will be halted with a friendly error unless all -# dependency commands exist. -dependencies: -- curl -``` + Option | Description +-----------|------------- +`name` | The name of the script or subcommand. +`short` | An additional, optional pattern - usually used to denote a one letter variation of the command name. You can add `*` as a suffix, to denote a "starts with" pattern - for example `short: m*`. *Applicable only in subcommands*. +`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 `yes` on any subcommand, will make unrecognized command line arguments to be passed to this command. *Applicable only in subcommands*. +`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 needed by your script. +`commands` | Specify the array of subcommands. Each subcommand will have its own args and flags. Note: if `commands` is provided, you cannot specify flags or args at the same level. +`args` | Specify the array of positional arguments this script needs. +`flags` | Specify the array of option flags this script needs. +`dependencies` | Specify an array of any required external dependencies (commands). The script execution will be halted with a friendly error unless all dependency commands exist. ### Argument options -The below configuration generates this argument: - -``` - Usage: - myscript USER - - Arguments: - USER - Username to use for logging in -``` - The argument's value will be available to you as `${args[user]}` in your bash function. -```yaml -# The name of the argument. -name: user - -# The message to display when using --help. -# This can have multiple lines. -help: Username to use for logging in - -# Specify if this argument is required. -# Note that once you define an optional argument (without required: true) -# then you cannot define required arguments after it. -required: true -``` + Option | Description +-----------|------------- +`name` | The name of the argument. +`help` | The message to display when using `--help`. Can have multiple lines. +`required` | Specify if this argument is required. Note that once you define an optional argument (without required: true) then you cannot define required arguments after it. ### Flag options -The below configuration generates this flag: - -``` - Options: - -o, --output DIRECTORY (required) - Specify the output directory -``` - The flag's value will be available to you as `${args[--output]}` in your bash function (regardless of whether the user provided ut with the long or short form). -```yaml -# The long form of the flag. -long: --output - -# The short form of the flag. -short: -o - -# The text to display when using --help -# This can have multiple lines. -help: Specify the output directory - -# If the flag requires an argument, specify its name here. -arg: directory - -# Specify if this flag is required. -required: true -``` + Option | Description +-----------|------------- +`long` | The long form of the flag. +`short` | The short form of the flag. +`help` | The text to display when using `--help`. Can have multiple lines. +`arg` | If the flag requires an argument, specify its name here. +`required` | Specify if this flag is required. ### Environment Variable options The below configuration generates this environment variable usage text: -``` - Environment Variables: - SECRET_KEY (required) - Your API secret key -``` - If an environment variable is defined as required (false by default), the execution of the script will be halted with a friendly error if it is not set. -```yaml -# The name of the variable (it will be automatically capitalized). -name: secret_key - -# The message to display when using --help. -# This can have multiple lines. -help: Your API secret key - -# Specify if this variable is required. -required: true -``` - + Option | Description +-----------|------------- +`name` | The name of the variable (it will be automatically capitalized). +`help` | The message to display when using --help. Can have multiple lines. +`required` | Specify if this variable is required. Real World Examples