anodator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +13 -0
  4. data/Gemfile.lock +28 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.ja.rdoc +33 -0
  7. data/README.rdoc +41 -0
  8. data/Rakefile +50 -0
  9. data/VERSION +1 -0
  10. data/example/example_01.rb +129 -0
  11. data/lib/anodator/anodator_error.rb +5 -0
  12. data/lib/anodator/check_result.rb +41 -0
  13. data/lib/anodator/checker.rb +58 -0
  14. data/lib/anodator/input_spec.rb +199 -0
  15. data/lib/anodator/input_spec_item.rb +33 -0
  16. data/lib/anodator/message.rb +59 -0
  17. data/lib/anodator/output_spec.rb +164 -0
  18. data/lib/anodator/rule.rb +97 -0
  19. data/lib/anodator/rule_set.rb +52 -0
  20. data/lib/anodator/utils.rb +234 -0
  21. data/lib/anodator/validator/base.rb +168 -0
  22. data/lib/anodator/validator/blank_validator.rb +14 -0
  23. data/lib/anodator/validator/complex_validator.rb +60 -0
  24. data/lib/anodator/validator/configuration_error.rb +8 -0
  25. data/lib/anodator/validator/date_validator.rb +151 -0
  26. data/lib/anodator/validator/format_validator.rb +48 -0
  27. data/lib/anodator/validator/inclusion_validator.rb +21 -0
  28. data/lib/anodator/validator/length_validator.rb +37 -0
  29. data/lib/anodator/validator/numeric_validator.rb +46 -0
  30. data/lib/anodator/validator/presence_validator.rb +14 -0
  31. data/lib/anodator/validator.rb +10 -0
  32. data/lib/anodator.rb +3 -0
  33. data/spec/anodator/check_result_spec.rb +101 -0
  34. data/spec/anodator/checker_spec.rb +273 -0
  35. data/spec/anodator/input_spec_item_spec.rb +100 -0
  36. data/spec/anodator/input_spec_spec.rb +584 -0
  37. data/spec/anodator/message_spec.rb +112 -0
  38. data/spec/anodator/output_spec_spec.rb +355 -0
  39. data/spec/anodator/rule_set_spec.rb +190 -0
  40. data/spec/anodator/rule_spec.rb +169 -0
  41. data/spec/anodator/validator/base_spec.rb +214 -0
  42. data/spec/anodator/validator/blank_validator_spec.rb +52 -0
  43. data/spec/anodator/validator/complex_validator_spec.rb +268 -0
  44. data/spec/anodator/validator/date_validator_spec.rb +350 -0
  45. data/spec/anodator/validator/format_validator_spec.rb +158 -0
  46. data/spec/anodator/validator/inclusion_validator_spec.rb +77 -0
  47. data/spec/anodator/validator/length_validator_spec.rb +236 -0
  48. data/spec/anodator/validator/numeric_validator_spec.rb +468 -0
  49. data/spec/anodator/validator/presence_validator_spec.rb +52 -0
  50. data/spec/anodator/validator_spec.rb +16 -0
  51. data/spec/anodator_spec.rb +2 -0
  52. data/spec/spec_helper.rb +12 -0
  53. metadata +188 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.2)
