diff --git a/examples/README.md b/examples/README.md index c035d11f..08e92b7e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -61,7 +61,7 @@ Each of these examples demonstrates one aspect or feature of bashly. - [yaml](yaml#readme) - using the YAML reading functions - [colors](colors#readme) - using the color print feature - [completions](completions#readme) - adding bash completion functionality -- [validations](validations#readme) - adding argument validation functions +- [validations](validations#readme) - adding validation functions for arguments, flags or environment variables - [hooks](hooks#readme) - adding before/after hooks ## Real-world-like examples diff --git a/examples/needs/README.md b/examples/needs/README.md index a3ff0b50..d3825211 100644 --- a/examples/needs/README.md +++ b/examples/needs/README.md @@ -83,7 +83,7 @@ Options: ### `$ ./cli --add deploy` ````shell ---add requires --command +--add needs --command ```` @@ -91,7 +91,7 @@ Options: ### `$ ./cli --add deploy --command 'git push'` ````shell ---add requires --target +--add needs --target ```` diff --git a/examples/validations/README.md b/examples/validations/README.md index 9b436fb4..75c72c47 100644 --- a/examples/validations/README.md +++ b/examples/validations/README.md @@ -50,6 +50,15 @@ commands: # Validations also work on flags (when they have arguments) validate: file_exists + +- name: build + environment_variables: + - name: build_dir + help: Path to the build directory + default: release + + # Validations also work on environment variables + validate: dir_exists ```` @@ -97,5 +106,28 @@ must be an existing file ```` +### `$ ./validate build` + +````shell +validation error in environment variable BUILD_DIR: +must be an existing directory + + +```` + +### `$ BUILD_DIR=src ./validate build` + +````shell +# this file is located in 'src/build_command.sh' +# code for 'validate build' goes here +# you can edit it freely and regenerate (it will not be overwritten) +args: none + +environment variables: +- $BUILD_DIR = src + + +```` + diff --git a/examples/validations/src/bashly.yml b/examples/validations/src/bashly.yml index 058e6df2..ae6c3ce8 100644 --- a/examples/validations/src/bashly.yml +++ b/examples/validations/src/bashly.yml @@ -27,3 +27,11 @@ commands: # Validations also work on flags (when they have arguments) validate: file_exists +- name: build + environment_variables: + - name: build_dir + help: Path to the build directory + default: release + + # Validations also work on environment variables + validate: dir_exists diff --git a/examples/validations/src/build_command.sh b/examples/validations/src/build_command.sh new file mode 100644 index 00000000..ec2f6d2b --- /dev/null +++ b/examples/validations/src/build_command.sh @@ -0,0 +1,4 @@ +echo "# this file is located in 'src/build_command.sh'" +echo "# code for 'validate build' goes here" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/validations/test.sh b/examples/validations/test.sh index 83adb4eb..e0574e17 100644 --- a/examples/validations/test.sh +++ b/examples/validations/test.sh @@ -14,3 +14,6 @@ bashly generate ./validate calc A ./validate calc 1 B ./validate calc 1 2 --save no-such-file.txt + +./validate build +BUILD_DIR=src ./validate build \ No newline at end of file diff --git a/lib/bashly/docs/env.yml b/lib/bashly/docs/env.yml index 21246489..096cc0d5 100644 --- a/lib/bashly/docs/env.yml +++ b/lib/bashly/docs/env.yml @@ -49,6 +49,16 @@ environment_variable.required: help: Specify that this variable is required. url: https://bashly.dannyb.co/configuration/environment-variable/#required example: |- + environment_variables: - name: api_key help: Your API key required: true + +environment_variable.validate: + help: Apply custom validation functions. + + url: https://bashly.dannyb.co/configuration/environment-variable/#validate + example: |- + environment_variables: + - name: build_dir + validate: dir_exists diff --git a/lib/bashly/libraries/strings/strings.yml b/lib/bashly/libraries/strings/strings.yml index 02f597ad..13834bed 100644 --- a/lib/bashly/libraries/strings/strings.yml +++ b/lib/bashly/libraries/strings/strings.yml @@ -42,3 +42,4 @@ disallowed_argument: "%{name} must be one of: %{allowed}" disallowed_environment_variable: "%{name} environment variable must be one of: %{allowed}" unsupported_bash_version: "bash version 4 or higher is required" validation_error: "validation error in %s:\\n%s" +environment_variable_validation_error: "validation error in environment variable %s:\\n%s" diff --git a/lib/bashly/script/environment_variable.rb b/lib/bashly/script/environment_variable.rb index b86ab274..c60a041b 100644 --- a/lib/bashly/script/environment_variable.rb +++ b/lib/bashly/script/environment_variable.rb @@ -3,7 +3,9 @@ module Script class EnvironmentVariable < Base class << self def option_keys - @option_keys ||= %i[allowed default help name required private] + @option_keys ||= %i[ + allowed default help name required private validate + ] end end diff --git a/lib/bashly/script/introspection/environment_variables.rb b/lib/bashly/script/introspection/environment_variables.rb index 0f2da004..512204f9 100644 --- a/lib/bashly/script/introspection/environment_variables.rb +++ b/lib/bashly/script/introspection/environment_variables.rb @@ -2,7 +2,7 @@ module Bashly module Script module Introspection module EnvironmentVariables - # Returns an array of all the default Environment Variables + # Returns an array of all the Environment Variables with default values def default_environment_variables environment_variables.select(&:default) end @@ -26,6 +26,11 @@ def required_environment_variables environment_variables.select(&:required) end + # Returns an array of all the environment_variables with a validation + def validated_environment_variables + environment_variables.select(&:validate) + end + # Returns an array of all the environment_variables with a whitelist arg def whitelisted_environment_variables environment_variables.select(&:allowed) diff --git a/lib/bashly/script/introspection/flags.rb b/lib/bashly/script/introspection/flags.rb index 07485664..81cea8cf 100644 --- a/lib/bashly/script/introspection/flags.rb +++ b/lib/bashly/script/introspection/flags.rb @@ -22,6 +22,11 @@ def global_flags? flags.any? and commands.any? end + # Returns an array of all fpags that need other flags + def needy_flags + flags.select(&:needs) + end + # Returns only flags that are not private def public_flags flags.reject(&:private) @@ -32,11 +37,6 @@ def required_flags flags.select(&:required) end - # Returns an array of all fpags that need other flags - def needy_flags - flags.select(&:needs) - end - # Returns true if one of the flags matches the provided short code def short_flag_exist?(flag) flags.any? { |f| f.short == flag } diff --git a/lib/bashly/views/command/environment_variables_filter.gtx b/lib/bashly/views/command/environment_variables_filter.gtx index 666d9377..f95fdf87 100644 --- a/lib/bashly/views/command/environment_variables_filter.gtx +++ b/lib/bashly/views/command/environment_variables_filter.gtx @@ -24,3 +24,9 @@ if whitelisted_environment_variables.any? > fi end end + +if validated_environment_variables.any? + validated_environment_variables.each do |env_var| + = env_var.render(:validations) + end +end diff --git a/lib/bashly/views/environment_variable/validations.gtx b/lib/bashly/views/environment_variable/validations.gtx new file mode 100644 index 00000000..805536cf --- /dev/null +++ b/lib/bashly/views/environment_variable/validations.gtx @@ -0,0 +1,9 @@ +if validate + = view_marker + + > if [[ -v {{ name.upcase }} && -n $(validate_{{ validate }} "${{ name.upcase }}") ]]; then + > printf "{{ strings[:environment_variable_validation_error] }}\n" "{{ usage_string }}" "$(validate_{{ validate }} "${{ name.upcase }}")" >&2 + > exit 1 + > fi + > +end diff --git a/schemas/bashly.json b/schemas/bashly.json index c1a3f26b..441c42e7 100644 --- a/schemas/bashly.json +++ b/schemas/bashly.json @@ -334,6 +334,17 @@ "type": "boolean", "default": true }, + "validate": { + "title": "validate", + "description": "A validation function for the current environment variable\nhttps://bashly.dannyb.co/configuration/environment-variable/#validate", + "type": "string", + "examples": [ + "file_exists", + "dir_exists", + "integer", + "non_empty" + ] + }, "allowed": { "title": "allowed", "description": "Valid values of the current environment variable\nhttps://bashly.dannyb.co/configuration/environment-variable/#allowed", diff --git a/schemas/strings.json b/schemas/strings.json index b57df61a..60a864b9 100644 --- a/schemas/strings.json +++ b/schemas/strings.json @@ -241,6 +241,13 @@ "type": "string", "minLength": 1, "default": "validation error in %s:\\n%s" + }, + "environment_variable_validation_error": { + "title": "environment variable validation error", + "description": "The error message template for failed custom validation for environment variables\nhttps://bashly.dannyb.co/advanced/strings/#custom-strings", + "type": "string", + "minLength": 1, + "default": "validation error in environment variable %s:\\n%s" } }, "additionalProperties": false diff --git a/spec/approvals/cli/doc/full b/spec/approvals/cli/doc/full index 18fe0697..67f56ae8 100644 --- a/spec/approvals/cli/doc/full +++ b/spec/approvals/cli/doc/full @@ -539,12 +539,23 @@ environment_variable.required Specify that this variable is required. + environment_variables: - name: api_key help: Your API key required: true See https://bashly.dannyb.co/configuration/environment-variable/#required +environment_variable.validate + + Apply custom validation functions. + + environment_variables: + - name: build_dir + validate: dir_exists + + See https://bashly.dannyb.co/configuration/environment-variable/#validate + flag Define option flags. diff --git a/spec/approvals/cli/doc/index b/spec/approvals/cli/doc/index index d9d7d9d6..00791e68 100644 --- a/spec/approvals/cli/doc/index +++ b/spec/approvals/cli/doc/index @@ -34,6 +34,7 @@ environment_variable.help environment_variable.name environment_variable.private environment_variable.required +environment_variable.validate flag flag.allowed flag.arg diff --git a/spec/approvals/examples/validations b/spec/approvals/examples/validations index 76b2085c..ffe232f9 100644 --- a/spec/approvals/examples/validations +++ b/spec/approvals/examples/validations @@ -6,6 +6,7 @@ created src/lib/validations/validate_not_empty.sh + bashly generate creating user files in src created src/calc_command.sh +created src/build_command.sh created ./validate run ./validate --help to test your bash script + ./validate calc 1 2 --save README.md @@ -25,3 +26,15 @@ must be an integer + ./validate calc 1 2 --save no-such-file.txt validation error in --save PATH: must be an existing file ++ ./validate build +validation error in environment variable BUILD_DIR: +must be an existing directory ++ BUILD_DIR=src ++ ./validate build +# this file is located in 'src/build_command.sh' +# code for 'validate build' goes here +# you can edit it freely and regenerate (it will not be overwritten) +args: none + +environment variables: +- $BUILD_DIR = src diff --git a/spec/bashly/script/command_spec.rb b/spec/bashly/script/command_spec.rb index a096bbdb..f7d3adce 100644 --- a/spec/bashly/script/command_spec.rb +++ b/spec/bashly/script/command_spec.rb @@ -48,13 +48,6 @@ end end - describe '#args' do - it 'returns an array of Argument objects' do - expect(subject.args).to be_an Array - expect(subject.args.first).to be_a Script::Argument - end - end - describe '#caption_string' do it 'returns a string containing the name and summary' do expect(subject.caption_string).to eq 'get - get something from somewhere' @@ -69,129 +62,6 @@ end end - describe '#command_aliases' do - let(:fixture) { :aliases } - - it 'returns an array of command aliases' do - expect(subject.command_aliases).to eq %w[download d pull upload u push update upgrade] - end - end - - describe '#command_help_data' do - let(:fixture) { :exposed } - - it 'returns a hash suitable for showing command and exposed subcommand help' do - expect(subject.command_help_data.to_yaml) - .to match_approval('script/command/exposed_commands') - end - end - - describe '#command_names' do - let(:fixture) { :docker } - - it 'returns an array of command names' do - expect(subject.command_names).to eq %w[container image] - end - end - - describe '#commands' do - let(:fixture) { :docker } - - it 'returns an array of Command objects' do - expect(subject.commands).to be_an Array - expect(subject.commands.first).to be_a described_class - end - - it 'sets the parents property of its commands' do - expect(subject.commands.first.parents).to eq ['docker'] - end - end - - describe '#deep_commands' do - let(:fixture) { :docker } - - it 'returns an array of all commands in the tree' do - expect(subject.deep_commands.map(&:full_name)) - .to eq ['docker container', 'docker container run', 'docker container stop', 'docker image'] - end - - context 'when include_self is true' do - it 'prepends the result with the command itself' do - expect(subject.deep_commands(include_self: true).map(&:full_name)) - .to eq ['docker', 'docker container', 'docker container run', 'docker container stop', 'docker image'] - end - end - end - - describe '#default_arguments' do - let(:fixture) { :default_values } - - it 'returns an array of only the Argument objects that have default' do - expect(subject.default_args.size).to eq 1 - expect(subject.default_args.first.name).to eq 'files' - 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 described_class - expect(subject.default_command.name).to eq 'get' - end - end - - describe '#default_flags' do - let(:fixture) { :default_values } - - it 'returns an array of only the Flags objects that have default' do - expect(subject.default_flags.size).to eq 1 - expect(subject.default_flags.first.long).to eq '--format' - end - end - - describe '#dependencies' do - let(:fixture) { :dependencies } - - it 'returns an array of Dependency objects' do - expect(subject.dependencies).to be_an Array - expect(subject.dependencies.first).to be_a Script::Dependency - end - end - - describe '#environment_cariables' do - it 'returns an array of EnvironmentVariable objects' do - expect(subject.environment_variables).to be_an Array - expect(subject.environment_variables.first).to be_a Script::EnvironmentVariable - end - end - - describe '#examples' do - context 'when there are no examples' do - it 'returns nil' do - expect(subject.examples).to be_nil - end - end - - context 'when there are examples as array' do - let(:fixture) { :examples_array } - - it 'returns the array' do - expect(subject.examples).to be_an Array - end - end - - context 'when there are examples as string' do - let(:fixture) { :examples_string } - - it 'returns an array with one item' do - expect(subject.examples).to be_an Array - expect(subject.examples.count).to eq 1 - expect(subject.examples.first).to start_with 'Download a file' - end - end - end - describe '#filename' do context 'when it is the root command' do it 'returns root_command.sh' do @@ -251,13 +121,6 @@ end end - describe '#flags' do - it 'returns an array of Flag objects' do - expect(subject.flags).to be_an Array - expect(subject.flags.first).to be_a Script::Flag - end - end - describe '#function_name' do let(:fixture) { :docker_container_run } @@ -290,32 +153,6 @@ end end - describe '#global_flags?' do - context 'when a command has flags and commands' do - let(:fixture) { :mode_global_flags } - - it 'returns true' do - expect(subject).to be_global_flags - end - end - - context 'when a command has flags but no commands' do - let(:fixture) { :mode_flags } - - it 'returns false' do - expect(subject).not_to be_global_flags - end - end - - context 'when a command has commands but no flags' do - let(:fixture) { :mode_commands } - - it 'returns false' do - expect(subject).not_to be_global_flags - end - end - end - describe '#group_string' do it 'returns a string suitable for showing the group in usage' do expect(subject.group_string).to eq 'Commands:' @@ -330,16 +167,6 @@ end end - describe '#grouped_commands' do - let(:fixture) { :exposed } - - it 'returns a hash with an array of Command objects per group key' do - expect(subject.grouped_commands.keys).to contain_exactly('Cluster Commands:', 'Commands:') - expect(subject.grouped_commands['Commands:'].count).to eq 4 - expect(subject.grouped_commands['Commands:']).to all(be_a described_class) - end - end - describe '#has_unique_args_or_flags?' do context 'when the command has any args that are unique' do let(:fixture) { :unique_args } @@ -455,69 +282,6 @@ end end - describe '#needy_flags' do - let(:fixture) { :needy_flags } - - it 'returns an array of only the needy Flag objects' do - expect(subject.needy_flags.size).to eq 2 - expect(subject.needy_flags.first.long).to eq '--add' - end - end - - describe '#public_commands' do - let(:fixture) { :private_commands } - - it 'returns an array of Command objects excluding private commands' do - expect(subject.public_commands.count).to eq 1 - expect(subject.public_commands.first.name).to eq 'connect' - end - end - - describe '#public_commands_aliases' do - let(:fixture) { :private_commands } - - it 'returns an array of command aliases of public subcommands' do - expect(subject.public_command_aliases).to eq %w[connect c] - end - end - - describe '#required_args' do - it 'returns an array of only the required Argument objects' do - expect(subject.required_args.size).to eq 1 - expect(subject.required_args.first.name).to eq 'source' - 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 - expect(subject.required_flags.first.long).to eq '--force' - end - end - - describe '#repeatable_arg_exist?' do - context 'when the command does not have any repeatable flags' do - it 'returns false' do - expect(subject.repeatable_arg_exist?).to be false - end - end - - context 'when the command has at least one repeatable flag' do - let(:fixture) { :repeatable_arg } - - it 'returns true' do - expect(subject.repeatable_arg_exist?).to be true - end - end - end - describe '#root_command?' do context 'when the command has no parents' do it 'returns true' do @@ -534,22 +298,6 @@ end end - describe '#short_flag_exist?' do - let(:fixture) { :flag_hog } - - context 'when the command has this short flag' do - it 'returns true' do - expect(subject.short_flag_exist?('-h')).to be true - end - end - - context 'when the command does not have this short flag' do - it 'returns false' do - expect(subject.short_flag_exist?('-s')).to be false - end - end - end - describe '#summary_string' do it 'returns the user defined summary' do expect(subject.summary_string).to eq 'get something from somewhere' @@ -678,22 +426,4 @@ end end end - - describe '#whitelisted_args' do - let(:fixture) { :whitelist } - - it 'returns an array of args that have a whitelist' do - expect(subject.whitelisted_args.size).to eq 1 - expect(subject.whitelisted_args.first.name).to eq 'region' - end - end - - describe '#whitelisted_flags' do - let(:fixture) { :whitelist } - - it 'returns an array of flags that have a whitelist' do - expect(subject.whitelisted_flags.size).to eq 1 - expect(subject.whitelisted_flags.first.long).to eq '--user' - end - end end diff --git a/spec/bashly/script/introspection/arguments_spec.rb b/spec/bashly/script/introspection/arguments_spec.rb new file mode 100644 index 00000000..d27c04c5 --- /dev/null +++ b/spec/bashly/script/introspection/arguments_spec.rb @@ -0,0 +1,58 @@ +describe Script::Introspection::Arguments do + subject do + result = Script::Command.new fixtures[fixture] + result.parents = result.options['parents'] + result + end + + let(:fixtures) { load_fixture 'script/commands' } + let(:fixture) { :basic_command } + + describe '#args' do + it 'returns an array of Argument objects' do + expect(subject.args).to be_an Array + expect(subject.args.first).to be_a Script::Argument + end + end + + describe '#default_args' do + let(:fixture) { :default_values } + + it 'returns an array of only the Argument objects that have default' do + expect(subject.default_args.size).to eq 1 + expect(subject.default_args.first.name).to eq 'files' + end + end + + describe '#repeatable_arg_exist?' do + context 'when the command does not have any repeatable flags' do + it 'returns false' do + expect(subject.repeatable_arg_exist?).to be false + end + end + + context 'when the command has at least one repeatable flag' do + let(:fixture) { :repeatable_arg } + + it 'returns true' do + expect(subject.repeatable_arg_exist?).to be true + end + end + end + + describe '#required_args' do + it 'returns an array of only the required Argument objects' do + expect(subject.required_args.size).to eq 1 + expect(subject.required_args.first.name).to eq 'source' + end + end + + describe '#whitelisted_args' do + let(:fixture) { :whitelist } + + it 'returns an array of args that have a whitelist' do + expect(subject.whitelisted_args.size).to eq 1 + expect(subject.whitelisted_args.first.name).to eq 'region' + end + end +end diff --git a/spec/bashly/script/introspection/commands_spec.rb b/spec/bashly/script/introspection/commands_spec.rb new file mode 100644 index 00000000..9e9668d1 --- /dev/null +++ b/spec/bashly/script/introspection/commands_spec.rb @@ -0,0 +1,100 @@ +describe Script::Introspection::Commands do + subject do + result = Script::Command.new fixtures[fixture] + result.parents = result.options['parents'] + result + end + + let(:fixtures) { load_fixture 'script/commands' } + let(:fixture) { :basic_command } + + describe '#command_aliases' do + let(:fixture) { :aliases } + + it 'returns an array of command aliases' do + expect(subject.command_aliases).to eq %w[download d pull upload u push update upgrade] + end + end + + describe '#command_help_data' do + let(:fixture) { :exposed } + + it 'returns a hash suitable for showing command and exposed subcommand help' do + expect(subject.command_help_data.to_yaml) + .to match_approval('script/command/exposed_commands') + end + end + + describe '#command_names' do + let(:fixture) { :docker } + + it 'returns an array of command names' do + expect(subject.command_names).to eq %w[container image] + end + end + + describe '#commands' do + let(:fixture) { :docker } + + it 'returns an array of Command objects' do + expect(subject.commands).to be_an Array + expect(subject.commands.first).to be_a described_class + end + + it 'sets the parents property of its commands' do + expect(subject.commands.first.parents).to eq ['docker'] + end + end + + describe '#deep_commands' do + let(:fixture) { :docker } + + it 'returns an array of all commands in the tree' do + expect(subject.deep_commands.map(&:full_name)) + .to eq ['docker container', 'docker container run', 'docker container stop', 'docker image'] + end + + context 'when include_self is true' do + it 'prepends the result with the command itself' do + expect(subject.deep_commands(include_self: true).map(&:full_name)) + .to eq ['docker', 'docker container', 'docker container run', 'docker container stop', 'docker image'] + end + 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 described_class + expect(subject.default_command.name).to eq 'get' + end + end + + describe '#grouped_commands' do + let(:fixture) { :exposed } + + it 'returns a hash with an array of Command objects per group key' do + expect(subject.grouped_commands.keys).to contain_exactly('Cluster Commands:', 'Commands:') + expect(subject.grouped_commands['Commands:'].count).to eq 4 + expect(subject.grouped_commands['Commands:']).to all(be_a described_class) + end + end + + describe '#public_commands' do + let(:fixture) { :private_commands } + + it 'returns an array of Command objects excluding private commands' do + expect(subject.public_commands.count).to eq 1 + expect(subject.public_commands.first.name).to eq 'connect' + end + end + + describe '#public_commands_aliases' do + let(:fixture) { :private_commands } + + it 'returns an array of command aliases of public subcommands' do + expect(subject.public_command_aliases).to eq %w[connect c] + end + end +end diff --git a/spec/bashly/script/introspection/dependencies_spec.rb b/spec/bashly/script/introspection/dependencies_spec.rb new file mode 100644 index 00000000..389de2a8 --- /dev/null +++ b/spec/bashly/script/introspection/dependencies_spec.rb @@ -0,0 +1,19 @@ +describe Script::Introspection::Dependencies do + subject do + result = Script::Command.new fixtures[fixture] + result.parents = result.options['parents'] + result + end + + let(:fixtures) { load_fixture 'script/commands' } + let(:fixture) { :basic_command } + + describe '#dependencies' do + let(:fixture) { :dependencies } + + it 'returns an array of Dependency objects' do + expect(subject.dependencies).to be_an Array + expect(subject.dependencies.first).to be_a Script::Dependency + end + end +end diff --git a/spec/bashly/script/introspection/environment_variables_spec.rb b/spec/bashly/script/introspection/environment_variables_spec.rb new file mode 100644 index 00000000..a400676c --- /dev/null +++ b/spec/bashly/script/introspection/environment_variables_spec.rb @@ -0,0 +1,52 @@ +describe Script::Introspection::EnvironmentVariables do + subject do + result = Script::Command.new fixtures[fixture] + result.parents = result.options['parents'] + result + end + + let(:fixtures) { load_fixture 'script/commands' } + let(:fixture) { :environment_variable_enthusiast } + + describe '#default_environment_variables' do + it 'returns an array of all the Environment Variables with default values' do + expect(subject.default_environment_variables.size).to eq 1 + expect(subject.default_environment_variables.first.name).to eq 'file' + end + end + + describe '#environment_variables' do + it 'returns an array of EnvironmentVariable objects' do + expect(subject.environment_variables).to be_an Array + expect(subject.environment_variables).to all(be_a Script::EnvironmentVariable) + end + end + + describe '#public_environment_variables' do + it 'returns only environment variables that are not private' do + expect(subject.public_environment_variables.size).to eq 4 + expect(subject.public_environment_variables.map(&:name)).not_to include 'secret' + 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 'username' + end + end + + describe '#validated_environment_variables' do + it 'returns an array of all the environment variables with a validation' do + expect(subject.validated_environment_variables.size).to eq 1 + expect(subject.validated_environment_variables.first.name).to eq 'config_dir' + end + end + + describe '#whitelisted_environment_variables' do + it 'returns an array of all the environment_variables with a whitelist arg' do + expect(subject.whitelisted_environment_variables.size).to eq 1 + expect(subject.whitelisted_environment_variables.first.name).to eq 'protocol' + end + end +end diff --git a/spec/bashly/script/introspection/examples_spec.rb b/spec/bashly/script/introspection/examples_spec.rb new file mode 100644 index 00000000..ab08c068 --- /dev/null +++ b/spec/bashly/script/introspection/examples_spec.rb @@ -0,0 +1,36 @@ +describe Script::Introspection::Examples do + subject do + result = Script::Command.new fixtures[fixture] + result.parents = result.options['parents'] + result + end + + let(:fixtures) { load_fixture 'script/commands' } + let(:fixture) { :basic_command } + + describe '#examples' do + context 'when there are no examples' do + it 'returns nil' do + expect(subject.examples).to be_nil + end + end + + context 'when there are examples as array' do + let(:fixture) { :examples_array } + + it 'returns the array' do + expect(subject.examples).to be_an Array + end + end + + context 'when there are examples as string' do + let(:fixture) { :examples_string } + + it 'returns an array with one item' do + expect(subject.examples).to be_an Array + expect(subject.examples.count).to eq 1 + expect(subject.examples.first).to start_with 'Download a file' + end + end + end +end diff --git a/spec/bashly/script/introspection/flags_spec.rb b/spec/bashly/script/introspection/flags_spec.rb new file mode 100644 index 00000000..bdef65b1 --- /dev/null +++ b/spec/bashly/script/introspection/flags_spec.rb @@ -0,0 +1,102 @@ +describe Script::Introspection::Flags do + subject do + result = Script::Command.new fixtures[fixture] + result.parents = result.options['parents'] + result + end + + let(:fixtures) { load_fixture 'script/commands' } + let(:fixture) { :basic_command } + + describe '#default_flags' do + let(:fixture) { :default_values } + + it 'returns an array of only the Flags objects that have default' do + expect(subject.default_flags.size).to eq 1 + expect(subject.default_flags.first.long).to eq '--format' + end + end + + describe '#flags' do + it 'returns an array of Flag objects' do + expect(subject.flags).to be_an Array + expect(subject.flags.first).to be_a Script::Flag + end + end + + describe '#global_flags?' do + context 'when a command has flags and commands' do + let(:fixture) { :mode_global_flags } + + it 'returns true' do + expect(subject).to be_global_flags + end + end + + context 'when a command has flags but no commands' do + let(:fixture) { :mode_flags } + + it 'returns false' do + expect(subject).not_to be_global_flags + end + end + + context 'when a command has commands but no flags' do + let(:fixture) { :mode_commands } + + it 'returns false' do + expect(subject).not_to be_global_flags + end + end + end + + describe '#needy_flags' do + let(:fixture) { :needy_flags } + + it 'returns an array of only the needy Flag objects' do + expect(subject.needy_flags.size).to eq 2 + expect(subject.needy_flags.first.long).to eq '--add' + end + end + + describe '#public_flags' do + let(:fixture) { :private_flags } + + it 'returns an array of only the non private Flag objects' do + expect(subject.public_flags.size).to eq 1 + expect(subject.public_flags.first.long).to eq '--new' + 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 + expect(subject.required_flags.first.long).to eq '--force' + end + end + + describe '#short_flag_exist?' do + let(:fixture) { :flag_hog } + + context 'when the command has this short flag' do + it 'returns true' do + expect(subject.short_flag_exist?('-h')).to be true + end + end + + context 'when the command does not have this short flag' do + it 'returns false' do + expect(subject.short_flag_exist?('-s')).to be false + end + end + end + + describe '#whitelisted_flags' do + let(:fixture) { :whitelist } + + it 'returns an array of flags that have a whitelist' do + expect(subject.whitelisted_flags.size).to eq 1 + expect(subject.whitelisted_flags.first.long).to eq '--user' + end + end +end diff --git a/spec/fixtures/script/commands.yml b/spec/fixtures/script/commands.yml index b4c49077..50a9c427 100644 --- a/spec/fixtures/script/commands.yml +++ b/spec/fixtures/script/commands.yml @@ -199,6 +199,20 @@ help: perform docker container run parents: [docker, container] +:environment_variable_enthusiast: + name: get + environment_variables: + - name: username + required: true + - name: file + default: README.md + - name: config_dir + validate: dir_exists + - name: protocol + allowed: [ssh, https] + - name: secret + private: true + :examples_array: name: get help: get something from somewhere @@ -404,6 +418,14 @@ alias: cf private: true +:private_flags: + name: status + help: perform git status + flags: + - long: --legacy + private: true + - long: --new + :repeatable_arg: name: get args: diff --git a/support/schema/bashly.yml b/support/schema/bashly.yml index 70ac28a6..37fd6a01 100644 --- a/support/schema/bashly.yml +++ b/support/schema/bashly.yml @@ -300,6 +300,17 @@ definitions: https://bashly.dannyb.co/configuration/environment-variable/#required type: boolean default: true + validate: + title: validate + description: |- + A validation function for the current environment variable + https://bashly.dannyb.co/configuration/environment-variable/#validate + type: string + examples: + - file_exists + - dir_exists + - integer + - non_empty allowed: title: allowed description: |-