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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +95 -0
  3. data/README.md +9 -71
  4. data/examples/numbers +1 -2
  5. data/examples/vagrant +0 -3
  6. data/lib/cliqr.rb +52 -11
  7. data/lib/cliqr/argument_validation/argument_type_validator.rb +2 -2
  8. data/lib/cliqr/argument_validation/validator.rb +3 -3
  9. data/lib/cliqr/{cli → command}/argument_operator.rb +2 -2
  10. data/lib/cliqr/{cli → command}/argument_operator_context.rb +1 -1
  11. data/lib/cliqr/{cli/command.rb → command/base_command.rb} +2 -2
  12. data/lib/cliqr/command/color.rb +174 -0
  13. data/lib/cliqr/{cli → command}/command_context.rb +68 -20
  14. data/lib/cliqr/command/shell_banner_builder.rb +17 -0
  15. data/lib/cliqr/command/shell_command.rb +125 -0
  16. data/lib/cliqr/command/shell_prompt_builder.rb +26 -0
  17. data/lib/cliqr/config/action.rb +226 -0
  18. data/lib/cliqr/config/base.rb +84 -0
  19. data/lib/cliqr/config/command.rb +137 -0
  20. data/lib/cliqr/config/dsl.rb +81 -0
  21. data/lib/cliqr/config/event.rb +43 -0
  22. data/lib/cliqr/config/event_based.rb +78 -0
  23. data/lib/cliqr/config/named.rb +55 -0
  24. data/lib/cliqr/config/option.rb +95 -0
  25. data/lib/cliqr/config/option_based.rb +130 -0
  26. data/lib/cliqr/config/shell.rb +87 -0
  27. data/lib/cliqr/config/validation/validation_set.rb +66 -0
  28. data/lib/cliqr/config/validation/validator_factory.rb +403 -0
  29. data/lib/cliqr/config/validation/verifiable.rb +91 -0
  30. data/lib/cliqr/error.rb +20 -4
  31. data/lib/cliqr/events/event.rb +56 -0
  32. data/lib/cliqr/events/event_context.rb +31 -0
  33. data/lib/cliqr/events/handler.rb +32 -0
  34. data/lib/cliqr/events/invoker.rb +70 -0
  35. data/lib/cliqr/{cli → executor}/command_runner_factory.rb +3 -3
  36. data/lib/cliqr/{cli → executor}/router.rb +4 -4
  37. data/lib/cliqr/{cli/executor.rb → executor/runner.rb} +25 -10
  38. data/lib/cliqr/interface.rb +98 -0
  39. data/lib/cliqr/parser/token_factory.rb +1 -1
  40. data/lib/cliqr/usage/command_usage_context.rb +94 -0
  41. data/lib/cliqr/usage/option_usage_context.rb +86 -0
  42. data/lib/cliqr/usage/templates/partial/action_list.erb +10 -0
  43. data/lib/cliqr/usage/templates/partial/command_name.erb +3 -0
  44. data/lib/cliqr/usage/templates/partial/option_list.erb +18 -0
  45. data/lib/cliqr/usage/templates/partial/usage_info.erb +5 -0
  46. data/lib/cliqr/usage/templates/usage/cli.erb +4 -0
  47. data/lib/cliqr/usage/templates/usage/shell.erb +2 -0
  48. data/lib/cliqr/usage/usage_builder.rb +59 -0
  49. data/lib/cliqr/util.rb +81 -34
  50. data/lib/cliqr/version.rb +1 -1
  51. data/spec/config/action_config_validator_spec.rb +127 -5
  52. data/spec/config/config_finalize_spec.rb +3 -3
  53. data/spec/config/config_validator_spec.rb +120 -17
  54. data/spec/config/option_config_validator_spec.rb +1 -1
  55. data/spec/dsl/interface_spec.rb +2 -2
  56. data/spec/dsl/usage_spec.rb +461 -465
  57. data/spec/executor/action_executor_spec.rb +1 -1
  58. data/spec/executor/color_executor_spec.rb +125 -0
  59. data/spec/executor/command_runner_spec.rb +6 -8
  60. data/spec/executor/event_executor_spec.rb +365 -0
  61. data/spec/executor/executor_spec.rb +49 -11
  62. data/spec/executor/help_executor_spec.rb +107 -103
  63. data/spec/fixtures/action_reader_command.rb +1 -1
  64. data/spec/fixtures/test_arg_printer_event_handler.rb +9 -0
  65. data/spec/fixtures/test_color_shell_prompt.rb +13 -0
  66. data/spec/fixtures/test_empty_event_handler.rb +5 -0
  67. data/spec/fixtures/test_invoker_event_handler.rb +9 -0
  68. data/spec/fixtures/test_shell_banner.rb +8 -0
  69. data/spec/fixtures/test_shell_prompt.rb +13 -0
  70. data/spec/shell/shell_executor_spec.rb +700 -0
  71. data/spec/validation/validation_spec.rb +2 -2
  72. metadata +65 -27
  73. data/lib/cliqr/cli/config.rb +0 -554
  74. data/lib/cliqr/cli/interface.rb +0 -107
  75. data/lib/cliqr/cli/shell_command.rb +0 -69
  76. data/lib/cliqr/cli/usage_builder.rb +0 -185
  77. data/lib/cliqr/config_validation/validation_set.rb +0 -48
  78. data/lib/cliqr/config_validation/validator_factory.rb +0 -319
  79. data/lib/cliqr/config_validation/verifiable.rb +0 -89
  80. data/lib/cliqr/dsl.rb +0 -59
  81. data/spec/executor/shell_executor_spec.rb +0 -233
  82. data/templates/usage.erb +0 -39
