cliqr 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +95 -0
- data/README.md +9 -71
- data/examples/numbers +1 -2
- data/examples/vagrant +0 -3
- data/lib/cliqr.rb +52 -11
- data/lib/cliqr/argument_validation/argument_type_validator.rb +2 -2
- data/lib/cliqr/argument_validation/validator.rb +3 -3
- data/lib/cliqr/{cli → command}/argument_operator.rb +2 -2
- data/lib/cliqr/{cli → command}/argument_operator_context.rb +1 -1
- data/lib/cliqr/{cli/command.rb → command/base_command.rb} +2 -2
- data/lib/cliqr/command/color.rb +174 -0
- data/lib/cliqr/{cli → command}/command_context.rb +68 -20
- data/lib/cliqr/command/shell_banner_builder.rb +17 -0
- data/lib/cliqr/command/shell_command.rb +125 -0
- data/lib/cliqr/command/shell_prompt_builder.rb +26 -0
- data/lib/cliqr/config/action.rb +226 -0
- data/lib/cliqr/config/base.rb +84 -0
- data/lib/cliqr/config/command.rb +137 -0
- data/lib/cliqr/config/dsl.rb +81 -0
- data/lib/cliqr/config/event.rb +43 -0
- data/lib/cliqr/config/event_based.rb +78 -0
- data/lib/cliqr/config/named.rb +55 -0
- data/lib/cliqr/config/option.rb +95 -0
- data/lib/cliqr/config/option_based.rb +130 -0
- data/lib/cliqr/config/shell.rb +87 -0
- data/lib/cliqr/config/validation/validation_set.rb +66 -0
- data/lib/cliqr/config/validation/validator_factory.rb +403 -0
- data/lib/cliqr/config/validation/verifiable.rb +91 -0
- data/lib/cliqr/error.rb +20 -4
- data/lib/cliqr/events/event.rb +56 -0
- data/lib/cliqr/events/event_context.rb +31 -0
- data/lib/cliqr/events/handler.rb +32 -0
- data/lib/cliqr/events/invoker.rb +70 -0
- data/lib/cliqr/{cli → executor}/command_runner_factory.rb +3 -3
- data/lib/cliqr/{cli → executor}/router.rb +4 -4
- data/lib/cliqr/{cli/executor.rb → executor/runner.rb} +25 -10
- data/lib/cliqr/interface.rb +98 -0
- data/lib/cliqr/parser/token_factory.rb +1 -1
- data/lib/cliqr/usage/command_usage_context.rb +94 -0
- data/lib/cliqr/usage/option_usage_context.rb +86 -0
- data/lib/cliqr/usage/templates/partial/action_list.erb +10 -0
- data/lib/cliqr/usage/templates/partial/command_name.erb +3 -0
- data/lib/cliqr/usage/templates/partial/option_list.erb +18 -0
- data/lib/cliqr/usage/templates/partial/usage_info.erb +5 -0
- data/lib/cliqr/usage/templates/usage/cli.erb +4 -0
- data/lib/cliqr/usage/templates/usage/shell.erb +2 -0
- data/lib/cliqr/usage/usage_builder.rb +59 -0
- data/lib/cliqr/util.rb +81 -34
- data/lib/cliqr/version.rb +1 -1
- data/spec/config/action_config_validator_spec.rb +127 -5
- data/spec/config/config_finalize_spec.rb +3 -3
- data/spec/config/config_validator_spec.rb +120 -17
- data/spec/config/option_config_validator_spec.rb +1 -1
- data/spec/dsl/interface_spec.rb +2 -2
- data/spec/dsl/usage_spec.rb +461 -465
- data/spec/executor/action_executor_spec.rb +1 -1
- data/spec/executor/color_executor_spec.rb +125 -0
- data/spec/executor/command_runner_spec.rb +6 -8
- data/spec/executor/event_executor_spec.rb +365 -0
- data/spec/executor/executor_spec.rb +49 -11
- data/spec/executor/help_executor_spec.rb +107 -103
- data/spec/fixtures/action_reader_command.rb +1 -1
- data/spec/fixtures/test_arg_printer_event_handler.rb +9 -0
- data/spec/fixtures/test_color_shell_prompt.rb +13 -0
- data/spec/fixtures/test_empty_event_handler.rb +5 -0
- data/spec/fixtures/test_invoker_event_handler.rb +9 -0
- data/spec/fixtures/test_shell_banner.rb +8 -0
- data/spec/fixtures/test_shell_prompt.rb +13 -0
- data/spec/shell/shell_executor_spec.rb +700 -0
- data/spec/validation/validation_spec.rb +2 -2
- metadata +65 -27
- data/lib/cliqr/cli/config.rb +0 -554
- data/lib/cliqr/cli/interface.rb +0 -107
- data/lib/cliqr/cli/shell_command.rb +0 -69
- data/lib/cliqr/cli/usage_builder.rb +0 -185
- data/lib/cliqr/config_validation/validation_set.rb +0 -48
- data/lib/cliqr/config_validation/validator_factory.rb +0 -319
- data/lib/cliqr/config_validation/verifiable.rb +0 -89
- data/lib/cliqr/dsl.rb +0 -59
- data/spec/executor/shell_executor_spec.rb +0 -233
- data/templates/usage.erb +0 -39
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/config/named'
|
4
|
+
require 'cliqr/command/argument_operator'
|
5
|
+
|
6
|
+
module Cliqr
|
7
|
+
module Config
|
8
|
+
# Config attributes for a command's option
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Option < Cliqr::Config::Named
|
12
|
+
# Optional short name for the option
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
attr_accessor :short
|
16
|
+
validates :short,
|
17
|
+
non_empty_nil_ok_format: /^[a-z0-9A-Z]$/
|
18
|
+
|
19
|
+
# Optional field that restricts values of this option to a certain type
|
20
|
+
#
|
21
|
+
# @return [Symbol] Type of the option
|
22
|
+
attr_accessor :type
|
23
|
+
validates :type,
|
24
|
+
inclusion: [:any, Config::NUMERIC_ARGUMENT_TYPE, Config::BOOLEAN_ARGUMENT_TYPE]
|
25
|
+
|
26
|
+
# Operation to be applied to the option value after validation
|
27
|
+
#
|
28
|
+
# @return [Class<Cliqr::Command::ArgumentOperator>]
|
29
|
+
attr_accessor :operator
|
30
|
+
validates :operator,
|
31
|
+
one_of: [
|
32
|
+
{ extend: Cliqr::Command::ArgumentOperator },
|
33
|
+
{ type_of: Proc }
|
34
|
+
]
|
35
|
+
|
36
|
+
# Default value for this option
|
37
|
+
#
|
38
|
+
# @return [Object]
|
39
|
+
attr_accessor :default
|
40
|
+
|
41
|
+
# Initialize a new config instance for an option with UNSET attribute values
|
42
|
+
def initialize
|
43
|
+
super
|
44
|
+
|
45
|
+
@short = UNSET
|
46
|
+
@type = UNSET
|
47
|
+
@operator = UNSET
|
48
|
+
@default = UNSET
|
49
|
+
end
|
50
|
+
|
51
|
+
# Finalize option's config by adding default values for unset values
|
52
|
+
#
|
53
|
+
# @return [Cliqr::Config::Option]
|
54
|
+
def finalize
|
55
|
+
super
|
56
|
+
|
57
|
+
@short = Config.get_if_unset(@short, nil)
|
58
|
+
@type = Config.get_if_unset(@type, ANY_ARGUMENT_TYPE)
|
59
|
+
@operator = Util.ensure_instance(
|
60
|
+
Config.get_if_unset(@operator, Cliqr::Command::ArgumentOperator.for_type(@type)))
|
61
|
+
@default = Config.get_if_unset(@default, ARGUMENT_DEFAULTS[@type])
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# Check if a option's short name is defined
|
67
|
+
#
|
68
|
+
# @return [Boolean] <tt>true</tt> if options' short name is not null neither empty
|
69
|
+
def short?
|
70
|
+
!(@short.nil? || @short.empty?)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check if a option's type is defined
|
74
|
+
#
|
75
|
+
# @return [Boolean] <tt>true</tt> if options' type is not nil and not equal to <tt>:any</tt>
|
76
|
+
def type?
|
77
|
+
!@type.nil? && @type != :any
|
78
|
+
end
|
79
|
+
|
80
|
+
# Check if a option is of boolean type
|
81
|
+
#
|
82
|
+
# @return [Boolean] <tt>true</tt> is the option is of type <tt>:boolean</tt>
|
83
|
+
def boolean?
|
84
|
+
@type == :boolean
|
85
|
+
end
|
86
|
+
|
87
|
+
# Check if a default value setting is defined
|
88
|
+
#
|
89
|
+
# @return [Boolean]
|
90
|
+
def default?
|
91
|
+
!@default.nil?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/util'
|
4
|
+
require 'cliqr/command/base_command'
|
5
|
+
require 'cliqr/config/base'
|
6
|
+
require 'cliqr/config/option'
|
7
|
+
|
8
|
+
module Cliqr
|
9
|
+
module Config
|
10
|
+
# Configuration setting for an option based setting
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
class OptionBased < Cliqr::Config::Named
|
14
|
+
# Array of options applied to the base command
|
15
|
+
#
|
16
|
+
# @return [Array<OptionConfig>]
|
17
|
+
attr_accessor :options
|
18
|
+
validates :options,
|
19
|
+
hash: true
|
20
|
+
|
21
|
+
# New config instance with all attributes set as UNSET
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
|
25
|
+
@options = {}
|
26
|
+
@short_option_index = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Finalize config by adding default values for unset values
|
30
|
+
#
|
31
|
+
# @return [Cliqr::Config::OptionBased]
|
32
|
+
def finalize
|
33
|
+
super
|
34
|
+
|
35
|
+
if options? && @short_option_index.empty?
|
36
|
+
@options.values.each do |option|
|
37
|
+
@short_option_index[option.short.to_s] = option if option.short?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set value for a config option
|
45
|
+
#
|
46
|
+
# @param [Symbol] name Name of the config parameter
|
47
|
+
# @param [Object] value Value for the config parameter
|
48
|
+
# @param [Proc] block Function which populates configuration for a sub-attribute
|
49
|
+
#
|
50
|
+
# @return [Cliqr::Config::Option] Newly added option config
|
51
|
+
def set_config(name, value, *args, &block)
|
52
|
+
case name
|
53
|
+
when :option
|
54
|
+
handle_option(value, &block) # value is the long name for the option
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check if options are set
|
61
|
+
def options?
|
62
|
+
return false if @options.nil?
|
63
|
+
!@options.empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
# Check if particular option is set
|
67
|
+
#
|
68
|
+
# @param [String] name Name of the option to check
|
69
|
+
def option?(name)
|
70
|
+
@options.key?(name.to_s) || @short_option_index.key?(name.to_s)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get value of a option
|
74
|
+
#
|
75
|
+
# @param [String] name Name of the option
|
76
|
+
#
|
77
|
+
# @return [String] value for the option
|
78
|
+
def option(name)
|
79
|
+
if @options.key?(name.to_s)
|
80
|
+
@options[name.to_s]
|
81
|
+
else
|
82
|
+
@short_option_index[name.to_s]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Handle configuration for a new option
|
89
|
+
#
|
90
|
+
# @param [Symbol] name Long name of the option
|
91
|
+
# @param [Proc] block Populate the option's config in this function block
|
92
|
+
#
|
93
|
+
# @return [Cliqr::Config::Option] Newly created option's config
|
94
|
+
def handle_option(name, &block)
|
95
|
+
option_config = Option.build(&block)
|
96
|
+
option_config.name = name
|
97
|
+
add_option(option_config)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add a new option for the command
|
101
|
+
#
|
102
|
+
# @return [Cliqr::Config::Option] Newly added option's config
|
103
|
+
def add_option(option_config)
|
104
|
+
validate_option_name(option_config)
|
105
|
+
|
106
|
+
@options[option_config.name.to_s] = option_config
|
107
|
+
@short_option_index[option_config.short.to_s] = option_config if option_config.short?
|
108
|
+
|
109
|
+
option_config
|
110
|
+
end
|
111
|
+
|
112
|
+
# Make sure that the option's name is unique
|
113
|
+
#
|
114
|
+
# @param [Cliqr::Config::Option] option_config Config for this particular option
|
115
|
+
#
|
116
|
+
# @return [Cliqr::Config::Option] Validated OptionConfig instance
|
117
|
+
def validate_option_name(option_config)
|
118
|
+
fail Cliqr::Error::DuplicateOptions,
|
119
|
+
"multiple options with long name \"#{option_config.name}\"" \
|
120
|
+
if option?(option_config.name)
|
121
|
+
|
122
|
+
fail Cliqr::Error::DuplicateOptions,
|
123
|
+
"multiple options with short name \"#{option_config.short}\"" \
|
124
|
+
if option?(option_config.short)
|
125
|
+
|
126
|
+
option_config
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/command/shell_prompt_builder'
|
4
|
+
require 'cliqr/command/shell_banner_builder'
|
5
|
+
|
6
|
+
module Cliqr
|
7
|
+
module Config
|
8
|
+
# Config attributes for shell
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Shell < Cliqr::Config::OptionBased
|
12
|
+
# Enable or disable the shell action
|
13
|
+
#
|
14
|
+
# @return [Symbol]
|
15
|
+
attr_accessor :enabled
|
16
|
+
validates :enabled,
|
17
|
+
inclusion: [true, false]
|
18
|
+
|
19
|
+
# Prompt for the shell
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
# @return [Proc]
|
23
|
+
attr_accessor :prompt
|
24
|
+
validates :prompt,
|
25
|
+
one_of: [
|
26
|
+
{ extend: Command::ShellPromptBuilder },
|
27
|
+
{ type_of: Proc },
|
28
|
+
{ type_of: String }
|
29
|
+
]
|
30
|
+
|
31
|
+
# Banner that is displayed when shell starts
|
32
|
+
#
|
33
|
+
# @return [String]
|
34
|
+
attr_accessor :banner
|
35
|
+
validates :banner,
|
36
|
+
one_of: [
|
37
|
+
{ extend: Command::ShellBannerBuilder },
|
38
|
+
{ type_of: Proc },
|
39
|
+
{ type_of: String }
|
40
|
+
]
|
41
|
+
|
42
|
+
# Initialize a new config instance for an option with UNSET attribute values
|
43
|
+
def initialize
|
44
|
+
super
|
45
|
+
|
46
|
+
@enabled = UNSET
|
47
|
+
@prompt = UNSET
|
48
|
+
@banner = UNSET
|
49
|
+
end
|
50
|
+
|
51
|
+
# Finalize shell's config by adding default values for unset values
|
52
|
+
#
|
53
|
+
# @return [Cliqr::Config::Option]
|
54
|
+
def finalize
|
55
|
+
super
|
56
|
+
|
57
|
+
case @enabled
|
58
|
+
when Cliqr::Config::ENABLE_CONFIG
|
59
|
+
@enabled = true
|
60
|
+
when Cliqr::Config::DISABLE_CONFIG
|
61
|
+
@enabled = false
|
62
|
+
when UNSET
|
63
|
+
@enabled = true
|
64
|
+
end
|
65
|
+
@prompt = Config.get_if_unset(@prompt, Cliqr::Command::ShellPromptBuilder.new)
|
66
|
+
@banner = Config.get_if_unset(@banner, Cliqr::Command::ShellBannerBuilder.new)
|
67
|
+
|
68
|
+
# set default name for the shell action
|
69
|
+
@name = 'shell' if @name.is_a?(String) && @name.empty?
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Check if shell is enabled
|
75
|
+
def enabled?
|
76
|
+
@enabled
|
77
|
+
end
|
78
|
+
|
79
|
+
# Disable colors in shell
|
80
|
+
#
|
81
|
+
# @return [Cliqr::Command::Color]
|
82
|
+
def disable_color
|
83
|
+
@prompt.disable_color if @prompt.respond_to?(:disable_color)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/config/validation/validator_factory'
|
4
|
+
|
5
|
+
module Cliqr
|
6
|
+
module Config
|
7
|
+
module Validation
|
8
|
+
# A collection of configured validators
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class ValidationSet
|
12
|
+
# Initialize a new instance of validation set
|
13
|
+
def initialize
|
14
|
+
@set = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add a new validator
|
18
|
+
#
|
19
|
+
# @param [Symbol] name Name of the validator
|
20
|
+
# @param [Object] options Configuration option to initialize the validator
|
21
|
+
#
|
22
|
+
# @return [Hash] A map of all validators
|
23
|
+
def add(name, options)
|
24
|
+
@set[name] = \
|
25
|
+
Hash[options.map { |type, config| [type, ValidatorFactory.get(type, config)] }]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Merge validations form another set
|
29
|
+
#
|
30
|
+
# @param [Cliqr::Config::Validation::ValidationSet] other
|
31
|
+
#
|
32
|
+
# @return [Cliqr::Config::Validation::ValidationSet]
|
33
|
+
def merge(other)
|
34
|
+
other.each { |name, validations| @set[name] = validations }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Iterate over each type of validators
|
38
|
+
#
|
39
|
+
# @return [Object]
|
40
|
+
def each_key(&block)
|
41
|
+
@set.each_key(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Iterate over each validators
|
45
|
+
#
|
46
|
+
# @return [Cliqr::Config::Validation::ValidatorFactory::Validator]
|
47
|
+
def each(&block)
|
48
|
+
@set.each(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Run the validators for a attribute against its value
|
52
|
+
#
|
53
|
+
# @param [Symbol] attribute Name of the attribute
|
54
|
+
# @param [Object] value Value of the attribute
|
55
|
+
# @param [Cliqr::ValidationErrors] errors A collection wrapper for all validation errors
|
56
|
+
#
|
57
|
+
# @return [Array] All validators that ran for the attribute against the value
|
58
|
+
def validate(attribute, value, errors)
|
59
|
+
@set[attribute].values.each do |validator|
|
60
|
+
validator.validate(attribute, value, errors)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,403 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cliqr
|
4
|
+
module Config
|
5
|
+
module Validation
|
6
|
+
# A factory class to retrieve a attribute validator based on the configuration type
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
module ValidatorFactory
|
10
|
+
# Does not validates anything, used by default if a unknown validator type is used
|
11
|
+
class Validator
|
12
|
+
# Run this validator against an attribute value
|
13
|
+
#
|
14
|
+
# @param [String] name Name of the attribute
|
15
|
+
# @param [Object] value Value of the attribute
|
16
|
+
# @param [Cliqr::ValidationErrors] errors Errors after validation finished
|
17
|
+
#
|
18
|
+
# @return [Boolean] <tt>true</tt> if validation passed
|
19
|
+
def validate(name, value, errors)
|
20
|
+
validation_sequence(name, value, errors)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Recursively validate using parent validator then apply itself
|
26
|
+
#
|
27
|
+
# @return [Boolean] <tt>true</tt> if validation passed
|
28
|
+
def validation_sequence(name, value, errors)
|
29
|
+
return false unless validate_parent(name, value, errors)
|
30
|
+
|
31
|
+
local_errors = ValidationErrors.new
|
32
|
+
@class_stack.last.instance_method(:do_validate).bind(self)
|
33
|
+
.call(name, value, local_errors)
|
34
|
+
errors.merge(local_errors)
|
35
|
+
local_errors.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Validate attribute using the parent validator's logic
|
39
|
+
#
|
40
|
+
# @return [Boolean] <tt>true</tt> if parent class' validation passed
|
41
|
+
def validate_parent(name, value, errors)
|
42
|
+
@class_stack = (@class_stack || [self.class])
|
43
|
+
parent_class = (@class_stack.last || self.class).superclass
|
44
|
+
@class_stack.push(parent_class)
|
45
|
+
begin
|
46
|
+
return parent_class.instance_method(:validate).bind(self) \
|
47
|
+
.call(name, value, errors) if parent_class < Validator
|
48
|
+
true
|
49
|
+
ensure
|
50
|
+
@class_stack.pop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# This is used in case a unknown validation is used
|
56
|
+
class NOOPValidator < Validator
|
57
|
+
# Initialize a new no-op validator
|
58
|
+
def initialize(type)
|
59
|
+
@type = type
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Fails if invoked
|
65
|
+
#
|
66
|
+
# @return [Object]
|
67
|
+
def do_validate(_name, _value, _errors)
|
68
|
+
fail Cliqr::Error::UnknownValidatorType, "unknown validation type: '#{@type}'"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Verifies that an attribute's value is non-nil
|
73
|
+
class NonNilValidator < Validator
|
74
|
+
# Initialize a new non-nil validator
|
75
|
+
def initialize(enabled)
|
76
|
+
@enabled = enabled
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
# Validate presence of an attribute's value
|
82
|
+
#
|
83
|
+
# @return [Cliqr::ValidationErrors] Errors after the validation has finished
|
84
|
+
def do_validate(name, value, errors)
|
85
|
+
errors.add("'#{name}' cannot be nil") if @enabled && value.nil?
|
86
|
+
errors
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Verifies that an attribute's value is non-empty
|
91
|
+
class NonEmptyValidator < NonNilValidator
|
92
|
+
# Create a new non-empty validator
|
93
|
+
def initialize(enabled)
|
94
|
+
super(enabled)
|
95
|
+
@enabled = enabled
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
# Validate that a attribute's value is not empty
|
101
|
+
#
|
102
|
+
# @return [Cliqr::ValidationErrors] Errors after the validation has finished
|
103
|
+
def do_validate(name, value, errors)
|
104
|
+
errors.add("'#{name}' cannot be empty") \
|
105
|
+
if @enabled && value.respond_to?(:empty?) && value.empty?
|
106
|
+
errors
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Validates the value of an attribute against a regex pattern
|
111
|
+
class FormatValidator < NonNilValidator
|
112
|
+
# Initialize a new format validator
|
113
|
+
#
|
114
|
+
# @param [Regex] format Format of the value to validate required
|
115
|
+
def initialize(format)
|
116
|
+
super(true)
|
117
|
+
@format = format
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
|
122
|
+
# Run the format validator to check attribute value's format
|
123
|
+
#
|
124
|
+
# @return [Boolean] <tt>true</tt> if there were any errors during validation
|
125
|
+
def do_validate(name, value, errors)
|
126
|
+
errors.add("value for '#{name}' must match /#{@format.source}/; " \
|
127
|
+
"actual: #{value.inspect}") \
|
128
|
+
if !value.nil? && !match?(value)
|
129
|
+
errors
|
130
|
+
end
|
131
|
+
|
132
|
+
# Check if the value matches a particular format
|
133
|
+
def match?(value)
|
134
|
+
return false unless value.is_a?(String) || value.is_a?(Symbol)
|
135
|
+
!@format.match(value).nil?
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Validates that a value matches a pattern and it is not empty
|
140
|
+
class NonEmptyFormatValidator < NonEmptyValidator
|
141
|
+
# Initialize a new non-empty format validator
|
142
|
+
#
|
143
|
+
# @param [Regex] format Format of the value to validate required
|
144
|
+
def initialize(format)
|
145
|
+
super(true)
|
146
|
+
@format = format
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
# Run the format validator along with non-empty check
|
152
|
+
#
|
153
|
+
# @return [Boolean] <tt>true</tt> if there were any errors during validation
|
154
|
+
def do_validate(name, value, errors)
|
155
|
+
FormatValidator.new(@format).validate(name, value, errors)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Validates that a value matches a pattern and it is not empty; nil value allowed
|
160
|
+
class NonEmptyNilOkFormatValidator < Validator
|
161
|
+
# Initialize a new non-empty-nil-ok format validator
|
162
|
+
#
|
163
|
+
# @param [Regex] format Format of the value to validate required
|
164
|
+
def initialize(format)
|
165
|
+
@format = format
|
166
|
+
end
|
167
|
+
|
168
|
+
protected
|
169
|
+
|
170
|
+
# Run the validator
|
171
|
+
#
|
172
|
+
# @return [Boolean] <tt>true</tt> if there were any errors during validation
|
173
|
+
def do_validate(name, value, errors)
|
174
|
+
unless value.nil?
|
175
|
+
local_errors = ValidationErrors.new
|
176
|
+
local_errors.add("'#{name}' cannot be empty") \
|
177
|
+
if value.respond_to?(:empty?) && value.empty?
|
178
|
+
FormatValidator.new(@format).validate(name, value, local_errors) \
|
179
|
+
if local_errors.empty?
|
180
|
+
errors.merge(local_errors)
|
181
|
+
end
|
182
|
+
errors
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Validates that the value of an attribute is of a type that extends from another
|
187
|
+
class TypeHierarchyValidator < NonNilValidator
|
188
|
+
# Create a new instance of type hierarchy validator
|
189
|
+
#
|
190
|
+
# @param [Class] super_type Class reference that the validated variable must extend from
|
191
|
+
def initialize(super_type)
|
192
|
+
super(true)
|
193
|
+
@super_type = super_type
|
194
|
+
end
|
195
|
+
|
196
|
+
protected
|
197
|
+
|
198
|
+
# Check if the type of <tt>value</tt> is extensible from a <tt>super_type</tt>
|
199
|
+
#
|
200
|
+
# @return [Boolean] <tt>true</tt> if there were any errors during validation
|
201
|
+
def do_validate(name, value, errors)
|
202
|
+
begin
|
203
|
+
return if value.is_a?(@super_type) || \
|
204
|
+
(value.respond_to?(:<) ? value < @super_type : value.is_a?(@super_type))
|
205
|
+
rescue ArgumentError
|
206
|
+
# the comparison failed which indicates that the validation also failed
|
207
|
+
end
|
208
|
+
errors.add("#{name} of type '#{value.class.name}' " \
|
209
|
+
"does not extend from '#{@super_type}'")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Validates each element inside a collection
|
214
|
+
class CollectionValidator < TypeHierarchyValidator
|
215
|
+
# Create a new collection validator
|
216
|
+
def initialize(_config, type = nil)
|
217
|
+
super(type || Array)
|
218
|
+
end
|
219
|
+
|
220
|
+
protected
|
221
|
+
|
222
|
+
# Validate each element inside a collection and prepend index to error
|
223
|
+
#
|
224
|
+
# @return [Boolean] <tt>true</tt> if there were any errors during validation
|
225
|
+
def do_validate(name, values, errors)
|
226
|
+
valid = true
|
227
|
+
iterator(values) do |value, key|
|
228
|
+
valid = false unless value.valid?
|
229
|
+
value.errors.each do |error|
|
230
|
+
if value.name.nil? || value.name.empty?
|
231
|
+
errors.add("#{name}[#{key}] - #{error}")
|
232
|
+
else
|
233
|
+
errors.add("#{name.to_s.gsub(/s$/, '')} \"#{value.name}\" - #{error}")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
valid
|
238
|
+
end
|
239
|
+
|
240
|
+
# Iterator for each item in the array
|
241
|
+
#
|
242
|
+
# @return [Array]
|
243
|
+
def iterator(array, &block)
|
244
|
+
array.each_with_index do |value, index|
|
245
|
+
block.call(value, index + 1) unless value.skip_validation?
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Validates each element inside a hash map
|
251
|
+
class HashValidator < CollectionValidator
|
252
|
+
# Create a new hash validator
|
253
|
+
def initialize(config)
|
254
|
+
super(config, Hash)
|
255
|
+
end
|
256
|
+
|
257
|
+
protected
|
258
|
+
|
259
|
+
# Validate each element inside a hash and prepend key to error
|
260
|
+
#
|
261
|
+
# @return [Boolean] <tt>true</tt> if there were any errors during validation
|
262
|
+
def do_validate(_name, _values, _errors)
|
263
|
+
true
|
264
|
+
end
|
265
|
+
|
266
|
+
# Iterate over each key value pair in the hash
|
267
|
+
#
|
268
|
+
# @return [Hash]
|
269
|
+
def iterator(hash, &block)
|
270
|
+
hash.each_with_index do |(key, value), index|
|
271
|
+
block.call(value, key.empty? ? (index + 1) : key)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Validate that a attribute value is included in a predefined set
|
277
|
+
class InclusionValidator < NonNilValidator
|
278
|
+
# Create a new inclusion validator
|
279
|
+
#
|
280
|
+
# @param [Array<Symbol>] allowed_values A set of allowed values
|
281
|
+
def initialize(allowed_values)
|
282
|
+
@allowed_values = allowed_values
|
283
|
+
end
|
284
|
+
|
285
|
+
protected
|
286
|
+
|
287
|
+
# Validate that a value is included in <tt>allowed_values</tt>
|
288
|
+
#
|
289
|
+
# @return [Nothing]
|
290
|
+
def do_validate(_name, value, errors)
|
291
|
+
errors.add("invalid type '#{value}'") unless @allowed_values.include?(value)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Validate the type of a attribute's value
|
296
|
+
class TypeOfValidator < NonNilValidator
|
297
|
+
# Create a new <tt>:type_of</tt> validator
|
298
|
+
def initialize(type)
|
299
|
+
super(true)
|
300
|
+
@type = type
|
301
|
+
end
|
302
|
+
|
303
|
+
protected
|
304
|
+
|
305
|
+
# Run the <tt>:type_of</tt> validation check
|
306
|
+
#
|
307
|
+
# @return [Nothing]
|
308
|
+
def do_validate(name, value, errors)
|
309
|
+
errors.add("#{name} should be a '#{@type}' not '#{value.class}'") \
|
310
|
+
unless matches?(value)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Check if a value matches a type
|
314
|
+
def matches?(value)
|
315
|
+
begin
|
316
|
+
return true if value.class == @type
|
317
|
+
return value < @type if value.respond_to?(:<)
|
318
|
+
rescue ArgumentError
|
319
|
+
# nothing to do here just return false below
|
320
|
+
end
|
321
|
+
false
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Run multiple validators on a value to assert at-least one of them passes
|
326
|
+
class OneOfValidator < Validator
|
327
|
+
# Create a new <tt>:one_of</tt> validator
|
328
|
+
def initialize(validator_configs)
|
329
|
+
@validators = []
|
330
|
+
validator_configs.each do |config|
|
331
|
+
@validators.push(ValidatorFactory.get(config.first[0], config.first[1]))
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
protected
|
336
|
+
|
337
|
+
# Run each validator one by one until one passes
|
338
|
+
#
|
339
|
+
# @return [Nothing]
|
340
|
+
def do_validate(name, value, errors)
|
341
|
+
local_errors = ValidationErrors.new
|
342
|
+
passing_validator = @validators.find do |validator|
|
343
|
+
validator_errors = ValidationErrors.new.tap do |temp_errors|
|
344
|
+
validator.validate(name, value, temp_errors)
|
345
|
+
local_errors.merge(temp_errors)
|
346
|
+
end
|
347
|
+
validator_errors.empty?
|
348
|
+
end
|
349
|
+
errors.add("invalid value for #{name}; fix one of - [#{local_errors}]") \
|
350
|
+
if passing_validator.nil?
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Validates child element
|
355
|
+
class ChildValidator < Validator
|
356
|
+
# Create a new child validator
|
357
|
+
def initialize(_config)
|
358
|
+
end
|
359
|
+
|
360
|
+
protected
|
361
|
+
|
362
|
+
# Validate the child element
|
363
|
+
#
|
364
|
+
# @return [Boolean] <tt>true</tt> if there were any errors during validation
|
365
|
+
def do_validate(name, value, errors)
|
366
|
+
return true if value.valid?
|
367
|
+
value.errors.each do |error|
|
368
|
+
errors.add("#{name} - #{error}")
|
369
|
+
end
|
370
|
+
false
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
# A hash of validator type id to validator class
|
375
|
+
VALIDATORS = {
|
376
|
+
:non_empty => NonEmptyValidator,
|
377
|
+
:non_empty_format => NonEmptyFormatValidator,
|
378
|
+
:non_empty_nil_ok_format => NonEmptyNilOkFormatValidator,
|
379
|
+
:format => FormatValidator,
|
380
|
+
:extend => TypeHierarchyValidator,
|
381
|
+
:collection => CollectionValidator,
|
382
|
+
:hash => HashValidator,
|
383
|
+
:inclusion => InclusionValidator,
|
384
|
+
:one_of => OneOfValidator,
|
385
|
+
:type_of => TypeOfValidator,
|
386
|
+
:child => ChildValidator
|
387
|
+
}
|
388
|
+
|
389
|
+
# Get a new validator based on the type and config param
|
390
|
+
#
|
391
|
+
# @return [Cliqr::Validation::ValidatorFactory::Validator]
|
392
|
+
def self.get(validator_type, config)
|
393
|
+
validator_class = VALIDATORS[validator_type]
|
394
|
+
if validator_class.nil?
|
395
|
+
NOOPValidator.new(validator_type)
|
396
|
+
else
|
397
|
+
validator_class.new(config)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|