anodator 1.0.0.pre1 → 1.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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