@@ -1,107 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'cliqr/error'
4
- require 'cliqr/cli/executor'
5
- require 'cliqr/cli/usage_builder'
6
-
7
- module Cliqr
8
- # Definition and builder for command line interface
9
- module CLI
10
- # Exit code hash map
11
- EXIT_CODE = {
12
- success: 0,
13
- 'Cliqr::Error::CommandRuntimeError'.to_sym => 1,
14
- 'Cliqr::Error::IllegalArgumentError'.to_sym => 2
15
- }
16
-
17
- # A CLI interface instance which is the entry point for all CLI commands.
18
- #
19
- # @api private
20
- class Interface
21
- # Command line interface configuration
22
- #
23
- # @return [Cliqr::CLI::Config]
24
- attr_accessor :config
25
-
26
- # Create a new interface instance with a config
27
- #
28
- # @param [Cliqr::CLI::Config] config Config used to create this interface
29
- def initialize(config)
30
- @config = config
31
- @executor = Executor.new(config)
32
- end
33
-
34
- # Get usage information of this command line interface instance
35
- #
36
- # @return [String] Defines usage of this interface
37
- def usage
38
- UsageBuilder.build(config)
39
- end
40
-
41
- # Execute a command
42
- #
43
- # @param [Array<String>] args Arguments that will be used to execute the command
44
- # @param [Hash] options Options for command execution
45
- #
46
- # @return [Integer] Exit code of the command execution
47
- def execute(args = [], **options)
48
- execute_internal(args, options)
49
- Cliqr::CLI::EXIT_CODE[:success]
50
- rescue Cliqr::Error::CliqrError => e
51
- puts e.message
52
- Cliqr::CLI::EXIT_CODE[e.class.to_s.to_sym]
53
- end
54
-
55
- # Executes a command without handling error conditions
56
- #
57
- # @return [Integer] Exit code
58
- def execute_internal(args = [], **options)
59
- options = {
60
- :output => :default,
61
- :environment => :bash
62
- }.merge(options)
63
- @executor.execute(args, options)
64
- end
65
-
66
- # Invoke the builder method for [Cliqr::CLI::Interface]
67
- #
68
- # @param [Cliqr::CLI::Config] config Instance of the command line config
69
- #
70
- # @return [Cliqr::CLI::Interface]
71
- def self.build(config)
72
- InterfaceBuilder.new(config).build
73
- end
74
- end
75
-
76
- private
77
-
78
- # Builder for [Cliqr::CLI::Interface]
79
- #
80
- # @api private
81
- class InterfaceBuilder
82
- # Start building a command line interface
83
- #
84
- # @param [Cliqr::CLI::Config] config the configuration options for the
85
- # interface (validated using CLI::Validator)
86
- #
87
- # @return [Cliqr::CLI::ConfigBuilder]
88
- def initialize(config)
89
- @config = config
90
- end
91
-
92
- # Validate and build a cli interface based on the configuration options
93
- #
94
- # @return [Cliqr::CLI::Interface]
95
- #
96
- # @throws [Cliqr::Error::ConfigNotFound] if a config is <tt>nil</tt>
97
- # @throws [Cliqr::Error::ValidationError] if the validation for config fails
98
- def build
99
- fail Cliqr::Error::ConfigNotFound, 'a valid config should be defined' if @config.nil?
100
- fail Cliqr::Error::ValidationError, \
101
- "invalid Cliqr interface configuration - [#{@config.errors}]" unless @config.valid?
102
-
103
- Interface.new(@config)
104
- end
105
- end
106
- end
107
- end
@@ -1,69 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'cliqr/cli/command'
4
-
5
- module Cliqr
6
- # @api private
7
- module CLI
8
- # The default command executed to run a shell action
9
- #
10
- # @api private
11
- class ShellCommand < Cliqr::CLI::Command
12
- # Start a shell in the context of some other command
13
- #
14
- # @return [Integer] Exit code
15
- def execute(context)
16
- fail(Cliqr::Error::IllegalCommandError,
17
- 'Cannot run another shell within an already running shell') unless context.bash?
18
-
19
- base_command = context.command[0...(context.command.rindex('shell'))].strip
20
- puts "Starting shell for command \"#{base_command}\""
21
- exit_code = ShellRunner.new(base_command, context).run
22
- puts "shell exited with code #{exit_code}"
23
- exit_code
24
- end
25
- end
26
-
27
- private
28
-
29
- # The runner for shell command
30
- class ShellRunner
31
- # Create the runner instance
32
- def initialize(base_command, context)
33
- @base_command = base_command
34
- @context = context
35
- end
36
-
37
- # Start shell
38
- #
39
- # @return [Integer] Exit code
40
- def run
41
- loop do
42
- command = prompt("#{@base_command} > ")
43
- execute(command) unless command == 'exit'
44
- break if command == 'exit'
45
- end
46
- 0
47
- end
48
-
49
- private
50
-
51
- # Execute a shell command
52
- #
53
- # @return [Integer] Exit code of the command executed
54
- def execute(command)
55
- @context.forward("#{@base_command} #{command}", :environment => :cliqr_shell)
56
- rescue StandardError => e
57
- puts e.message
58
- end
59
-
60
- # Show a prompt and ask for input
61
- #
62
- # @return [String]
63
- def prompt(prefix = '')
64
- print prefix
65
- $stdin.gets.chomp
66
- end
67
- end
68
- end
69
- end
@@ -1,185 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'cliqr/error'
4
- require 'cliqr/cli/executor'
5
- require 'erb'
6
-
7
- module Cliqr
8
- module CLI
9
- # Builds the usage information based on the configuration settings
10
- #
11
- # @api private
12
- class UsageBuilder
13
- # Build the usage information
14
- #
15
- # @param [Cliqr::CLI::Config] config Configuration of the command line interface
16
- #
17
- # @return [String]
18
- def self.build(config)
19
- template_file_path = File.expand_path('../../../../templates/usage.erb', __FILE__)
20
- template = ERB.new(File.new(template_file_path).read, nil, '%')
21
- usage_context = CommandUsageContext.new(config)
22
- result = template.result(usage_context.instance_eval { binding })
23
-
24
- # remove multiple newlines from the end of usage
25
- "#{result.strip}\n"
26
- end
27
- end
28
-
29
- # The context in which the usage template will be executed
30
- #
31
- # @api private
32
- class CommandUsageContext
33
- # Name of the current command in context
34
- #
35
- # @return [String]
36
- attr_reader :name
37
-
38
- # Description of the current command
39
- #
40
- # @return [String]
41
- attr_reader :description
42
-
43
- # Pre-configured command's actions
44
- #
45
- # @return [Array<Cliqr::CLI::CommandUsageContext>]
46
- attr_reader :actions
47
-
48
- # List of options configured for current context
49
- #
50
- # @return [Array<Cliqr::CLI::OptionUsageContext>]
51
- attr_reader :options
52
-
53
- # Command for the current context
54
- #
55
- # @return [String]
56
- attr_reader :command
57
-
58
- # Wrap a [Cliqr::CLI::Config] instance for usage template
59
- def initialize(config)
60
- @config = config
61
-
62
- @name = config.name
63
- @description = config.description
64
- @actions = @config.actions.map { |action| CommandUsageContext.new(action) }
65
- @options = @config.options.map { |option| OptionUsageContext.new(option) }
66
- @command = @config.command
67
- end
68
-
69
- # Check if command has a description
70
- def description?
71
- non_empty?(@description)
72
- end
73
-
74
- # Check if there are any preconfigured options
75
- def options?
76
- non_empty?(@config.options)
77
- end
78
-
79
- # Check if current command allows arguments
80
- def arguments?
81
- @config.arguments == Cliqr::CLI::ENABLE_CONFIG
82
- end
83
-
84
- # Check if current command has any actions
85
- def actions?
86
- non_empty?(@actions)
87
- end
88
-
89
- # Check if the help is enabled
90
- #
91
- # @return [Boolean]
92
- def help?
93
- @config.help?
94
- end
95
-
96
- private
97
-
98
- # Check if a obj is non-empty
99
- def non_empty?(obj)
100
- !(obj.nil? || obj.empty?)
101
- end
102
- end
103
-
104
- # Wrapper of [Cliqr::CLI::OptionConfig] to be used in usage rendering
105
- #
106
- # @api private
107
- class OptionUsageContext
108
- # Name of the option
109
- #
110
- # @return [String]
111
- attr_reader :name
112
-
113
- # Short name of the option
114
- #
115
- # @return [String]
116
- attr_reader :short
117
-
118
- # Option's type
119
- #
120
- # @return [Symbol]
121
- attr_reader :type
122
-
123
- # Option's description
124
- #
125
- # @return [String]
126
- attr_reader :description
127
-
128
- # Default value for this option
129
- #
130
- # @return [Object]
131
- attr_reader :default
132
-
133
- # Create a new option usage context
134
- def initialize(option_config)
135
- @option_config = option_config
136
-
137
- @name = @option_config.name
138
- @short = @option_config.short
139
- @type = @option_config.type
140
- @description = @option_config.description
141
- @default = @option_config.default
142
- end
143
-
144
- # Check if current option is a boolean option
145
- def boolean?
146
- @option_config.boolean? && !help? && !version?
147
- end
148
-
149
- # Check if the option has a short name
150
- def short?
151
- @option_config.short?
152
- end
153
-
154
- # Check if the option has non-empty description
155
- def description?
156
- @option_config.description?
157
- end
158
-
159
- # Assert if the details of this options should be printed
160
- def details?
161
- @option_config.description? || @option_config.type? || @option_config.default?
162
- end
163
-
164
- # Check if the option has a non-default type
165
- def type?
166
- @option_config.type? && !help? && !version?
167
- end
168
-
169
- # check if the option should display default setting
170
- def default?
171
- @option_config.default? && !help? && !version?
172
- end
173
-
174
- # Check if the option is for getting help
175
- def help?
176
- @option_config.name == 'help'
177
- end
178
-
179
- # Check if the option is for version
180
- def version?
181
- @option_config.name == 'version'
182
- end
183
- end
184
- end
185
- end
@@ -1,48 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'cliqr/config_validation/validator_factory'
4
-
5
- module Cliqr
6
- module ConfigValidation
7
- # A collection of configured validators
8
- #
9
- # @api private
10
- class ValidationSet
11
- # Initialize a new instance of validation set
12
- def initialize
13
- @validations = {}
14
- end
15
-
16
- # Add a new validator
17
- #
18
- # @param [Symbol] name Name of the validator
19
- # @param [Object] options Configuration option to initialize the validator
20
- #
21
- # @return [Hash] A map of all validators
22
- def add(name, options)
23
- @validations[name] = \
24
- Hash[options.map { |type, config| [type, ValidatorFactory.get(type, config)] }]
25
- end
26
-
27
- # Iterate over each type of validators
28
- #
29
- # @return [Object]
30
- def each_key(&block)
31
- @validations.each_key(&block)
32
- end
33
-
34
- # Run the validators for a attribute against its value
35
- #
36
- # @param [Symbol] attribute Name of the attribute
37
- # @param [Object] value Value of the attribute
38
- # @param [Cliqr::Validation::Errors] errors A collection wrapper for all validation errors
39
- #
40
- # @return [Array] All validators that ran for the attribute against the value
41
- def validate(attribute, value, errors)
42
- @validations[attribute].values.each do |validator|
43
- validator.validate(attribute, value, errors)
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,319 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Cliqr
4
- module ConfigValidation
5
- # A factory class to retrieve a attribute validator based on the configuration type
6
- #
7
- # @api private
8
- module ValidatorFactory
9
- # Does not validates anything, used by default if a unknown validator type is used
10
- class Validator
11
- # Run this validator against an attribute value
12
- #
13
- # @param [String] name Name of the attribute
14
- # @param [Object] value Value of the attribute
15
- # @param [Cliqr::ValidationErrors] errors Errors after validation finished
16
- #
17
- # @return [Boolean] <tt>true</tt> if validation passed
18
- def validate(name, value, errors)
19
- validation_sequence(name, value, errors)
20
- end
21
-
22
- private
23
-
24
- # Recursively validate using parent validator then apply itself
25
- #
26
- # @return [Boolean] <tt>true</tt> if validation passed
27
- def validation_sequence(name, value, errors)
28
- return false unless validate_parent(name, value, errors)
29
-
30
- local_errors = ValidationErrors.new
31
- @class_stack.last.instance_method(:do_validate).bind(self).call(name, value, local_errors)
32
- errors.merge(local_errors)
33
- local_errors.empty?
34
- end
35
-
36
- # Validate attribute using the parent validator's logic
37
- #
38
- # @return [Boolean] <tt>true</tt> if parent class' validation passed
39
- def validate_parent(name, value, errors)
40
- @class_stack = (@class_stack || [self.class])
41
- parent_class = (@class_stack.last || self.class).superclass
42
- @class_stack.push(parent_class)
43
- begin
44
- return parent_class.instance_method(:validate).bind(self) \
45
- .call(name, value, errors) if parent_class < Validator
46
- true
47
- ensure
48
- @class_stack.pop
49
- end
50
- end
51
- end
52
-
53
- # This is used in case a unknown validation is used
54
- class NOOPValidator < Validator
55
- # Initialize a new no-op validator
56
- def initialize(type)
57
- @type = type
58
- end
59
-
60
- protected
61
-
62
- # Fails if invoked
63
- #
64
- # @return [Object]
65
- def do_validate(_name, _value, _errors)
66
- fail Cliqr::Error::UnknownValidatorType, "unknown validation type: '#{@type}'"
67
- end
68
- end
69
-
70
- # Verifies that an attribute's value is non-nil
71
- class NonNilValidator < Validator
72
- # Initialize a new non-nil validator
73
- def initialize(enabled)
74
- @enabled = enabled
75
- end
76
-
77
- protected
78
-
79
- # Validate presence of an attribute's value
80
- #
81
- # @return [Cliqr::ValidationErrors] Errors after the validation has finished
82
- def do_validate(name, value, errors)
83
- errors.add("'#{name}' cannot be nil") if @enabled && value.nil?
84
- errors
85
- end
86
- end
87
-
88
- # Verifies that an attribute's value is non-empty
89
- class NonEmptyValidator < NonNilValidator
90
- # Create a new non-empty validator
91
- def initialize(enabled)
92
- super(enabled)
93
- @enabled = enabled
94
- end
95
-
96
- protected
97
-
98
- # Validate that a attribute's value is not empty
99
- #
100
- # @return [Cliqr::ValidationErrors] Errors after the validation has finished
101
- def do_validate(name, value, errors)
102
- errors.add("'#{name}' cannot be empty") \
103
- if @enabled && value.respond_to?(:empty?) && value.empty?
104
- errors
105
- end
106
- end
107
-
108
- # Validates the value of an attribute against a regex pattern
109
- class FormatValidator < NonNilValidator
110
- # Initialize a new format validator
111
- #
112
- # @param [Regex] format Format of the value to validate required
113
- def initialize(format)
114
- super(true)
115
- @format = format
116
- end
117
-
118
- protected
119
-
120
- # Run the format validator to check attribute value's format
121
- #
122
- # @return [Boolean] <tt>true</tt> if there were any errors during validation
123
- def do_validate(name, value, errors)
124
- errors.add("value for '#{name}' must match /#{@format.source}/; " \
125
- "actual: #{value.inspect}") \
126
- if !value.nil? && @format.match(value).nil?
127
- errors
128
- end
129
- end
130
-
131
- # Validates that a value matches a pattern and it is not empty
132
- class NonEmptyFormatValidator < NonEmptyValidator
133
- # Initialize a new non-empty format validator
134
- #
135
- # @param [Regex] format Format of the value to validate required
136
- def initialize(format)
137
- super(true)
138
- @format = format
139
- end
140
-
141
- protected
142
-
143
- # Run the format validator along with non-empty check
144
- #
145
- # @return [Boolean] <tt>true</tt> if there were any errors during validation
146
- def do_validate(name, value, errors)
147
- FormatValidator.new(@format).validate(name, value, errors)
148
- end
149
- end
150
-
151
- # Validates that a value matches a pattern and it is not empty; nil value allowed
152
- class NonEmptyNilOkFormatValidator < Validator
153
- # Initialize a new non-empty-nil-ok format validator
154
- #
155
- # @param [Regex] format Format of the value to validate required
156
- def initialize(format)
157
- @format = format
158
- end
159
-
160
- protected
161
-
162
- # Run the validator
163
- #
164
- # @return [Boolean] <tt>true</tt> if there were any errors during validation
165
- def do_validate(name, value, errors)
166
- unless value.nil?
167
- local_errors = ValidationErrors.new
168
- local_errors.add("'#{name}' cannot be empty") \
169
- if value.respond_to?(:empty?) && value.empty?
170
- FormatValidator.new(@format).validate(name, value, local_errors) \
171
- if local_errors.empty?
172
- errors.merge(local_errors)
173
- end
174
- errors
175
- end
176
- end
177
-
178
- # Validates that the value of an attribute is of a type that extends from another
179
- class TypeHierarchyValidator < NonNilValidator
180
- # Create a new instance of type hierarchy validator
181
- #
182
- # @param [Class] super_type Class reference that the validated variable must extend from
183
- def initialize(super_type)
184
- super(true)
185
- @super_type = super_type
186
- end
187
-
188
- protected
189
-
190
- # Check if the type of <tt>value</tt> is extensible from a <tt>super_type</tt>
191
- #
192
- # @return [Boolean] <tt>true</tt> if there were any errors during validation
193
- def do_validate(name, value, errors)
194
- return if value.is_a?(@super_type) || \
195
- (value.respond_to?(:<) ? value < @super_type : value.is_a?(@super_type))
196
- errors.add("#{name} of type '#{value.class.name}' " \
197
- "does not extend from '#{@super_type}'")
198
- end
199
- end
200
-
201
- # Validates each element inside a collection
202
- class CollectionValidator < TypeHierarchyValidator
203
- # Create a new collection validator
204
- def initialize(_config)
205
- super(Array)
206
- end
207
-
208
- protected
209
-
210
- # Validate each element inside a collection and prepend index to error
211
- #
212
- # @return [Boolean] <tt>true</tt> if there were any errors during validation
213
- def do_validate(name, values, errors)
214
- valid = true
215
- values.each_with_index do |value, index|
216
- valid = false unless value.valid?
217
- value.errors.each do |error|
218
- if value.name.nil? || value.name.empty?
219
- errors.add("#{name}[#{index + 1}] - #{error}")
220
- else
221
- errors.add("#{name.to_s.gsub(/s$/, '')} \"#{value.name}\" - #{error}")
222
- end
223
- end
224
- end
225
- valid
226
- end
227
- end
228
-
229
- # Validate that a attribute value is included in a predefined set
230
- class InclusionValidator < NonNilValidator
231
- # Create a new inclusion validator
232
- #
233
- # @param [Array<Symbol>] allowed_values A set of allowed values
234
- def initialize(allowed_values)
235
- @allowed_values = allowed_values
236
- end
237
-
238
- protected
239
-
240
- # Validate that a value is included in <tt>allowed_values</tt>
241
- #
242
- # @return [Nothing]
243
- def do_validate(_name, value, errors)
244
- errors.add("invalid type '#{value}'") unless @allowed_values.include?(value)
245
- end
246
- end
247
-
248
- # Validate the type of a attribute's value
249
- class TypeOfValidator < NonNilValidator
250
- # Create a new <tt>:type_of</tt> validator
251
- def initialize(type)
252
- super(true)
253
- @type = type
254
- end
255
-
256
- protected
257
-
258
- # Run the <tt>:type_of</tt> validation check
259
- #
260
- # @return [Nothing]
261
- def do_validate(name, value, errors)
262
- errors.add("#{name} should be a '#{@type}' not '#{value.class}'") \
263
- unless value.class == @type
264
- end
265
- end
266
-
267
- # Run multiple validators on a value to assert at-least one of them passes
268
- class OneOfValidator < Validator
269
- # Create a new <tt>:one_of</tt> validator
270
- def initialize(validators)
271
- @validators = validators.map { |type, config| ValidatorFactory.get(type, config) }
272
- end
273
-
274
- protected
275
-
276
- # Run each validator one by one until one passes
277
- #
278
- # @return [Nothing]
279
- def do_validate(name, value, errors)
280
- local_errors = ValidationErrors.new
281
- passing_validator = @validators.find do |validator|
282
- validator_errors = ValidationErrors.new.tap do |temp_errors|
283
- validator.validate(name, value, temp_errors)
284
- local_errors.merge(temp_errors)
285
- end
286
- validator_errors.empty?
287
- end
288
- errors.add("invalid value for #{name}; fix one of - [#{local_errors}]") \
289
- if passing_validator.nil?
290
- end
291
- end
292
-
293
- # A hash of validator type id to validator class
294
- VALIDATORS = {
295
- :non_empty => NonEmptyValidator,
296
- :non_empty_format => NonEmptyFormatValidator,
297
- :non_empty_nil_ok_format => NonEmptyNilOkFormatValidator,
298
- :format => FormatValidator,
299
- :extend => TypeHierarchyValidator,
300
- :collection => CollectionValidator,
301
- :inclusion => InclusionValidator,
302
- :one_of => OneOfValidator,
303
- :type_of => TypeOfValidator
304
- }
305
-
306
- # Get a new validator based on the type and config param
307
- #
308
- # @return [Cliqr::Validation::ValidatorFactory::Validator]
309
- def self.get(validator_type, config)
310
- validator_class = VALIDATORS[validator_type]
311
- if validator_class.nil?
312
- NOOPValidator.new(validator_type)
313
- else
314
- validator_class.new(config)
315
- end
316
- end
317
- end
318
- end
319
- end