anodator 1.0.0.pre1 → 1.0.0.pre2

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 (44) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +3 -0
  3. data/.gitignore +3 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +2 -2
  6. data/README.md +9 -4
  7. data/Rakefile +3 -3
  8. data/anodator.gemspec +17 -14
  9. data/bin/console +3 -3
  10. data/bin/console-on-docker +4 -0
  11. data/bin/docker-console +2 -0
  12. data/bin/docker-prompt +2 -0
  13. data/bin/prompt-on-docker +4 -0
  14. data/bin/setup +0 -0
  15. data/example/example_01.rb +88 -61
  16. data/lib/anodator.rb +3 -2
  17. data/lib/anodator/anodator_error.rb +1 -1
  18. data/lib/anodator/check_result.rb +7 -7
  19. data/lib/anodator/checker.rb +33 -26
  20. data/lib/anodator/common.rb +15 -0
  21. data/lib/anodator/data_source.rb +87 -0
  22. data/lib/anodator/data_source_set.rb +36 -0
  23. data/lib/anodator/input_spec.rb +105 -111
  24. data/lib/anodator/input_spec_item.rb +8 -8
  25. data/lib/anodator/message.rb +14 -14
  26. data/lib/anodator/output_spec.rb +40 -43
  27. data/lib/anodator/rule.rb +26 -32
  28. data/lib/anodator/rule_set.rb +6 -12
  29. data/lib/anodator/utils.rb +36 -42
  30. data/lib/anodator/validator.rb +9 -9
  31. data/lib/anodator/validator/base.rb +44 -27
  32. data/lib/anodator/validator/blank_validator.rb +2 -2
  33. data/lib/anodator/validator/complex_validator.rb +12 -12
  34. data/lib/anodator/validator/configuration_error.rb +1 -2
  35. data/lib/anodator/validator/date_validator.rb +93 -103
  36. data/lib/anodator/validator/format_validator.rb +8 -11
  37. data/lib/anodator/validator/inclusion_validator.rb +3 -3
  38. data/lib/anodator/validator/length_validator.rb +6 -6
  39. data/lib/anodator/validator/numeric_validator.rb +13 -13
  40. data/lib/anodator/validator/presence_validator.rb +2 -2
  41. data/lib/anodator/validator/value_proxy.rb +31 -9
  42. data/lib/anodator/version.rb +1 -1
  43. metadata +41 -6
  44. data/VERSION +0 -1
@@ -1,48 +1,55 @@
1
- require "anodator/input_spec"
2
- require "anodator/rule_set"
3
- require "anodator/output_spec"
4
- require "anodator/anodator_error"
1
+ require 'anodator/input_spec'
2
+ require 'anodator/rule_set'
3
+ require 'anodator/output_spec'
4
+ require 'anodator/data_source_set'
5
+ require 'anodator/anodator_error'
5
6
 
6
7
  module Anodator
8
+ # Checker class.
7
9
  class Checker
8
- def initialize(input_spec, rule_set, default_output_spec, configuration_check = false)
9
- @input_spec = input_spec
10
- @rule_set = rule_set
11
- @output_specs = [default_output_spec]
10
+ include Common
12
11
 
13
- unless @input_spec.is_a? InputSpec
14
- raise ArgumentError.new("input_spec must be InputSpec object")
15
- end
16
- unless @rule_set.is_a? RuleSet
17
- raise ArgumentError.new("rule_set must be RuleSet object")
18
- end
19
- unless @output_specs.first.is_a? OutputSpec
20
- raise ArgumentError.new("default_output_spec must be OutputSpec object")
21
- end
12
+ def initialize(input_spec, rule_set, output_spec, config_check = false)
13
+ validate_initialize_params(input_spec, rule_set, output_spec)
22
14
 
15
+ @input_spec = input_spec
16
+ @rule_set = rule_set
17
+ @output_specs = [output_spec]
18
+ @data_source_set = DataSourceSet.new
23
19
  Validator::Base.values = @input_spec
20
+ Validator::Base.data_source_set = @data_source_set
24
21
 
25
- validate_configuration if configuration_check
22
+ validate_configuration if config_check
23
+ end
24
+
25
+ def validate_initialize_params(input_spec, rule_set, output_spec)
26
+ params =
27
+ [
28
+ [input_spec, InputSpec],
29
+ [rule_set, RuleSet],
30
+ [output_spec, OutputSpec]
31
+ ]
32
+
33
+ check_instances(params, ArgumentError)
26
34
  end
