Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion examples/validations/src/bashly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/validations/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/bashly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions lib/bashly/config_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions lib/bashly/script/argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Bashly
module Script
class Argument < Base
include Introspection::Validate

class << self
def option_keys
@option_keys ||= %i[
Expand Down
2 changes: 1 addition & 1 deletion lib/bashly/script/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/bashly/script/environment_variable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Bashly
module Script
class EnvironmentVariable < Base
include Introspection::Visibility
include Introspection::Validate

class << self
def option_keys
Expand Down
1 change: 1 addition & 0 deletions lib/bashly/script/flag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Script
class Flag < Base
include Completions::Flag
include Introspection::Visibility
include Introspection::Validate

class << self
def option_keys
Expand Down
2 changes: 1 addition & 1 deletion lib/bashly/script/introspection/environment_variables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions lib/bashly/script/introspection/validate.rb
Original file line number Diff line number Diff line change
@@ -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
10 changes: 7 additions & 3 deletions lib/bashly/views/argument/validations.gtx
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
if validate
if validate?
= view_marker

if repeatable
> if [[ -v args['{{ name }}'] ]]; then
> 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
Expand Down
6 changes: 4 additions & 2 deletions lib/bashly/views/environment_variable/validations.gtx
Original file line number Diff line number Diff line change
@@ -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
10 changes: 7 additions & 3 deletions lib/bashly/views/flag/validations.gtx
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
if validate
if validate?
= view_marker

if repeatable
> if [[ -v args['{{ long }}'] ]]; then
> 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
Expand Down
57 changes: 51 additions & 6 deletions schemas/bashly.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
3 changes: 3 additions & 0 deletions spec/approvals/examples/validations
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions spec/bashly/commands/preview_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,3 @@
end
end
end


4 changes: 2 additions & 2 deletions spec/bashly/integration/examples_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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:<line>'],
'examples/stacktrace' => [/download:\d+/, 'download:<line>'],
'examples/render-mandoc' => [/Version 0.1.0.*download\(1\)/, '<footer>'],
}

Expand Down
7 changes: 7 additions & 0 deletions spec/bashly/script/argument_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

let(:fixture) { :basic_argument }

describe 'composition' do
it 'includes the necessary modules' do
modules = [Script::Introspection::Validate]
expect(described_class.ancestors).to include(*modules)
end
end

describe '#default_string' do
context 'when default is an array' do
let(:fixture) { :default_array }
Expand Down
17 changes: 17 additions & 0 deletions spec/bashly/script/command_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@
let(:fixtures) { load_fixture 'script/commands' }
let(:fixture) { :basic_command }

describe 'composition' do
it 'includes the necessary modules' do
modules = [
Script::Introspection::Arguments,
Script::Introspection::Commands,
Script::Introspection::Dependencies,
Script::Introspection::EnvironmentVariables,
Script::Introspection::Examples,
Script::Introspection::Flags,
Script::Introspection::Variables,
Script::Introspection::Visibility,
Completions::Command,
]
expect(described_class.ancestors).to include(*modules)
end
end

describe '#action_name' do
context 'when it is the root command' do
it 'returns root' do
Expand Down
37 changes: 37 additions & 0 deletions spec/bashly/script/environment_variable_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
describe Script::EnvironmentVariable do
subject do
options = load_fixture('script/env_vars')[fixture]
described_class.new options
end

let(:fixture) { :basic_env_var }

describe 'composition' do
it 'includes the necessary modules' do
modules = [Script::Introspection::Visibility, Script::Introspection::Validate]
expect(described_class.ancestors).to include(*modules)
end
end

describe '#usage_string' do
it 'returns a string suitable to be used as a usage pattern' do
expect(subject.usage_string).to eq 'BUILD_DIR'
end

context 'with extended: true' do
context 'when the flag is optional' do
it 'returns the same string as it does without extended' do
expect(subject.usage_string(extended: true)).to eq subject.usage_string
end
end

context 'when the flag is also required' do
let(:fixture) { :required }

it 'appends (required) to the usage string' do
expect(subject.usage_string(extended: true)).to eq "#{subject.usage_string} (required)"
end
end
end
end
end
Loading