hash_engine 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +52 -0
- data/Guardfile +24 -0
- data/README.txt +320 -0
- data/Rakefile +3 -0
- data/hash_engine.gemspec +21 -0
- data/lib/hash_engine.rb +20 -0
- data/lib/hash_engine/actions.rb +82 -0
- data/lib/hash_engine/add_error.rb +13 -0
- data/lib/hash_engine/conditionals.rb +32 -0
- data/lib/hash_engine/csv_parse.rb +49 -0
- data/lib/hash_engine/extract.rb +139 -0
- data/lib/hash_engine/fetchers.rb +27 -0
- data/lib/hash_engine/format.rb +62 -0
- data/lib/hash_engine/transform.rb +196 -0
- data/spec/hash_engine/actions_spec.rb +146 -0
- data/spec/hash_engine/conditional_spec.rb +28 -0
- data/spec/hash_engine/csv_parse_spec.rb +42 -0
- data/spec/hash_engine/csv_transform_spec.rb +90 -0
- data/spec/hash_engine/ds_spec.rb +178 -0
- data/spec/hash_engine/extract_spec.rb +144 -0
- data/spec/hash_engine/fetchers_spec.rb +30 -0
- data/spec/hash_engine/format_spec.rb +55 -0
- data/spec/hash_engine/transform_spec.rb +365 -0
- data/spec/hash_engine_spec.rb +0 -0
- data/spec/spec_helper.rb +12 -0
- metadata +82 -0
data/lib/hash_engine.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'hash_engine/actions'
|
2
|
+
require 'hash_engine/fetchers'
|
3
|
+
require 'hash_engine/conditionals'
|
4
|
+
require 'hash_engine/format'
|
5
|
+
require 'hash_engine/extract'
|
6
|
+
require 'hash_engine/transform'
|
7
|
+
require 'hash_engine/csv_parse'
|
8
|
+
|
9
|
+
module HashEngine
|
10
|
+
extend Actions
|
11
|
+
extend Fetchers
|
12
|
+
extend Conditionals
|
13
|
+
extend Format
|
14
|
+
extend Extract
|
15
|
+
extend Transform
|
16
|
+
extend CSVParse
|
17
|
+
extend self
|
18
|
+
|
19
|
+
VERSION = '0.3.0'
|
20
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'hash_engine/format'
|
2
|
+
|
3
|
+
module HashEngine
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
include Format
|
7
|
+
|
8
|
+
@@actions = {}
|
9
|
+
|
10
|
+
def actions
|
11
|
+
@@actions
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_action name, &block
|
15
|
+
@@actions[name] = block
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid_action?(action)
|
19
|
+
actions.has_key?(action)
|
20
|
+
end
|
21
|
+
|
22
|
+
def action(type, field_data, fetched_data)
|
23
|
+
if valid_action?(type)
|
24
|
+
actions[type].call(fetched_data, field_data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
unprotected_proc = Proc.new do |data, action_data|
|
29
|
+
if action_data.is_a?(Proc)
|
30
|
+
action_data.call(*data)
|
31
|
+
else
|
32
|
+
eval(action_data).call(*data)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@@actions['proc'] = Proc.new do |data, action_data|
|
37
|
+
if data.is_a?(Array) && data.any?(&:nil?) || data.nil?
|
38
|
+
nil
|
39
|
+
else
|
40
|
+
unprotected_proc.call(data, action_data)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@@actions['unprotected_proc'] = unprotected_proc
|
45
|
+
|
46
|
+
@@actions['lookup_map'] = Proc.new do |key, hash|
|
47
|
+
if hash.has_key?(key) ||
|
48
|
+
hash.default ||
|
49
|
+
hash.default_proc then
|
50
|
+
hash[key]
|
51
|
+
elsif hash.has_key?('default') then
|
52
|
+
hash['default']
|
53
|
+
elsif hash.has_key?('default_to_key')
|
54
|
+
key
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@@actions['join'] = Proc.new {|data, sep| data.is_a?(Array) ? data.join(sep) : data }
|
59
|
+
@@actions['first_value'] = Proc.new { |data, first| data.is_a?(Array) ? data.detect {|f| !(f.nil? || f.empty?)} : data }
|
60
|
+
|
61
|
+
# 1.8.7 behavior
|
62
|
+
@@actions['max_length'] = Proc.new {|data, length| /\w/u.match(data.to_s).to_s }
|
63
|
+
# 1.9.x behavior
|
64
|
+
if RUBY_VERSION > "1.9"
|
65
|
+
@@actions['max_length'] = Proc.new {|data, length| data.to_s.slice(0, length.to_i) }
|
66
|
+
end
|
67
|
+
|
68
|
+
@@actions['format'] = Proc.new do |data, format_instructions|
|
69
|
+
# puts " Formatting data: #{data} with #{format_instructions}"
|
70
|
+
if format_instructions.is_a?(Hash) &&
|
71
|
+
format_instructions.has_key?('strftime') &&
|
72
|
+
data.respond_to?(:strftime) then
|
73
|
+
data.send(:strftime, format_instructions['strftime'])
|
74
|
+
elsif @@formats.has_key?(format_instructions)
|
75
|
+
@@formats[format_instructions][data]
|
76
|
+
else
|
77
|
+
data
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module HashEngine
|
2
|
+
module Conditionals
|
3
|
+
|
4
|
+
@@conditionals = {}
|
5
|
+
|
6
|
+
def conditionals
|
7
|
+
@@conditionals
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_conditional name, &block
|
11
|
+
@@conditionals[name] = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid_conditional?(conditional)
|
15
|
+
conditionals.has_key?(conditional)
|
16
|
+
end
|
17
|
+
|
18
|
+
def conditional(type, left_operand, right_operand)
|
19
|
+
if valid_conditional?(type)
|
20
|
+
conditionals[type].call(left_operand, right_operand)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@@conditionals['ne'] = Proc.new {|left, right| left != right }
|
25
|
+
@@conditionals['eq'] = Proc.new {|left, right| left == right }
|
26
|
+
@@conditionals['lt'] = Proc.new {|left, right| left < right }
|
27
|
+
@@conditionals['gt'] = Proc.new {|left, right| left > right }
|
28
|
+
@@conditionals['lteq'] = Proc.new {|left, right| left <= right }
|
29
|
+
@@conditionals['gteq'] = Proc.new {|left, right| left >= right }
|
30
|
+
@@conditionals['exist'] = Proc.new {|left, right| !!left }
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Code based on FasterCSV
|
2
|
+
# Created by James Edward Gray II on 2005-10-31.
|
3
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
4
|
+
#
|
5
|
+
# This is a cut down and simplified version for dealing with a single line
|
6
|
+
|
7
|
+
module HashEngine
|
8
|
+
module CSVParse
|
9
|
+
|
10
|
+
QUOTE_CHAR = '"'.freeze
|
11
|
+
DEFAULT_DELIMITER = ','.freeze
|
12
|
+
QUOTED_FIELD = Regexp.new /^"(.*)"$/
|
13
|
+
|
14
|
+
def parse_line(line, headers, delimiter=DEFAULT_DELIMITER)
|
15
|
+
delimiter = DEFAULT_DELIMITER if delimiter.empty?
|
16
|
+
result = {:error => []}
|
17
|
+
unless line.empty?
|
18
|
+
parse = line.chomp
|
19
|
+
csv = Array.new
|
20
|
+
current_field = String.new
|
21
|
+
field_quotes = 0
|
22
|
+
parse.split(delimiter, -1).each do |match|
|
23
|
+
if current_field.empty? && match.count(QUOTE_CHAR).zero?
|
24
|
+
csv << (match.empty? ? nil : match)
|
25
|
+
else
|
26
|
+
current_field << match
|
27
|
+
field_quotes += match.count(QUOTE_CHAR)
|
28
|
+
if field_quotes % 2 == 0
|
29
|
+
in_quotes = current_field[QUOTED_FIELD, 1] || current_field
|
30
|
+
current_field = in_quotes
|
31
|
+
current_field.gsub!(QUOTE_CHAR * 2, QUOTE_CHAR) # unescape contents
|
32
|
+
csv << current_field
|
33
|
+
current_field = String.new
|
34
|
+
field_quotes = 0
|
35
|
+
else # we found a quoted field that spans multiple lines
|
36
|
+
current_field << delimiter
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
if csv.size == headers.size
|
41
|
+
headers.each_with_index {|name, index| result[name] = csv[index] }
|
42
|
+
else
|
43
|
+
result[:error] = ["header.size: #{headers.size} parsed_data.size: #{csv.size}"]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'hash_engine/format'
|
2
|
+
|
3
|
+
module HashEngine
|
4
|
+
module Extract
|
5
|
+
include Format
|
6
|
+
|
7
|
+
@@reserved_keys ||= {'method' => true, 'method_args' => true, 'cast' => true, 'required' => true}.freeze
|
8
|
+
|
9
|
+
# Collects data from a root object based on the provided instructions hash.
|
10
|
+
# The data is returned as a flat hash. A hash is used to specify the set of
|
11
|
+
# attributes which should be included in the .
|
12
|
+
|
13
|
+
def extract(objects, instructions)
|
14
|
+
if instructions.nil? || instructions.empty?
|
15
|
+
# can't do anything
|
16
|
+
{:error => ['Missing instructions']}
|
17
|
+
else
|
18
|
+
objects_to_walk = instructions.keys
|
19
|
+
if objects.nil?
|
20
|
+
{:error => ['Missing object(s)']}
|
21
|
+
else
|
22
|
+
if objects.is_a?(Hash)
|
23
|
+
# hash path
|
24
|
+
objects_given = objects.keys
|
25
|
+
if delta = objects_to_walk - objects_given and
|
26
|
+
!delta.empty?
|
27
|
+
{:error => ["Missing object(s): #{(delta).sort.join(', ')}"]}
|
28
|
+
else
|
29
|
+
# instructions for objects a, b, c
|
30
|
+
# given objects a, b, c
|
31
|
+
fetch_objects(objects_hash, objects_to_walk, instructions)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
# single object path
|
35
|
+
if objects_to_walk.size > 1
|
36
|
+
{:error => ["Instructions given for #{objects_to_walk.sort.join(', ')} but only 1 object given"]}
|
37
|
+
else
|
38
|
+
# instructions for 1 object
|
39
|
+
# given 1 object
|
40
|
+
object_name = objects_to_walk.first
|
41
|
+
fetch_objects({object_name => objects}, objects_to_walk, instructions)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch_objects(objects_hash, objects_to_walk, instructions)
|
49
|
+
results = {:error => []}
|
50
|
+
objects_to_walk.each do |object_name|
|
51
|
+
instructions[object_name].each_pair do |field, field_instructions|
|
52
|
+
# Command keywords are reserved and will not be walked.
|
53
|
+
unless @@reserved_keys.has_key?(field)
|
54
|
+
fetch_value(field, field_instructions, objects_hash[object_name], object_name, instructions[object_name], results)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
results
|
59
|
+
end
|
60
|
+
|
61
|
+
def fetch_attributes(object, parent_name, object_hash, results)
|
62
|
+
object_hash.each_pair do |field, field_instructions|
|
63
|
+
# Command keywords are reserved and will not be walked.
|
64
|
+
unless @@reserved_keys.has_key?(field)
|
65
|
+
fetch_value(field, field_instructions, object, parent_name, object_hash, results)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def fetch_value(field, field_instructions, object, parent_name, object_hash, results)
|
71
|
+
# puts " Field: #{field} field_instructions #{field_instructions.inspect} object #{object.inspect}"
|
72
|
+
method = fetch_method(object, field, field_instructions)
|
73
|
+
# puts " Method: #{method.inspect}"
|
74
|
+
args = fetch_method_args(field, field_instructions)
|
75
|
+
if method
|
76
|
+
return_val = object.send(method, *args)
|
77
|
+
return_val = cast_value(return_val, field, field_instructions)
|
78
|
+
# check if we need to dive deeper into recursion
|
79
|
+
set_result_or_recurse(return_val, parent_name, field, field_instructions, results)
|
80
|
+
else
|
81
|
+
append_error_for_required_fields(results,
|
82
|
+
"#{parent_name} does not respond to any of: #{fetch_method_array(field, field_instructions).join(', ')}", field_instructions)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_result_or_recurse(return_val, parent_name, field, field_instructions, results)
|
87
|
+
if field_instructions && field_instructions.keys.any? {|k| !@@reserved_keys.has_key?(k) }
|
88
|
+
# yes, dive!
|
89
|
+
if return_val.nil?
|
90
|
+
append_error_for_required_fields(results, "Missing required field: #{parent_name}.#{field}", field_instructions)
|
91
|
+
else
|
92
|
+
fetch_attributes(return_val, "#{parent_name}.#{field}", field_instructions, results)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
# no, add to results
|
96
|
+
results[field] = return_val
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def cast_value(return_val, field, field_instructions)
|
101
|
+
if field_instructions && field_instructions.has_key?('cast')
|
102
|
+
return_val = format_value(return_val, field_instructions['cast'])
|
103
|
+
else
|
104
|
+
return_val
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def fetch_method_array(field, field_instructions)
|
109
|
+
# Don't use compact! => returns nil if unchanged instead of returning the unchanged array
|
110
|
+
[(field_instructions && field_instructions['method']), field, field+'?'].compact
|
111
|
+
end
|
112
|
+
|
113
|
+
# Identify the method to be called.
|
114
|
+
def fetch_method(object, field, field_instructions)
|
115
|
+
fetch_method_array(field, field_instructions).detect {|method|
|
116
|
+
object.respond_to?(method) }
|
117
|
+
end
|
118
|
+
|
119
|
+
def fetch_method_args(field, field_instructions)
|
120
|
+
if field_instructions && field_instructions['method_args']
|
121
|
+
# if args specified make sure its an array
|
122
|
+
if field_instructions['method_args'].is_a?(Array)
|
123
|
+
field_instructions['method_args']
|
124
|
+
else
|
125
|
+
[ field_instructions['method_args'] ]
|
126
|
+
end
|
127
|
+
else
|
128
|
+
[]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def append_error_for_required_fields(results, message, instructions)
|
133
|
+
# Only log an error for required fields.
|
134
|
+
if (!instructions || instructions.fetch('required', true))
|
135
|
+
results[:error] << message
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HashEngine
|
2
|
+
module Fetchers
|
3
|
+
@@fetchers = {}
|
4
|
+
|
5
|
+
def fetchers
|
6
|
+
@@fetchers
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_fetcher name, &block
|
10
|
+
@@fetchers[name] = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_fetcher?(fetcher)
|
14
|
+
fetchers.has_key?(fetcher)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetcher(type, field_data, customer_data)
|
18
|
+
if valid_fetcher?(type)
|
19
|
+
fetchers[type].call(field_data, customer_data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@@fetchers['input'] = Proc.new {|field, data| data[field] }
|
24
|
+
@@fetchers['literal'] = Proc.new {|field, data| field }
|
25
|
+
@@fetchers['data'] = Proc.new {|field, data| data.values_at(*field) }
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module HashEngine
|
4
|
+
module Format
|
5
|
+
|
6
|
+
@@formats = {}
|
7
|
+
|
8
|
+
def formats
|
9
|
+
@@formats
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_format name, &block
|
13
|
+
@@formats[name] = block
|
14
|
+
end
|
15
|
+
|
16
|
+
# leverage the fact that procs and hash have a common interface
|
17
|
+
def format(data, format_instructions)
|
18
|
+
if format_instructions.is_a?(Hash) &&
|
19
|
+
format_instructions.has_key?('strftime') &&
|
20
|
+
data.respond_to?(:strftime) then
|
21
|
+
data.send(:strftime, format_instructions['strftime'])
|
22
|
+
elsif formats.has_key?(format_instructions)
|
23
|
+
formats[format_instructions][data]
|
24
|
+
else
|
25
|
+
data
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# \W includes '_0-9'
|
30
|
+
@@formats['alpha'] = Proc.new {|data| data.to_s.gsub(/[^A-Za-z]/,'') }
|
31
|
+
|
32
|
+
# \W includes '_'
|
33
|
+
@@formats['alphanumeric'] = Proc.new {|data| data.to_s.gsub(/[^A-Za-z0-9]/,'') }
|
34
|
+
@@formats['no_whitespace'] = Proc.new {|data| data.to_s.gsub(/[^A-Za-z0-9-]/,'') }
|
35
|
+
@@formats['numeric'] = Proc.new {|data| data.to_s.gsub(/\D/,'') }
|
36
|
+
@@formats['string'] = Proc.new {|data| data.to_s.strip }
|
37
|
+
|
38
|
+
# 1.8.7 behavior
|
39
|
+
@@formats['first'] = Proc.new {|data| /\w/u.match(data.to_s).to_s }
|
40
|
+
|
41
|
+
# 1.9.x behavior
|
42
|
+
if RUBY_VERSION > "1.9"
|
43
|
+
@@formats['first'] = Proc.new {|data| data.to_s[0] }
|
44
|
+
end
|
45
|
+
|
46
|
+
@@formats['float'] = Proc.new {|data| data.to_f }
|
47
|
+
@@formats['upcase'] = Proc.new {|data| data.to_s.upcase }
|
48
|
+
@@formats['downcase'] = Proc.new {|data| data.to_s.downcase }
|
49
|
+
@@formats['capitalize'] = Proc.new {|data| data.to_s.capitalize }
|
50
|
+
@@formats['reverse'] = Proc.new {|data| data.to_s.reverse }
|
51
|
+
@@formats['date'] = Proc.new {|data| Date.parse(data) rescue data }
|
52
|
+
|
53
|
+
int_lookup = Hash.new {|hash, key| hash[key] = key.to_i }
|
54
|
+
int_lookup[true] = 1
|
55
|
+
int_lookup[false] = 0
|
56
|
+
int_lookup[nil] = 0
|
57
|
+
@@formats['integer'] = int_lookup
|
58
|
+
|
59
|
+
@@formats['boolean'] = Hash.new {|hash, key|
|
60
|
+
hash[key] = ['true', 't', 'yes', 'y', '1'].include?(key.to_s.downcase) }
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'hash_engine/format'
|
2
|
+
require 'hash_engine/actions'
|
3
|
+
require 'hash_engine/add_error'
|
4
|
+
require 'hash_engine/fetchers'
|
5
|
+
require 'hash_engine/csv_parse'
|
6
|
+
#require 'csv'
|
7
|
+
|
8
|
+
module HashEngine
|
9
|
+
module Transform
|
10
|
+
include Format
|
11
|
+
include Actions
|
12
|
+
include AddError
|
13
|
+
include Fetchers
|
14
|
+
include CSVParse
|
15
|
+
|
16
|
+
DEFAULT_INSTRUCTIONS = {'default_value' => '',
|
17
|
+
'allow_nil' => false,
|
18
|
+
'suppress_nil' => true,
|
19
|
+
'allow_blank' => false,
|
20
|
+
'suppress_blank' => true,
|
21
|
+
'long_error' => true,
|
22
|
+
'copy_source' => false,
|
23
|
+
'delimiter' => DEFAULT_DELIMITER,
|
24
|
+
'quiet' => false }
|
25
|
+
|
26
|
+
def nil_check(instructions, value)
|
27
|
+
instructions['allow_nil'] == false && value.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def blank_check(instructions, value)
|
31
|
+
instructions['allow_blank'] == false && value.is_a?(String) && value !~ /\S/
|
32
|
+
end
|
33
|
+
|
34
|
+
def required_check(field_hash)
|
35
|
+
!(field_hash['optional'] == true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def suppress_nil(instructions, value)
|
39
|
+
instructions['suppress_nil'] == true && value.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def suppress_blank(instructions, value)
|
43
|
+
instructions['suppress_blank'] == true && value.is_a?(String) && value !~ /\S/
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_or_suppress(value, field_name, field_hash, instructions, result)
|
47
|
+
if (nil_check(instructions, value) || blank_check(instructions, value)) &&
|
48
|
+
required_check(field_hash) then
|
49
|
+
add_error(result[:error], "required field '#{field_name}' missing", field_name)
|
50
|
+
result[field_name] = instructions['default_value']
|
51
|
+
else
|
52
|
+
unless suppress_nil(instructions, value) || suppress_blank(instructions, value)
|
53
|
+
result[field_name] = value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def transform(data, passed_instructions)
|
59
|
+
instructions = DEFAULT_INSTRUCTIONS.merge(passed_instructions)
|
60
|
+
if instructions['fields']
|
61
|
+
# continue
|
62
|
+
result = if instructions['copy_source']
|
63
|
+
data.merge(:error => [])
|
64
|
+
else
|
65
|
+
{:error => []}
|
66
|
+
end
|
67
|
+
result[:error].push(instructions['long_error'] ? :long : :short)
|
68
|
+
instructions['fields'].each_pair do |field_name, field_hash|
|
69
|
+
value = get_value(field_name, field_hash, data, result[:error])
|
70
|
+
default_or_suppress(value, field_name, field_hash, instructions, result)
|
71
|
+
end
|
72
|
+
# remove the :long/:short instruction
|
73
|
+
result[:error].shift
|
74
|
+
else
|
75
|
+
result = {:error => ["Missing instructions"]}
|
76
|
+
end
|
77
|
+
result.delete(:error) if instructions['quiet']
|
78
|
+
result
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Given String:
|
83
|
+
# ok|http://www.domain.com|1234567890
|
84
|
+
# Given Instructions:
|
85
|
+
# deliminator: '|'
|
86
|
+
# hash_keys:
|
87
|
+
# - status:
|
88
|
+
# lookup_map:
|
89
|
+
# ok: accepted
|
90
|
+
# decline: reject
|
91
|
+
# default: error
|
92
|
+
# - payload:
|
93
|
+
# - uuid:
|
94
|
+
# Return:
|
95
|
+
# status: accepted
|
96
|
+
# payload: http://www.domain.com
|
97
|
+
# uuid: 1234567890
|
98
|
+
|
99
|
+
def csv_transform(data_string, passed_instructions, additional_data={})
|
100
|
+
instructions = DEFAULT_INSTRUCTIONS.merge(passed_instructions)
|
101
|
+
result = {:error => ["Missing CSV instructions"]}
|
102
|
+
if instructions['header']
|
103
|
+
result = parse_line data_string, instructions['header'], instructions['delimiter']
|
104
|
+
result = transform(result.merge(additional_data), instructions) if result[:error].empty?
|
105
|
+
end
|
106
|
+
result.delete(:error) if instructions['quiet']
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def simple_instructions(field_name, field_hash, source_data, error_array)
|
112
|
+
instructions = []
|
113
|
+
field_hash.each_pair do |key, value|
|
114
|
+
case
|
115
|
+
when key == 'conditional_eval'
|
116
|
+
instructions.unshift(key => value)
|
117
|
+
when key == 'subgroup'
|
118
|
+
instructions.unshift(key => value)
|
119
|
+
when valid_fetcher?(key)
|
120
|
+
instructions.unshift(key => value)
|
121
|
+
when valid_action?(key)
|
122
|
+
instructions.push(key => value)
|
123
|
+
else
|
124
|
+
add_error(error_array, "Invalid operation '#{key.inspect}' in transform for field '#{field_name}'", field_name)
|
125
|
+
data = nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
process_instructions(field_name, instructions, source_data, error_array)
|
129
|
+
end
|
130
|
+
|
131
|
+
def concat(data, fetched)
|
132
|
+
if data.nil?
|
133
|
+
fetched
|
134
|
+
elsif data.is_a?(Array) && fetched.is_a?(Array)
|
135
|
+
data + fetched
|
136
|
+
elsif data.is_a?(Array)
|
137
|
+
data.push fetched
|
138
|
+
elsif fetched.is_a?(Array)
|
139
|
+
fetched.unshift(data)
|
140
|
+
else
|
141
|
+
[data, fetched]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def conditional_eval(field_name, instructions, source_data, error_array)
|
146
|
+
left_operand = get_value("#{field_name}_conditional_eval_left_operand",
|
147
|
+
instructions['left_operand'], source_data, error_array)
|
148
|
+
right_operand = get_value("#{field_name}_conditional_eval_right_operand",
|
149
|
+
instructions['right_operand'], source_data, error_array)
|
150
|
+
if conditional(instructions['operator'], left_operand, right_operand)
|
151
|
+
get_value("#{field_name}_conditional_eval_true_instructions",
|
152
|
+
instructions['true_instructions'], source_data, error_array)
|
153
|
+
else
|
154
|
+
get_value("#{field_name}_conditional_eval_false_instructions",
|
155
|
+
instructions['false_instructions'], source_data, error_array)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def process_instructions(field_name, instruction_array, source_data, error_array)
|
160
|
+
data = nil
|
161
|
+
instruction_array.each do |instruction_hash|
|
162
|
+
(instruction, instruction_data) = instruction_hash.first
|
163
|
+
# puts " Evaluating instruction: #{instruction} with instruction_data: #{instruction_data}"
|
164
|
+
case
|
165
|
+
when instruction == 'subgroup'
|
166
|
+
fetched = get_value(field_name, instruction_data, source_data, error_array)
|
167
|
+
data = concat(data, fetched)
|
168
|
+
when instruction == 'conditional_eval'
|
169
|
+
fetched = conditional_eval(field_name, instruction_data, source_data, error_array)
|
170
|
+
data = concat(data, fetched)
|
171
|
+
when valid_fetcher?(instruction)
|
172
|
+
fetched = fetcher(instruction, instruction_data, source_data)
|
173
|
+
data = concat(data, fetched)
|
174
|
+
when valid_action?(instruction)
|
175
|
+
data = action(instruction, instruction_data, data)
|
176
|
+
else
|
177
|
+
add_error(error_array, "Invalid operation '#{instruction_hash.inspect}' in transform for field '#{field_name}'", field_name)
|
178
|
+
data = nil
|
179
|
+
break
|
180
|
+
end
|
181
|
+
end
|
182
|
+
data
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_value(field_name, field_data, source_data, error_array)
|
186
|
+
case field_data
|
187
|
+
when Array
|
188
|
+
process_instructions(field_name, field_data, source_data, error_array)
|
189
|
+
when Hash
|
190
|
+
simple_instructions(field_name, field_data, source_data, error_array)
|
191
|
+
else
|
192
|
+
fetcher('input', field_data, source_data)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|