27
35
 
28
36
  def validate_configuration
29
- # RuleSet
30
37
  @rule_set.validate_configuration
31
- # OutputSpec
32
- @output_specs.each do |spec|
33
- spec.validate_configuration
34
- end
38
+ @output_specs.each(&:validate_configuration)
35
39
  end
36
40
 
37
41
  def add_output_spec(output_spec, configuration_check = false)
38
- unless output_spec.is_a? OutputSpec
39
- raise ArgumentError.new("output_spec must be OutputSpec object")
40
- end
42
+ check_instances([[output_spec, OutputSpec]], ArgumentError)
43
+
41
44
  @output_specs << output_spec
42
45
 
43
46
  validate_configuration if configuration_check
44
47
  end
45
48
 
49
+ def add_data_source(data_source)
50
+ @data_source_set << data_source
51
+ end
52
+
46
53
  def run(values)
47
54
  @input_spec.source = values
48
55
  @rule_set.check_all
@@ -0,0 +1,15 @@
1
+ module Anodator
2
+ # Anodator common class.
3
+ #
4
+ # Common class is useful common functions.
5
+ module Common
6
+ def check_instances(value_and_classes, exception_class)
7
+ value_and_classes.each do |value_and_class|
8
+ v = value_and_class[0]
9
+ c = value_and_class[1]
10
+
11
+ raise exception_class, "#{v} must be #{c}" unless v.is_a? c
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,87 @@
1
+ module Anodator
2
+ # Unknown key on DataSource error.
3
+ class UnknownKeyOnDataSourceError < StandardError; end
4
+ # DataSource is external data source for check input datas.
5
+ #
6
+ # datas = {
7
+ # 'key1' => {
8
+ # name: 'taro',
9
+ # age: 20
10
+ # },
11
+ # 'key2' => {
12
+ # name: 'jiro',
13
+ # age: 35
14
+ # }
15
+ # }
16
+ # # datas object must be respond to :[].
17
+ # ds = DataSource.new('name', datas)
18
+ class DataSource
19
+ VALID_OPTIONS = {
20
+ on_nil: %i[raise_error nil],
21
+ on_exception: %i[raise_error nil]
22
+ }.freeze
23
+
24
+ DEFAULT_OPTIONS = {
25
+ on_nil: :nil,
26
+ on_exception: :raise_error
27
+ }.freeze
28
+
29
+ attr_reader :identifier
30
+
31
+ def initialize(identifier, datas, options = {})
32
+ msg = 'datas must be respond to :[]'
33
+ raise ArgumentError, msg unless datas.respond_to?(:[])
34
+ options = merge_default_options(options)
35
+ validate_options(options)
36
+
37
+ @identifier = identifier.to_s
38
+ @datas = datas
39
+ @options = options
40
+ end
41
+
42
+ def [](key)
43
+ @datas[key].nil? ? on_nil_value : @datas[key]
44
+ rescue UnknownKeyOnDataSourceError
45
+ raise
46
+ rescue StandardError => e
47
+ on_exception(e)
48
+ end
49
+
50
+ private
51
+
52
+ def merge_default_options(options)
53
+ DEFAULT_OPTIONS.each_with_object(options.dup) do |(k, v), merged|
54
+ merged[k] ||= v
55
+ end
56
+ end
57
+
58
+ def validate_options(options)
59
+ validate_option_keys(options)
60
+ validate_option_values(options)
61
+ end
62
+
63
+ def validate_option_keys(options)
64
+ options.each_key do |key|
65
+ msg = "Invalid option '#{key.inspect}'"
66
+ raise ArgumentError, msg unless VALID_OPTIONS.key?(key)
67
+ end
68
+ end
69
+
70
+ def validate_option_values(options)
71
+ options.each do |key, value|
72
+ msg = "Invalid #{key.inspect} option value '#{value.inspect}'"
73
+ raise ArgumentError, msg unless VALID_OPTIONS[key].include?(value)
74
+ end
75
+ end
76
+
77
+ def on_nil_value
78
+ return nil if @options[:on_nil] == :nil
79
+ raise UnknownKeyOnDataSourceError, 'DataSource nil'
80
+ end
81
+
82
+ def on_exception(e)
83
+ return nil if @options[:on_exception] == :nil
84
+ raise UnknownKeyOnDataSourceError, e
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,36 @@
1
+ require 'anodator/data_source'
2
+
3
+ module Anodator
4
+ # duplicated DataSource Identifier error.
5
+ class DuplicatedDataSourceIdentifierError < StandardError; end
6
+ # DataSourceSet is a set of DataSource
7
+ class DataSourceSet
8
+ def initialize
9
+ @data_sources = {}
10
+ end
11
+
12
+ def <<(data_source)
13
+ msg = 'data_source must be DataSource.'
14
+ raise ArgumentError, msg unless data_source.is_a? DataSource
15
+
16
+ check_duplicate_key(data_source)
17
+ end
18
+
19
+ def fetch(identifier, key, column)
20
+ identifier = identifier.to_s.to_sym
21
+ key = key.to_s.to_sym
22
+ column = column.to_s.to_sym
23
+
24
+ @data_sources[identifier][key][column]
25
+ end
26
+
27
+ private
28
+
29
+ def check_duplicate_key(data_source)
30
+ key = data_source.identifier.to_sym
31
+ raise DuplicatedDataSourceIdentifierError if @data_sources.key?(key)
32
+
33
+ @data_sources[key] = data_source
34
+ end
35
+ end
36
+ end
@@ -1,60 +1,68 @@
1
- require "anodator/input_spec_item"
2
- require "anodator/anodator_error"
1
+ require 'anodator/input_spec_item'
2
+ require 'anodator/anodator_error'
3
3
 
