diff --git a/examples/validations/src/bashly.yml b/examples/validations/src/bashly.yml index 93790b65..db0e83e0 100644 --- a/examples/validations/src/bashly.yml +++ b/examples/validations/src/bashly.yml @@ -15,9 +15,12 @@ commands: # Bashly will look for a function named `validate_integer` in your # script, you can use any name as long as it has a matching function. validate: integer + - name: second help: Second number - validate: integer + + # Multiple validations can be provided as an array. + validate: [not_empty, integer] flags: - long: --save diff --git a/examples/validations/test.sh b/examples/validations/test.sh index f7eb8769..41f87490 100644 --- a/examples/validations/test.sh +++ b/examples/validations/test.sh @@ -12,6 +12,7 @@ bashly generate ./validate calc 1 2 --save README.md ./validate calc A +./validate calc 1 '' ./validate calc 1 B ./validate calc 1 2 --save no-such-file.txt diff --git a/lib/bashly.rb b/lib/bashly.rb index 016e2e2d..a934b5c1 100644 --- a/lib/bashly.rb +++ b/lib/bashly.rb @@ -28,7 +28,7 @@ module Script module Introspection autoloads 'bashly/script/introspection', %i[ Arguments Commands Dependencies EnvironmentVariables Examples Flags - Variables Visibility + Validate Variables Visibility ] end end diff --git a/lib/bashly/config_validator.rb b/lib/bashly/config_validator.rb index 650a5eb6..0c430054 100644 --- a/lib/bashly/config_validator.rb +++ b/lib/bashly/config_validator.rb @@ -92,7 +92,7 @@ def assert_arg(key, value) assert_string "#{key}.name", value['name'] assert_optional_string "#{key}.help", value['help'] assert_string_or_array "#{key}.default", value['default'] - assert_optional_string "#{key}.validate", value['validate'] + assert_string_or_array "#{key}.validate", value['validate'] assert_boolean "#{key}.required", value['required'] assert_boolean "#{key}.repeatable", value['repeatable'] assert_boolean "#{key}.unique", value['unique'] @@ -123,7 +123,7 @@ def assert_flag(key, value) assert_optional_string "#{key}.help", value['help'] assert_optional_string "#{key}.arg", value['arg'] assert_string_or_array "#{key}.default", value['default'] - assert_optional_string "#{key}.validate", value['validate'] + assert_string_or_array "#{key}.validate", value['validate'] assert_boolean "#{key}.private", value['private'] assert_boolean "#{key}.repeatable", value['repeatable'] @@ -169,7 +169,7 @@ def assert_env_var(key, value) assert_boolean "#{key}.required", value['required'] assert_boolean "#{key}.private", value['private'] assert_array "#{key}.allowed", value['allowed'], of: :string - assert_optional_string "#{key}.validate", value['validate'] + assert_string_or_array "#{key}.validate", value['validate'] refute value['required'] && value['default'], "#{key} cannot have both nub`required` and nub`default`" end diff --git a/lib/bashly/script/argument.rb b/lib/bashly/script/argument.rb index cb18fe79..5fd96f6e 100644 --- a/lib/bashly/script/argument.rb +++ b/lib/bashly/script/argument.rb @@ -3,6 +3,8 @@ module Bashly module Script class Argument < Base + include Introspection::Validate + class << self def option_keys @option_keys ||= %i[ diff --git a/lib/bashly/script/command.rb b/lib/bashly/script/command.rb index ba20bb00..7b109c0a 100644 --- a/lib/bashly/script/command.rb +++ b/lib/bashly/script/command.rb @@ -158,7 +158,7 @@ def user_lib # Returns a mixed array of Argument and Flag objects that have validations def validatables - @validatables ||= args.select(&:validate) + flags.select(&:validate) + @validatables ||= args.select(&:validate?) + flags.select(&:validate?) end private diff --git a/lib/bashly/script/environment_variable.rb b/lib/bashly/script/environment_variable.rb index 4e3b08b7..97a1d3ed 100644 --- a/lib/bashly/script/environment_variable.rb +++ b/lib/bashly/script/environment_variable.rb @@ -2,6 +2,7 @@ module Bashly module Script class EnvironmentVariable < Base include Introspection::Visibility + include Introspection::Validate class << self def option_keys diff --git a/lib/bashly/script/flag.rb b/lib/bashly/script/flag.rb index 2d5bc048..19fca03f 100644 --- a/lib/bashly/script/flag.rb +++ b/lib/bashly/script/flag.rb @@ -5,6 +5,7 @@ module Script class Flag < Base include Completions::Flag include Introspection::Visibility + include Introspection::Validate class << self def option_keys diff --git a/lib/bashly/script/introspection/environment_variables.rb b/lib/bashly/script/introspection/environment_variables.rb index 502be4bb..04476919 100644 --- a/lib/bashly/script/introspection/environment_variables.rb +++ b/lib/bashly/script/introspection/environment_variables.rb @@ -28,7 +28,7 @@ def required_environment_variables # Returns an array of all the environment_variables with a validation def validated_environment_variables - environment_variables.select(&:validate) + environment_variables.select(&:validate?) end # Returns only public environment variables, or both public and private diff --git a/lib/bashly/script/introspection/validate.rb b/lib/bashly/script/introspection/validate.rb new file mode 100644 index 00000000..8550bde0 --- /dev/null +++ b/lib/bashly/script/introspection/validate.rb @@ -0,0 +1,18 @@ +module Bashly + module Script + module Introspection + module Validate + # Returns an array of validations + def validate + return [] unless options['validate'] + + result = options['validate'] + result.is_a?(Array) ? result : [result] + end + + # Returns true if there are any validations defined + def validate? = validate.any? + end + end + end +end diff --git a/lib/bashly/views/argument/validations.gtx b/lib/bashly/views/argument/validations.gtx index 758217ab..d2b7e910 100644 --- a/lib/bashly/views/argument/validations.gtx +++ b/lib/bashly/views/argument/validations.gtx @@ -1,4 +1,4 @@ -if validate +if validate? = view_marker if repeatable @@ -6,20 +6,24 @@ if validate > values='' > eval "values=(${args['{{ name }}']})" > for value in "${values[@]}"; do - > validation_output="$(validate_{{ validate }} "$value")" + validate.each do |funcname| + > validation_output="$(validate_{{ funcname }} "$value")" > if [[ -n "$validation_output" ]]; then > printf "{{ strings[:validation_error] }}\n" "{{ name.upcase }}" "$validation_output" >&2 > exit 1 > fi + end > done > fi else > if [[ -v args['{{ name }}'] ]]; then - > validation_output="$(validate_{{ validate }} "${args['{{ name }}']:-}")" + validate.each do |funcname| + > validation_output="$(validate_{{ funcname }} "${args['{{ name }}']:-}")" > if [[ -n "$validation_output" ]]; then > printf "{{ strings[:validation_error] }}\n" "{{ name.upcase }}" "$validation_output" >&2 > exit 1 > fi + end > fi > end diff --git a/lib/bashly/views/environment_variable/validations.gtx b/lib/bashly/views/environment_variable/validations.gtx index abbf1ebc..578eef91 100644 --- a/lib/bashly/views/environment_variable/validations.gtx +++ b/lib/bashly/views/environment_variable/validations.gtx @@ -1,12 +1,14 @@ -if validate +if validate? = view_marker > if [[ -v {{ name.upcase }} ]]; then - > validation_output="$(validate_{{ validate }} "${{ name.upcase }}")" + validate.each do |funcname| + > validation_output="$(validate_{{ funcname }} "${{ name.upcase }}")" > if [[ -n "${validation_output}" ]]; then > printf "{{ strings[:environment_variable_validation_error] }}\n" "{{ usage_string }}" "$validation_output" >&2 > exit 1 > fi + end > fi > end diff --git a/lib/bashly/views/flag/validations.gtx b/lib/bashly/views/flag/validations.gtx index 43b0628a..06665323 100644 --- a/lib/bashly/views/flag/validations.gtx +++ b/lib/bashly/views/flag/validations.gtx @@ -1,4 +1,4 @@ -if validate +if validate? = view_marker if repeatable @@ -6,20 +6,24 @@ if validate > values='' > eval "values=(${args['{{ long }}']})" > for value in "${values[@]}"; do - > validation_output="$(validate_{{ validate }} "$value")" + validate.each do |funcname| + > validation_output="$(validate_{{ funcname }} "$value")" > if [[ -n "$validation_output" ]]; then > printf "{{ strings[:validation_error] }}\n" "{{ usage_string }}" "$validation_output" >&2 > exit 1 > fi > done + end > fi else > if [[ -v args['{{ long }}'] ]]; then - > validation_output="$(validate_{{ validate }} "${args['{{ long }}']:-}")" + validate.each do |funcname| + > validation_output="$(validate_{{ funcname }} "${args['{{ long }}']:-}")" > if [[ -n "${validation_output}" ]]; then > printf "{{ strings[:validation_error] }}\n" "{{ usage_string }}" "$validation_output" >&2 > exit 1 > fi + end > fi > end diff --git a/schemas/bashly.json b/schemas/bashly.json index 0c0160fe..06615873 100644 --- a/schemas/bashly.json +++ b/schemas/bashly.json @@ -76,12 +76,27 @@ "validate": { "title": "validate", "description": "A validation function of the current positional argument\nhttps://bashly.dev/configuration/argument/#validate", - "type": "string", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], "examples": [ "file_exists", "dir_exists", "integer", - "non_empty" + "non_empty", + [ + "file_exists", + "file_writable" + ] ] }, "unique": { @@ -261,12 +276,27 @@ "validate": { "title": "validate", "description": "A validation function of the current flag\nhttps://bashly.dev/configuration/flag/#validate", - "type": "string", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], "examples": [ "file_exists", "dir_exists", "integer", - "non_empty" + "non_empty", + [ + "file_exists", + "file_writable" + ] ] }, "unique": { @@ -337,12 +367,27 @@ "validate": { "title": "validate", "description": "A validation function for the current environment variable\nhttps://bashly.dev/configuration/environment-variable/#validate", - "type": "string", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], "examples": [ "file_exists", "dir_exists", "integer", - "non_empty" + "non_empty", + [ + "file_exists", + "file_writable" + ] ] }, "allowed": { diff --git a/spec/approvals/examples/validations b/spec/approvals/examples/validations index 77d3d534..22966367 100644 --- a/spec/approvals/examples/validations +++ b/spec/approvals/examples/validations @@ -22,6 +22,9 @@ args: + ./validate calc A validation error in FIRST: must be an integer ++ ./validate calc 1 '' +validation error in SECOND: +must not be empty + ./validate calc 1 B validation error in SECOND: must be an integer diff --git a/spec/bashly/commands/preview_spec.rb b/spec/bashly/commands/preview_spec.rb index 6839888f..3246f417 100644 --- a/spec/bashly/commands/preview_spec.rb +++ b/spec/bashly/commands/preview_spec.rb @@ -21,5 +21,3 @@ end end end - - diff --git a/spec/bashly/integration/examples_spec.rb b/spec/bashly/integration/examples_spec.rb index 690bd598..6285fc1a 100644 --- a/spec/bashly/integration/examples_spec.rb +++ b/spec/bashly/integration/examples_spec.rb @@ -24,10 +24,10 @@ # Allow up to a certain string distance from the approval text in CI leeway = ENV['CI'] ? 40 : 0 - # For certain examples, allow some exceptions (replacements) since they + # For certain examples, allow some exceptions (replacements) since they # are too volatile (e.g. line number changes) exceptions = { - 'examples/stacktrace' => [/download:\d+/, 'download:'], + 'examples/stacktrace' => [/download:\d+/, 'download:'], 'examples/render-mandoc' => [/Version 0.1.0.*download\(1\)/, '