hash_engine 0.3.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 +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
|