4
4
  module Anodator
5
5
  class DuplicatedInputSpecItemError < AnodatorError; end
6
6
  class UnknownTargetExpressionError < AnodatorError; end
7
7
  class SourceDataNotProvidedError < AnodatorError; end
8
8
 
9
+ # InputSpec
9
10
  class InputSpec
10
11
  CALCULATION_HOLDER_REGEXP = /\[\[([^\]]+)\]\]/
11
12
 
13
+ include Common
14
+
12
15
  def initialize(spec_items = [])
13
16
  @spec_items = []
14
17
  @source = nil
15
- @number_dict = { }
16
- @name_dict = { }
18
+ @number_dict = {}
19
+ @name_dict = {}
20
+
21
+ check_instances([[spec_items, Array]], ArgumentError)
22
+ setup_input_spec_items(spec_items)
23
+ end
17
24
 
18
- unless spec_items.is_a? Array
19
- raise ArgumentError.new("initialized by Array by Hash(key is :name and :number")
25
+ def setup_input_spec_items(spec_items)
26
+ spec_items.each do |spec|
27
+ next unless %i[number name].all? { |k| spec.key?(k) }
28
+ push_spec_items(build_input_spec_item(spec))
20
29
  end
30
+ end
31
+ private :setup_input_spec_items
21
32
 
22
- spec_items.each do |spec_item_values|
23
- if spec_item_values.keys.include?(:number) &&
24
- spec_item_values.keys.include?(:name)
25
- if spec_item_values.keys.include?(:type) &&
26
- !spec_item_values[:type].nil?
27
- push_spec_items(InputSpecItem.new(spec_item_values[:number],
28
- spec_item_values[:name],
29
- spec_item_values[:type]))
30
- else
31
- push_spec_items(InputSpecItem.new(spec_item_values[:number],
32
- spec_item_values[:name]))
33
- end
34
- end
33
+ def build_input_spec_item(spec)
34
+ if spec[:type].nil?
35
+ InputSpecItem.new(spec[:number], spec[:name])
36
+ else
37
+ InputSpecItem.new(spec[:number], spec[:name], spec[:type])
35
38
  end
36
39
  end
40
+ private :build_input_spec_item
37
41
 
38
42
  def push_spec_items(spec)
39
- if @number_dict.keys.include?(spec.number)
40
- raise DuplicatedInputSpecItemError.new("duplicated number spec item '#{spec.number}'")
41
- end
42
- if @name_dict.keys.include?(spec.name)
43
- raise DuplicatedInputSpecItemError.new("duplicated name spec item '#{spec.name}'")
44
- end
43
+ check_duplicate_key(spec.number, spec.name)
44
+
45
45
  @spec_items << spec
46
46
  index = @spec_items.size - 1
47
- @number_dict[spec.number] = { :item => spec, :index => index }
48
- @name_dict[spec.name] = { :item => spec, :index => index }
47
+ @number_dict[spec.number] = { item: spec, index: index }
48
+ @name_dict[spec.name] = { item: spec, index: index }
49
49
  end
50
50
  private :push_spec_items
