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
data/lib/cliqr/cli/interface.rb
DELETED
@@ -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
|