11
+ rcov (0.9.9)
12
+ rspec (2.3.0)
13
+ rspec-core (~> 2.3.0)
14
+ rspec-expectations (~> 2.3.0)
15
+ rspec-mocks (~> 2.3.0)
16
+ rspec-core (2.3.1)
17
+ rspec-expectations (2.3.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.3.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.0.0)
26
+ jeweler (~> 1.6.4)
27
+ rcov
28
+ rspec (~> 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Tetsuhisa MAKINO
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.ja.rdoc ADDED
@@ -0,0 +1,33 @@
1
+ = anodator is Anonimous Data Validator
2
+
3
+ anodator はテキストデータのチェックを支援するライブラリです。
4
+ チェック対象となるテキストデータは配列形式で渡され、
5
+ 出力結果は二次元配列として出力されます。
6
+ anodator はチェックに際し、受け取るデータの仕様、チェックするためのルールセット、
7
+ および、出力時の仕様を受け取ることで、任意形式の入出力に対応します。
8
+ 通常は Ruby コード上で各種仕様の受け渡し等を実施しますが、簡易的な設定方法も
9
+ 提供されます。
10
+
11
+ == インストール方法
12
+
13
+ コマンドラインより下記のコマンドを実行してください。
14
+
15
+ $ gem install anodator
16
+
17
+ == ライブラリとして使うには
18
+
19
+ (後で書く)
20
+
21
+ == コマンドとして使う
22
+
23
+ (後で書く)
24
+
25
+ == Author
26
+
27
+ Tetsuhisa MAKINO <tim.makino at gmail.com>
28
+
29
+ == Copyright
30
+
31
+ Copyright (c) 2011 Tetsuhisa MAKINO. See LICENSE.txt for
32
+ further details.
33
+
data/README.rdoc ADDED
@@ -0,0 +1,41 @@
1
+ = anodator is Anonymous Data Validator
2
+
3
+ anodator is a library to help you check the text data. Text data being checked
4
+ is passed in an array, The output will be printed as two-dimensional array.
5
+ anodator is checked upon the specifications of the received data, a set of rules
6
+ for checking, And by receiving the output specification, corresponds to the
7
+ input of any type. Although usually performed on the various specifications such
8
+ as Ruby code and passing, how a simple set Will be provided.
9
+
10
+ == How to install
11
+
12
+ Please run the following command from the command line.
13
+
14
+ $ gem install anodator
15
+
16
+ == To use a library
17
+
18
+ (Written later)
19
+
20
+ == To use a command
21
+
22
+ (Written later)
23
+
24
+ == Contributing to anodator
25
+
26
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
27
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
28
+ * Fork the project
29
+ * Start a feature/bugfix branch
30
+ * Commit and push until you are happy with your contribution
31
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
32
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
33
+
34
+ == Author
35
+
36
+ Tetsuhisa MAKINO <tim.makino at gmail.com>
37
+
38
+ == Copyright
39
+
40
+ Copyright (c) 2011 Tetsuhisa MAKINO. See LICENSE.txt for
41
+ further details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "anodator"
18
+ gem.homepage = "https://github.com/maki-tetsu/anodator"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{anodator is Anonymous Data Validator.}
21
+ gem.description = %Q{anodator is Anonymous Data Validator.}
22
+ gem.email = "tim.makino at gmail.com"
23
+ gem.authors = ["Tetsuhisa MAKINO"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "anodator #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ rdoc.options << "--charset=utf8"
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Simple check example by anodator
3
+
4
+ require File.join(File.dirname(__FILE__), "..", "lib", "anodator")
5
+ include Anodator
6
+
7
+ ### target data columns
8
+ # ID, Family name, First name, Sex, Phone number, Birthday, Blood type
9
+ input_spec_array_definition =
10
+ [
11
+ { :number => "1", :name => "ID", },
12
+ { :number => "2", :name => "Family name", },
13
+ { :number => "3", :name => "First name", },
14
+ { :number => "4", :name => "Sex", },
15
+ { :number => "5", :name => "Phone number", },
16
+ { :number => "6", :name => "Birthday", },
17
+ { :number => "7", :name => "Blood type", },
18
+ ]
19
+ input_spec = InputSpec.new(input_spec_array_definition)
20
+
21
+
22
+ ### check rules
23
+ rule_set = RuleSet.new
24
+ ## ID
25
+ # ID must be integer number
26
+ validator = Validator::NumericValidator.new("1", :only_integer => true)
27
+ rule_set << Rule.new("1", Message.new("[[1::name]] must be integer number.([[1::value]])"), validator)
28
+
29
+ ## Family name
30
+ # Family name must not be blank
31
+ # Family name length must be 2..20
32
+ # Family name must be include a-z or A-Z and "'"
33
+ validators = []
34
+ validators << Validator::PresenceValidator.new("2")
35
+ validators << Validator::LengthValidator.new("2", :in => 2..20)
36
+ validators << Validator::FormatValidator.new("2", :format => /^[a-z']+$/i)
37
+ validator = Validator::ComplexValidator.new(:validators => validators,
38
+ :logic => Validator::ComplexValidator::LOGIC_AND)
39
+ rule_set << Rule.new("2",
40
+ Message.new("[[2::name]] must be alphabets or \"'\", maximum length 20 and cannot be blank.([[2::value]])"),
41
+ validator)
42
+
43
+ ## First name
44
+ # Family name must not be blank
45
+ # Family name length must be 2..20
46
+ # Family name must be include a-z or A-Z and "'"
47
+ validators = []
48
+ validators << Validator::PresenceValidator.new("3")
49
+ validators << Validator::LengthValidator.new("3", :in => 2..20)
50
+ validators << Validator::FormatValidator.new("3", :format => /^[a-z']+$/i)
51
+ validator = Validator::ComplexValidator.new(:validators => validators,
52
+ :logic => Validator::ComplexValidator::LOGIC_AND)
53
+ rule_set << Rule.new("3",
54
+ Message.new("[[3::name]] must be alphabets or \"'\", maximum length 20 and cannot be blank.([[3::value]])"),
55
+ validator)
56
+
57
+ ## Sex
58
+ # Sex is 'M' or 'F'
59
+ validator = Validator::InclusionValidator.new("4", :in => %W(M F))
60
+ rule_set << Rule.new("4",
61
+ Message.new("[[4::name]] must be 'M' or 'F'.([[4::value]])"),
62
+ validator)
63
+
64
+ ## Phone number
65
+ # Phone number is only number
66
+ # Phone number length must be maximum 12
67
+ validators = []
68
+ validators << Validator::LengthValidator.new("5", :maximum => 12, :allow_blank => true)
69
+ validators << Validator::FormatValidator.new("5", :format => /^[0-9]+$/, :allow_blank => true)
70
+ validator = Validator::ComplexValidator.new(:validators => validators,
71
+ :logic => Validator::ComplexValidator::LOGIC_AND)
72
+ rule_set << Rule.new("5",
73
+ Message.new("[[5::name]] must be include only integer number and mixumum length is 12.([[5::value]])"),
74
+ validator)
75
+
76
+ ## Birthday
77
+ # Birthday must be YYYY-MM-DD format
78
+ validator = Validator::DateValidator.new("6")
79
+ rule_set << Rule.new("6",
80
+ Message.new("[[6::name]] must be date expression.([[6::value]])"),
81
+ validator)
82
+
83
+ ## Blood type
84
+ # Blood type must be include 'A', 'B', 'O' or 'AB'
85
+ validator = Validator::InclusionValidator.new("7", :in => %W(A B O AB))
86
+ rule_set << Rule.new("7",
87
+ Message.new("[[7::name]] must be 'A', 'B', 'O' or 'AB'.(([[7::value]]))"),
88
+ validator)
89
+
90
+ ### output spec
91
+ ## error list
92
+ items =
93
+ [
94
+ "1",
95
+ :target_numbers,
96
+ :error_message,
97
+ :error_level
98
+ ]
99
+ output_spec = OutputSpec.new(items,
100
+ :target => OutputSpec::TARGET_ERROR,
101
+ :include_no_error => false)
102
+
103
+ ### Checker
104
+ checker = Checker.new(input_spec, rule_set, output_spec)
105
+
106
+ ### target datas
107
+ datas =
108
+ [
109
+ ["1", "Murayama", "Honoka","F","08050967141","1971-10-01","B"],
110
+ ["2", "Izawa", "Kazuma", "M", "09070028635", "1968-03-24","O"],
111
+ ["3", "Hasebe", "Miyu", "F", "08087224562", "1991-01-21", "A"],
112
+ ["4", "Furusawa", "Eri", "F", "08017372898","1965-02-14", "O"],
113
+ ["5", "Hiramoto", "Yutaka", "M", "", "1986-09-14", "AB"],
114
+ ["6", "Matsuzaki", "Runa", "F", "", "1960-03-27", "O"],
115
+ ["7", "Inagaki", "Kouichi", "M", "", "1961-01-04", "B"],
116
+ ["8", "Kase", "Sueji", "", "", "1969-03-19", "B"],
117
+ ["9", "Kawanishi", "Hinako", "F", "08029628506", "1970-05-29", "B"],
118
+ ["10", "Sakurai", "Eijirou", "M", "", "1988-13-16", "A"],
119
+ ]
120
+
121
+ ### run check for all datas
122
+ error_list = []
123
+ datas.each do |data|
124
+ error_list += checker.run(data).first
125
+ end
126
+
127
+ error_list.each do |error|
128
+ puts "#{error[0]},#{error[3]},#{error[2]}"
129
+ end
@@ -0,0 +1,5 @@
1
+ module Anodator
2
+ # Basic error for anodator
3
+ class AnodatorError < StandardError; end
4
+ class InvalidConfiguration < AnodatorError ; end
5
+ end
@@ -0,0 +1,41 @@
1
+ require "anodator/rule"
2
+
3
+ module Anodator
4
+ class CheckResult
5
+ attr_reader :target_numbers, :message, :level
6
+
7
+ def initialize(target_numbers, message, level)
8
+ @target_numbers = target_numbers.to_a
9
+ @message = message.to_s
10
+ @level = level
11
+
12
+ if @target_numbers.size.zero?
13
+ raise ArgumentError.new("target numbers cannot be blank")
14
+ end
15
+ if @message.split(//).size.zero?
16
+ raise ArgumentError.new("message cannot be blank")
17
+ end
18
+ unless [Rule::LEVEL_ERROR, Rule::LEVEL_WARNING].include?(level)
19
+ raise ArgumentError.new("level must be ERROR or WARNING")
20
+ end
21
+ end
22
+
23
+ def to_s
24
+ buf = "[ ]\t"
25
+ if @level == Rule::LEVEL_WARNING
26
+ buf = "[WARING]\t"
27
+ else
28
+ buf = "[ERROR ]\t"
29
+ end
30
+ buf += @message + " |#{@target_numbers.join(", ")}|"
31
+ end
32
+
33
+ def error?
34
+ return Rule::LEVEL_ERROR == @level
35
+ end
36
+
37
+ def warning?
38
+ return Rule::LEVEL_WARNING == @level
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,58 @@
1
+ require "anodator/input_spec"
2
+ require "anodator/rule_set"
3
+ require "anodator/output_spec"
4
+ require "anodator/anodator_error"
5
+
6
+ module Anodator
7
+ 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]
12
+
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
22
+
23
+ Validator::Base.values = @input_spec
24
+
25
+ validate_configuration if configuration_check
26
+ end
27
+
28
+ def validate_configuration
29
+ # RuleSet
30
+ @rule_set.validate_configuration
31
+ # OutputSpec
32
+ @output_specs.each do |spec|
33
+ spec.validate_configuration
34
+ end
35
+ end
36
+
37
+ 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
41
+ @output_specs << output_spec
42
+
43
+ validate_configuration if configuration_check
44
+ end
45
+
46
+ def run(values)
47
+ @input_spec.source = values
48
+ @rule_set.check_all
49
+ @output_specs.map do |spec|
50
+ spec.generate(@input_spec, @rule_set.results)
51
+ end
52
+ end
53
+
54
+ def rule_info
55
+ @rule_set.to_s
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,199 @@
1
+ require "anodator/input_spec_item"
2
+ require "anodator/anodator_error"
3
+
4
+ module Anodator
5
+ class DuplicatedInputSpecItemError < AnodatorError; end
6
+ class UnknownTargetExpressionError < AnodatorError; end
7
+ class SourceDataNotProvidedError < AnodatorError; end
8
+
9
+ class InputSpec
10
+ CALCULATION_HOLDER_REGEXP = /\[\[([^\]]+)\]\]/
11
+
12
+ def initialize(spec_items = [])
13
+ @spec_items = []
14
+ @source = nil
15
+ @number_dict = { }
16
+ @name_dict = { }
17
+
18
+ unless spec_items.is_a? Array
19
+ raise ArgumentError.new("initialized by Array by Hash(key is :name and :number")
20
+ end
21
+
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
35
+ end
36
+ end
37
+
38
+ 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
45
+ @spec_items << spec
46
+ index = @spec_items.size - 1
47
+ @number_dict[spec.number] = { :item => spec, :index => index }
48
+ @name_dict[spec.name] = { :item => spec, :index => index }
49
+ end
50
+ private :push_spec_items
51
+
52
+ def source=(source)
53
+ if source.respond_to? :[]
54
+ @source = source
55
+ else
56
+ raise ArgumentError.new("source should respond to :[] method")
57
+ end
58
+ end
59
+
60
+ def clear_source
61
+ @source = nil
62
+ end
63
+
64
+ def value_at(index)
65
+ raise SourceDataNotProvidedError.new if @source.nil?
66
+
67
+ if @spec_items[index].nil?
68
+ raise UnknownTargetExpressionError.new("accessed by index '#{index}'")
69
+ else
70
+ return @source[index].to_s
71
+ end
72
+ end
73
+
74
+ def value_at_by_number(number)
75
+ raise SourceDataNotProvidedError.new if @source.nil?
76
+
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
82
+ end
83
+
84
+ def value_at_by_name(name)
85
+ raise SourceDataNotProvidedError.new if @source.nil?
86
+
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
92
+ end
93
+
94
+ def value_by_calculation(calculation)
95
+ 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
113
+
114
+ value = eval(calculation)
115
+ value = value.to_s("F") if value.is_a? BigDecimal
116
+
117
+ return value
118
+ rescue UnknownTargetExpressionError => ex
119
+ raise
120
+ rescue => ex
121
+ raise UnknownTargetExpressionError.new("accessed by calculation '#{calculation}'")
122
+ end
123
+ private :value_by_calculation
124
+
125
+ def [](target_expression)
126
+ raise SourceDataNotProvidedError.new if @source.nil?
127
+
128
+ if target_expression.is_a? Fixnum
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
138
+ end
139
+ end
140
+
141
+ 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
147
+ end
148
+
149
+ 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
155
+ end
156
+
157
+ 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
163
+ end
164
+
165
+ def spec_items_by_calculation(calculation)
166
+ holders = check_calculation_expression(calculation)
167
+ holders.map do |expression|
168
+ spec_item_by_expression(expression)
169
+ end
170
+ end
171
+ private :spec_items_by_calculation
172
+
173
+ def spec_item_by_expression(target_expression)
174
+ if target_expression.is_a? Fixnum
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
184
+ end
185
+ end
186
+
187
+ # return all holders specs
188
+ def check_calculation_expression(calculation)
189
+ if /(@|require|load|;)/.match calculation
190
+ return ArgumentError.new("Invalid calcuation expression '#{calcuation}'")
191
+ end
192
+
193
+ calculation.scan(CALCULATION_HOLDER_REGEXP).flatten.map do |target_expression|
194
+ spec_item_by_expression(target_expression)
195
+ next target_expression
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,33 @@
1
+ module Anodator
2
+ class InputSpecItem
3
+ TYPE_STRING = "STRING"
4
+ TYPE_NUMERIC = "NUMERIC"
5
+ TYPE_DATE = "DATE"
6
+
7
+ attr_reader :number, :name, :type
8
+
9
+ def initialize(number, name, type = TYPE_STRING)
10
+ if number.nil? || number.to_s.split(//).size.zero?
11
+ raise ArgumentError.new("number cannot be blank")
12
+ end
13
+ if name.nil? || name.to_s.split(//).size.zero?
14
+ raise ArgumentError.new("name cannot be blank")
15
+ end
16
+ unless [TYPE_STRING, TYPE_NUMERIC, TYPE_DATE].include?(type)
17
+ raise ArgumentError.new("unknown data type '#{type}'")
18
+ end
19
+
20
+ @number = number
21
+ @name = name
22
+ @type = type
23
+ end
24
+
25
+ def ==(other)
26
+ if other.is_a? InputSpecItem
27
+ self.number == other.number
28
+ else
29
+ return false
30
+ end
31
+ end
32
+ end
33
+ end