51
51
 
52
+ def check_duplicate_key(num, name)
53
+ msg = nil
54
+ msg = "duplicated number spec item '#{num}'" if @number_dict.key?(num)
55
+ msg = "duplicated name spec item '#{name}'" if @name_dict.key?(name)
56
+
57
+ raise DuplicatedInputSpecItemError, msg unless msg.nil?
58
+ end
59
+ private :check_duplicate_key
60
+
52
61
  def source=(source)
53
- if source.respond_to? :[]
54
- @source = source
55
- else
56
- raise ArgumentError.new("source should respond to :[] method")
57
- end
62
+ msg = 'source should respond to :[] method'
63
+ raise ArgumentError, msg unless source.respond_to? :[]
64
+
65
+ @source = source
58
66
  end
59
67
 
60
68
  def clear_source
@@ -62,104 +70,91 @@ module Anodator
62
70
  end
63
71
 
64
72
  def value_at(index)
65
- raise SourceDataNotProvidedError.new if @source.nil?
73
+ raise SourceDataNotProvidedError if @source.nil?
74
+ msg = "accessed by index '#{index}'"
75
+ raise UnknownTargetExpressionError, msg if @spec_items[index].nil?
66
76
 
67
- if @spec_items[index].nil?
68
- raise UnknownTargetExpressionError.new("accessed by index '#{index}'")
69
- else
70
- return @source[index].to_s
71
- end
77
+ @source[index].to_s
72
78
  end
73
79
 
74
80
  def value_at_by_number(number)
75
- raise SourceDataNotProvidedError.new if @source.nil?
81
+ raise SourceDataNotProvidedError if @source.nil?
82
+ msg = "accessed by number '#{number}'"
83
+ raise UnknownTargetExpressionError, msg unless @number_dict.key?(number)
76
84
 
77
- if @number_dict.keys.include?(number)
78
- return value_at(@number_dict[number][:index])
79
- else
80
- raise UnknownTargetExpressionError.new("accessed by number '#{number}'")
81
- end
85
+ value_at(@number_dict[number][:index])
82
86
  end
83
87
 
84
88
  def value_at_by_name(name)
85
- raise SourceDataNotProvidedError.new if @source.nil?
89
+ raise SourceDataNotProvidedError if @source.nil?
90
+ msg = "accessed by name '#{name}'"
91
+ raise UnknownTargetExpressionError, msg unless @name_dict.key?(name)
86
92
 
87
- if @name_dict.keys.include?(name)
88
- return value_at(@name_dict[name][:index])
89
- else
90
- raise UnknownTargetExpressionError.new("accessed by name '#{name}'")
91
- end
93
+ value_at(@name_dict[name][:index])
92
94
  end
93
95
 
94
96
  def value_by_calculation(calculation)
95
97
  holders = check_calculation_expression(calculation)
96
-
97
- values = holders.inject({ }) do |hash, expression|
98
- value = self[expression]
99
- spec = spec_item_by_expression(expression)
100
-
101
- case spec.type
102
- when InputSpecItem::TYPE_NUMERIC
103
- hash["[[#{expression}]]"] = %Q|BigDecimal("#{value}")|
104
- else # String, other
105
- hash["[[#{expression}]]"] = %Q|"#{value}"|
106
- end
107
- next hash
108
- end
109
-
110
- calculation.gsub!(CALCULATION_HOLDER_REGEXP) do |match|
111
- values[match]
112
- end
98
+ values = convert_values_from_holders(holders)
99
+ calculation.gsub!(CALCULATION_HOLDER_REGEXP) { |m| values[m] }
113
100
 
114
101
  value = eval(calculation)
115
- value = value.to_s("F") if value.is_a? BigDecimal
102
+ value = value.to_s('F') if value.is_a? BigDecimal
116
103
 
117
104
  return value
118
- rescue UnknownTargetExpressionError => ex
119
- raise
120
- rescue => ex
121
- raise UnknownTargetExpressionError.new("accessed by calculation '#{calculation}'")
105
+ rescue StandardError
106
+ msg = "accessed by calculation '#{calculation}'"
107
+ raise UnknownTargetExpressionError, msg
122
108
  end
123
109
  private :value_by_calculation
124
110
 
111
+ def convert_values_from_holders(holders)
112
+ holders.each_with_object({}) do |expression, hash|
113
+ value = self[expression]
114
+ spec = spec_item_by_expression(expression)
115
+
116
+ hash["[[#{expression}]]"] =
117
+ if spec.type == InputSpecItem::TYPE_NUMERIC
118
+ %|BigDecimal("#{value}")|
119
+ else
120
+ %("#{value}")
121
+ end
122
+ end
123
+ end
124
+ private :convert_values_from_holders
125
+
125
126
  def [](target_expression)
126
- raise SourceDataNotProvidedError.new if @source.nil?
127
+ raise SourceDataNotProvidedError if @source.nil?
128
+ return value_at(target_expression) if target_expression.is_a? Integer
127
129
 
128
- if target_expression.is_a? Integer
129
- return value_at(target_expression)
130
- elsif /^CALC::(.+)$/.match target_expression
131
- return value_by_calculation($1)
132
- else
133
- begin
134
- return value_at_by_number(target_expression)
135
- rescue UnknownTargetExpressionError
136
- return value_at_by_name(target_expression)
137
- end
130
+ if target_expression =~ /^CALC::(.+)$/
131
+ return value_by_calculation(Regexp.last_match(1))
138
132
  end
133
+
134
+ value_at_by_number(target_expression)
135
+ rescue UnknownTargetExpressionError
136
+ value_at_by_name(target_expression)
139
137
  end
140
138
 
141
139
  def spec_item_at(index)
142
- if @spec_items[index].nil?
143
- raise UnknownTargetExpressionError.new("accessed by index '#{index}'")
144
- else
145
- @spec_items[index].dup
146
- end
140
+ msg = "accessed by index '#{index}'"
141
+ raise UnknownTargetExpressionError, msg if @spec_items[index].nil?
142
+
143
+ @spec_items[index].dup
147
144
  end
148
145
 
149
146
  def spec_item_at_by_number(number)
150
- if @number_dict.keys.include?(number)
151
- return @number_dict[number][:item].dup
152
- else
153
- raise UnknownTargetExpressionError.new("accessed by number '#{number}'")
154
- end
147
+ msg = "accessed by number '#{number}'"
148
+ raise UnknownTargetExpressionError, msg unless @number_dict.key?(number)
149
+
150
+ @number_dict[number][:item].dup
155
151
  end
156
152
 
157
153
  def spec_item_at_by_name(name)
158
- if @name_dict.keys.include?(name)
159
- return @name_dict[name][:item].dup
160
- else
161
- raise UnknownTargetExpressionError.new("accessed by name '#{name}'")
162
- end
154
+ msg = "accessed by name '#{name}'"
155
+ raise UnknownTargetExpressionError, msg unless @name_dict.key?(name)
156
+
157
+ @name_dict[name][:item].dup
163
158
  end
164
159
 
165
160
  def spec_items_by_calculation(calculation)
@@ -171,26 +166,25 @@ module Anodator
171
166
  private :spec_items_by_calculation
172
167
 
173
168
  def spec_item_by_expression(target_expression)
174
- if target_expression.is_a? Integer
175
- return spec_item_at(target_expression)
176
- elsif /^CALC::(.+)$/.match target_expression
177
- return spec_items_by_calculation($1)
178
- else
179
- begin
180
- return spec_item_at_by_number(target_expression)
181
- rescue UnknownTargetExpressionError
182
- return spec_item_at_by_name(target_expression)
183
- end
169
+ return spec_item_at(target_expression) if target_expression.is_a? Integer
170
+
171
+ if target_expression =~ /^CALC::(.+)$/
172
+ return spec_items_by_calculation(Regexp.last_match(1))
184
173
  end
174
+
175
+ return spec_item_at_by_number(target_expression)
176
+ rescue UnknownTargetExpressionError
177
+ return spec_item_at_by_name(target_expression)
185
178
  end
186
179
 
187
180
  # return all holders specs
188
181
  def check_calculation_expression(calculation)
189
- if /(@|require|load|;)/.match calculation
190
- return ArgumentError.new("Invalid calcuation expression '#{calcuation}'")
182
+ if calculation =~ /(@|require|load|;)/
183
+ raise ArgumentError, "Invalid calcuation expression '#{calcuation}'"
191
184
  end
192
185
 
193
- calculation.scan(CALCULATION_HOLDER_REGEXP).flatten.map do |target_expression|
186
+ calculation.scan(CALCULATION_HOLDER_REGEXP)
187
+ .flatten.map do |target_expression|
194
188
  spec_item_by_expression(target_expression)
195
189
  next target_expression
196
190
  end