yahm 0.0.13 → 0.1.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 +4 -4
- data/CHANGELOG.md +85 -0
- data/Gemfile +5 -0
- data/README.md +42 -74
- data/lib/yahm.rb +132 -1
- data/lib/yahm/mapping.rb +50 -51
- data/lib/yahm/version.rb +1 -1
- data/spec/spec_helper.rb +4 -1
- data/spec/yahm/mapping_spec.rb +64 -33
- data/spec/yahm_spec.rb +88 -3
- data/yahm.gemspec +3 -5
- metadata +14 -58
- data/lib/yahm/hash_mapper.rb +0 -20
- data/spec/yahm/hash_mapper_spec.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f91884e21f0dc68a1777f51cda708d53f053608
|
4
|
+
data.tar.gz: c3ad59e14872e528f9a19d3b203645dbc9f9d36e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d57cb78535fd2ef77a611088ed6311d0932b406e27b366de9f9ddc7477837ca14c7bbfb3814f3ce7f36a430e808186fb088528addbc979e151bcd6e0c0933ae
|
7
|
+
data.tar.gz: a59e91fd750bd9640cee0e3d71bb989fa7a6b8fafdf0748f927f725373344ff7e03649b18e92d5133edac055992c379bc314d592b890d8b8dddaba9cc1afce54
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
## 0.0.x => 0.1.x
|
2
|
+
|
3
|
+
### `Yahm::Mapping`
|
4
|
+
|
5
|
+
#### Constructor now takes a block directly
|
6
|
+
```ruby
|
7
|
+
|
8
|
+
require "yahm"
|
9
|
+
|
10
|
+
mapping = Yahm::Mapping.new do
|
11
|
+
map "/foo", to: "/bar"
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
#### Constructor takes a new option ```:context```, which is used for lambda/proc method resolution
|
16
|
+
```ruby
|
17
|
+
some_class = Class.new do
|
18
|
+
require "yahm"
|
19
|
+
|
20
|
+
def some_method(value)
|
21
|
+
value.to_s + "bar"
|
22
|
+
end
|
23
|
+
|
24
|
+
def mapping
|
25
|
+
@mapping ||= Yahm::Mapping.new(context: self) do
|
26
|
+
map "/foo", to: "/bar", processed_by: lambda { |v| some_method(v) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def apply_mapping_to(hash)
|
31
|
+
mapping.apply_to(hash)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
some_class.new.apply_mapping_to({foo: "foo"})) # => {:bar=>"foobar"}
|
36
|
+
```
|
37
|
+
|
38
|
+
#### Renamed `translate_hash` to `apply_to`
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
|
42
|
+
require "yahm"
|
43
|
+
|
44
|
+
mapping = Yahm::Mapping.new do
|
45
|
+
map "/foo", to: "/bar"
|
46
|
+
end
|
47
|
+
|
48
|
+
# before
|
49
|
+
# mapping.translate_hash({foo:1})
|
50
|
+
|
51
|
+
# now
|
52
|
+
mapping.apply_to({foo:1})
|
53
|
+
```
|
54
|
+
|
55
|
+
#### Lambdas/Procs as to: option now take two parameters ```|value, result|```
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
require "yahm"
|
59
|
+
|
60
|
+
Yahm::Mapping.new do
|
61
|
+
map "/source", to: lambda { |v, result| result.merge!(target: true) }
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
### Removed `Yahm::HashMapper`
|
66
|
+
|
67
|
+
The modul was removed, because mappings can be stored and executed with plain ruby class/instance methods without the need for a seperate modul to extend classes with.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
require "yahm"
|
71
|
+
|
72
|
+
class Foo
|
73
|
+
def initialize(hash)
|
74
|
+
@hash = hash
|
75
|
+
end
|
76
|
+
|
77
|
+
@mapping = Yahm::Mapping.new do
|
78
|
+
map "/foo", to: "/bar"
|
79
|
+
end
|
80
|
+
|
81
|
+
def apply_mapping_to(hash = @hash)
|
82
|
+
@mapping.apply_to(hash)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -18,7 +18,7 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## Dependencies
|
20
20
|
|
21
|
-
|
21
|
+
* thread (https://github.com/meh/ruby-thread) for threading support
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
@@ -28,16 +28,12 @@ None
|
|
28
28
|
```ruby
|
29
29
|
require "yahm"
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
define_mapper :from_other_record do
|
35
|
-
map "/record/id", to: "/id"
|
36
|
-
end
|
31
|
+
mapping = Yahm::Mapping.new do
|
32
|
+
map "/foo", to: "/bar"
|
37
33
|
end
|
38
34
|
|
39
|
-
|
40
|
-
=> {
|
35
|
+
mapping.apply_to({foo: 1})
|
36
|
+
# => {:bar=>1}
|
41
37
|
```
|
42
38
|
|
43
39
|
### More advanced examples
|
@@ -46,90 +42,62 @@ Record.new.from_other_record({ ... })
|
|
46
42
|
require "yahm"
|
47
43
|
|
48
44
|
class Record
|
49
|
-
|
50
|
-
|
45
|
+
|
46
|
+
def initialize(hash)
|
47
|
+
@record = mapping.apply_to(hash)
|
48
|
+
end
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
50
|
+
private
|
51
|
+
|
52
|
+
def lookup_id(id)
|
53
|
+
# ...
|
54
|
+
end
|
55
|
+
|
56
|
+
def mapping
|
57
|
+
@mapping ||= Yahm::Mapping.new(context: self) do
|
58
|
+
map "/record/id", to: "/id", processed_by: lambda { |id| lookup_id(id) }
|
59
|
+
map "/record/count", to: "/count", processed_by: proc { |count| count.to_i }
|
60
|
+
map "/record/subject[0]", to: "/subjects/most_important_subject"
|
61
|
+
map "/record/languages[]", to: "/languages", force_array: true
|
62
|
+
map "/record/authors", to: "/authors", split_by: ";"
|
63
|
+
map "/record/version", to: "/version", default: 1
|
64
|
+
map "/record/label", to: lambda { |label, result| result[:label] = label }
|
65
|
+
end
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
62
|
-
Record.new
|
63
|
-
=> { :id => "...", :count => ..., :subjects => { :most_important_subject => "..."}, ... }
|
64
|
-
|
65
|
-
Record.translated_hash
|
69
|
+
Record.new({ ... })
|
66
70
|
=> { :id => "...", :count => ..., :subjects => { :most_important_subject => "..."}, ... }
|
67
71
|
```
|
72
|
+
### Options
|
68
73
|
|
69
|
-
####
|
74
|
+
#### `to: [string|lambda|proc]`
|
70
75
|
|
71
|
-
The `to` option of a mapping rule can either be a string, naming the path in the resultung hash or a proc, which is executed
|
76
|
+
The `to` option of a mapping rule can either be a string, naming the path in the resultung hash or a lambda/proc, which is executed within the created mapper. When a `context` option was supplied to `#new` or `apply_to` methods are also resolved against this scope.
|
72
77
|
|
73
|
-
|
74
|
-
require "yahm"
|
78
|
+
#### `default: [value]`
|
75
79
|
|
76
|
-
|
77
|
-
extend Yahm::HashMapper
|
78
|
-
|
79
|
-
define_mapper :from_other_record do
|
80
|
-
def funky_method(value)
|
81
|
-
@result[:is_funky] = true if funky?(value)
|
82
|
-
end
|
83
|
-
|
84
|
-
map "/record/count", to: "count"
|
85
|
-
map "/record/name", to: lambda { |v| funky_method(v) }
|
86
|
-
end
|
87
|
-
end
|
88
|
-
```
|
80
|
+
If a value would be `nil` and `default` is given, the `default` value is used instead of `nil`.
|
89
81
|
|
90
|
-
####
|
82
|
+
#### `force_array: [true|false]`
|
91
83
|
|
92
|
-
|
84
|
+
This ensures, that the value written in the result ist always a hash. Even when the result would be `nil`, an empty array is created.
|
93
85
|
|
86
|
+
#### `processed_by: [lambda|proc]`
|
94
87
|
|
95
|
-
|
96
|
-
require "yahm"
|
88
|
+
You can pass a lambda/proc as `processed_by` option. Inside of lambdas or procs, `self` corresponds to the context of the defined mapper. When a `context` option was supplied to `#new` or `apply_to` methods are also resolved against this scope.
|
97
89
|
|
98
|
-
|
99
|
-
extend Yahm::HashMapper
|
100
|
-
|
101
|
-
def process_bool_string(string)
|
102
|
-
string == "true" ? true : (string == "false" ? false : nil)
|
103
|
-
end
|
104
|
-
|
105
|
-
define_mapper :from_other_record do
|
106
|
-
def cheap?(price)
|
107
|
-
price.to_i < 20
|
108
|
-
end
|
109
|
-
|
110
|
-
map "/record/id", to: "id", processed_by: :to_i
|
111
|
-
map "/record/count", to: "count", processed_by: proc { |v| v.to_i }
|
112
|
-
map "/record/subject", to: "subjects", processed_by: :downcase
|
113
|
-
map "/record/price", to: "is_cheap", processed_by: lambda { |v| cheap?(v) }
|
114
|
-
map "/record/is_available", to: "available", processed_by: lambda { |v| _self.process_bool_string(v) }
|
115
|
-
end
|
116
|
-
end
|
117
|
-
```
|
90
|
+
#### `split_by: [string]`
|
118
91
|
|
119
|
-
|
92
|
+
If a value is a string and `split_by` is supplied, the string is splitted by the given character and an array would be returned.
|
120
93
|
|
121
|
-
|
94
|
+
#### `use_threads: [true|false]` [experimental]
|
122
95
|
|
123
|
-
|
96
|
+
Use a thread pool to execute to rules of a mapping. The current implementation is very naiv. No locking of shared data etc. Use this *only* if *you know* what you are doing and if your rules a complex enough to compensate the overhead introduced by threading.
|
124
97
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
define_mapper :my_private_mapper, private: true do
|
129
|
-
map "/some/internals", to: "internals"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
```
|
98
|
+
#### `thread_pool_size: [number]` [experimental]
|
99
|
+
|
100
|
+
The number of threads used to execute rules if `use_threads: true`.
|
133
101
|
|
134
102
|
## Related work
|
135
103
|
|
data/lib/yahm.rb
CHANGED
@@ -1,6 +1,137 @@
|
|
1
1
|
require "yahm/version"
|
2
2
|
|
3
3
|
class Yahm
|
4
|
-
require "yahm/hash_mapper"
|
5
4
|
require "yahm/mapping"
|
5
|
+
|
6
|
+
def self.get(object, path)
|
7
|
+
current_path_element, remaining_path = split_path(path)
|
8
|
+
|
9
|
+
if hash_path?(current_path_element) && object.is_a?(Hash)
|
10
|
+
key = current_path_element[1..-1]
|
11
|
+
|
12
|
+
# handle cases where one forces symbol/string keys
|
13
|
+
value =
|
14
|
+
if key.start_with?(":")
|
15
|
+
object[key[1..-1].to_sym]
|
16
|
+
elsif key.start_with?('"') && key.end_with?('"')
|
17
|
+
object[key[1..-2]]
|
18
|
+
else
|
19
|
+
object[key.to_sym] || object[key.to_s]
|
20
|
+
end
|
21
|
+
|
22
|
+
unless value.nil?
|
23
|
+
remaining_path ? self.get(value, remaining_path) : value
|
24
|
+
else
|
25
|
+
value
|
26
|
+
end
|
27
|
+
elsif array_path?(current_path_element) && object.is_a?(Array)
|
28
|
+
index_parameter = current_path_element.slice(1..-2)
|
29
|
+
|
30
|
+
index =
|
31
|
+
if index_parameter == ""
|
32
|
+
0..object.length
|
33
|
+
else
|
34
|
+
index_parameter.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
if remaining_path
|
38
|
+
unless object[index].nil?
|
39
|
+
map_result = object[index.is_a?(Range) ? index : index..index].map do |array_element|
|
40
|
+
self.get(array_element, remaining_path)
|
41
|
+
end.compact
|
42
|
+
|
43
|
+
map_result == [] ? nil : map_result.flatten(array_depth(map_result) - 1)
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
else
|
48
|
+
object[index]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.set(object, path, value)
|
54
|
+
current_path_element, remaining_path = split_path(path)
|
55
|
+
next_path_element = split_path(remaining_path)[0]
|
56
|
+
|
57
|
+
if hash_path?(current_path_element) && object.is_a?(Hash)
|
58
|
+
key = current_path_element[1..-1]
|
59
|
+
|
60
|
+
# handle cases where one forces symbol/string keys
|
61
|
+
key =
|
62
|
+
if key.start_with?(":")
|
63
|
+
key[1..-1].to_sym
|
64
|
+
elsif key.start_with?('"') && key.end_with?('"')
|
65
|
+
key[1..-2]
|
66
|
+
else
|
67
|
+
key.to_sym # keys should be symbols per default
|
68
|
+
end
|
69
|
+
|
70
|
+
if next_path_element.nil?
|
71
|
+
object[key] = value
|
72
|
+
elsif hash_path?(next_path_element)
|
73
|
+
self.set(object[key] || object[key] = {}, remaining_path, value)
|
74
|
+
elsif array_path?(next_path_element)
|
75
|
+
self.set(object[key] || object[key] = [], remaining_path, value)
|
76
|
+
end
|
77
|
+
elsif array_path?(current_path_element) && object.is_a?(Array)
|
78
|
+
if hash_path?(next_path_element)
|
79
|
+
object.push(_object = {})
|
80
|
+
self.set(_object, remaining_path, value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
private
|
87
|
+
#
|
88
|
+
def self.array_depth(array)
|
89
|
+
depth = 1
|
90
|
+
until array == (flattend_array = array.flatten(1))
|
91
|
+
depth += 1
|
92
|
+
array = flattend_array
|
93
|
+
end
|
94
|
+
|
95
|
+
depth
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.array_path?(path)
|
99
|
+
path[0] == "["
|
100
|
+
end
|
101
|
+
|
102
|
+
def decode_string_or_symbol_from(string)
|
103
|
+
if string.start_with?(":")
|
104
|
+
string[1..-1].to_sym
|
105
|
+
elsif string.start_with?('"') && string.end_with?('"')
|
106
|
+
string[1..-2]
|
107
|
+
else
|
108
|
+
string.to_sym # defaults to symbols
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.hash_path?(path)
|
113
|
+
path[0] == "/"
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.split_path(path)
|
117
|
+
unless path.nil?
|
118
|
+
path_elements = path
|
119
|
+
.gsub(/(\/)|(\[)/,
|
120
|
+
"/" => " /",
|
121
|
+
"[" => " [",
|
122
|
+
)
|
123
|
+
.split(" ")
|
124
|
+
|
125
|
+
remaining_path =
|
126
|
+
if path_elements.length > 1
|
127
|
+
path_elements.slice(1..-1).join
|
128
|
+
else
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
[path_elements.first, remaining_path]
|
133
|
+
else
|
134
|
+
[nil, nil]
|
135
|
+
end
|
136
|
+
end
|
6
137
|
end
|
data/lib/yahm/mapping.rb
CHANGED
@@ -1,76 +1,75 @@
|
|
1
|
+
require "thread/pool"
|
2
|
+
|
1
3
|
class Yahm::Mapping
|
2
4
|
|
3
|
-
|
5
|
+
attr_accessor :thread_pool_size
|
6
|
+
attr_accessor :use_threads
|
4
7
|
|
5
8
|
def initialize(options = {}, &block)
|
9
|
+
@context = options[:context]
|
6
10
|
@rules = []
|
7
11
|
@result = nil
|
8
|
-
|
9
|
-
|
12
|
+
@use_threads = options[:use_threads] || false
|
13
|
+
@thread_pool_size = options[:thread_pool_size] || 2
|
10
14
|
|
11
|
-
|
12
|
-
@rules.push [str, options]
|
13
|
-
self
|
15
|
+
self.instance_eval(&block) if block
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
17
|
-
@_self = options[:_self]
|
18
|
+
def apply_to(input_hash, options = {})
|
18
19
|
@result = {}
|
19
20
|
|
20
|
-
@
|
21
|
-
|
21
|
+
if @use_threads
|
22
|
+
thread_pool = Thread.pool(@thread_pool_size) if @use_threads
|
23
|
+
|
24
|
+
@rules.each do |rule|
|
25
|
+
thread_pool.process do
|
26
|
+
apply_rule(rule, input_hash, @result)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
thread_pool.shutdown
|
31
|
+
else
|
32
|
+
@rules.each do |rule|
|
33
|
+
apply_rule(rule, input_hash, @result)
|
34
|
+
end
|
22
35
|
end
|
23
36
|
|
24
37
|
@result
|
25
38
|
end
|
26
39
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
.split("/")
|
36
|
-
.map do |element|
|
37
|
-
if element[/\A\d+\Z/]
|
38
|
-
element.to_i
|
39
|
-
else
|
40
|
-
element.to_sym
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
if (to_option = rule.last[:to]).is_a?(String)
|
45
|
-
sanitized_target_path = rule.last[:to].sub(/^\//, "").split("/").map(&:to_sym)
|
46
|
-
target_parent_path = sanitized_target_path.slice(0, sanitized_target_path.length - 1)
|
40
|
+
#
|
41
|
+
# dynamic context method resolutuon
|
42
|
+
#
|
43
|
+
def method_missing(sym, *args, &block)
|
44
|
+
if @context.respond_to?(sym)
|
45
|
+
@context.send(sym, *args, &block)
|
46
|
+
else
|
47
|
+
super
|
47
48
|
end
|
49
|
+
end
|
48
50
|
|
49
|
-
|
51
|
+
def respond_to_missing?(sym, *)
|
52
|
+
@context.respond_to?(sym) || super
|
53
|
+
end
|
50
54
|
|
51
|
-
|
52
|
-
source_value = if symbol_or_callable.is_a?(Symbol)
|
53
|
-
source_value.send(symbol_or_callable)
|
54
|
-
else
|
55
|
-
symbol_or_callable.call(source_value)
|
56
|
-
end
|
57
|
-
end
|
55
|
+
private
|
58
56
|
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
def map(path, options = {})
|
58
|
+
@rules.push({ path: path }.merge(options))
|
59
|
+
self
|
60
|
+
end
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
def apply_rule(rule, input_hash, output_hash)
|
63
|
+
value = Yahm.get(input_hash, rule[:path])
|
64
|
+
value = rule[:default] && value.nil? ? rule[:default] : value
|
65
|
+
value = rule[:force_array] ? (value.nil? ? [] : [value].flatten(1)) : value
|
66
|
+
value = rule[:split_by] && value.is_a?(String) ? value.split(rule[:split_by]).map!(&:strip) : value
|
67
|
+
value = rule[:processed_by] ? rule[:processed_by].call(value) : value
|
66
68
|
|
67
|
-
if
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
})
|
72
|
-
elsif to_option.is_a?(Proc)
|
73
|
-
instance_exec(source_value, &to_option)
|
69
|
+
if rule[:to].is_a?(Proc)
|
70
|
+
instance_exec(value, output_hash, &rule[:to])
|
71
|
+
else
|
72
|
+
Yahm.set(output_hash, rule[:to], value)
|
74
73
|
end
|
75
74
|
end
|
76
75
|
end
|
data/lib/yahm/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
data/spec/yahm/mapping_spec.rb
CHANGED
@@ -1,32 +1,54 @@
|
|
1
1
|
require_relative "../spec_helper"
|
2
2
|
|
3
3
|
describe Yahm::Mapping do
|
4
|
-
|
5
|
-
|
4
|
+
describe "#new" do
|
5
|
+
it "can be called with a block, containing the mapping described by the DSL" do
|
6
|
+
mapping = Yahm::Mapping.new do
|
7
|
+
map "/foo", to: "/bar"
|
8
|
+
end
|
9
|
+
|
10
|
+
expect(mapping.apply_to({foo:1})).to eq({:bar=>1})
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when called with a \"context\" option" do
|
14
|
+
it "passes methods from withing the mapping (e.g. procs/lambdas) to the context" do
|
15
|
+
some_class = Class.new do
|
16
|
+
require "yahm"
|
6
17
|
|
7
|
-
|
8
|
-
|
18
|
+
def some_method(value)
|
19
|
+
value.to_s + "bar"
|
20
|
+
end
|
9
21
|
|
10
|
-
|
11
|
-
|
12
|
-
|
22
|
+
def mapping
|
23
|
+
@mapping ||= Yahm::Mapping.new(context: self) do
|
24
|
+
map "/foo", to: "/bar", processed_by: lambda { |v| some_method(v) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply_mapping_to(hash)
|
29
|
+
mapping.apply_to(hash)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
expect(some_class.new.apply_mapping_to({foo: "foo"})).to eq({:bar=>"foobar"})
|
34
|
+
end
|
13
35
|
end
|
14
36
|
end
|
15
|
-
|
16
|
-
describe ".
|
37
|
+
|
38
|
+
describe ".apply_to" do
|
17
39
|
let(:mapping) do
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
40
|
+
Yahm::Mapping.new do
|
41
|
+
map "/record_id", to: "/id"
|
42
|
+
map "/record/title", to: "/title"
|
43
|
+
map "/record/isbns", to: "/my_data/isbns"
|
44
|
+
map "/record/isbns[1]", to: "/main_isbn" # when an array, one can specifiy which element to choose
|
45
|
+
map "/record/count", to: "/count", processed_by: lambda { |v| v.to_i }
|
46
|
+
map "/record/languages", to: "/languages", force_array: true
|
47
|
+
map "/record/authors", to: "/authors", split_by: ";"
|
48
|
+
map "/record/version", to: "/version", default: 1
|
49
|
+
map "/record/creators", to: "/creators", force_array: true # when source value is nil, there should be an empty array in the target hash
|
50
|
+
map "/record/publishers", to: "/publishers", split_by: ";" # if there are is no source value, this should result to nil in the target hash
|
51
|
+
end
|
30
52
|
end
|
31
53
|
|
32
54
|
let(:input_hash) do
|
@@ -45,10 +67,10 @@ describe Yahm::Mapping do
|
|
45
67
|
}
|
46
68
|
end
|
47
69
|
|
48
|
-
|
49
|
-
|
70
|
+
let(:expected_result) do
|
71
|
+
{
|
50
72
|
:id=>"some_id123",
|
51
|
-
:
|
73
|
+
:title=>"some title",
|
52
74
|
:my_data=>{:isbns=>["3-86680-192-0", "3-680-08783-7"]},
|
53
75
|
:main_isbn=>"3-680-08783-7",
|
54
76
|
:count=>3,
|
@@ -57,48 +79,57 @@ describe Yahm::Mapping do
|
|
57
79
|
:version=>1,
|
58
80
|
:creators=>[],
|
59
81
|
:publishers=>nil
|
60
|
-
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "translates a given hash according to @rules" do
|
86
|
+
expect(mapping.apply_to(input_hash)).to eq(expected_result)
|
61
87
|
end
|
62
88
|
|
89
|
+
context "when threading is enabled" do
|
90
|
+
it "translates a given hash according to @rules" do
|
91
|
+
mapping.use_threads = true
|
92
|
+
expect(mapping.apply_to(input_hash)).to eq(expected_result)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
63
96
|
it "does not apply \"split_by\" if source value is an array" do
|
64
|
-
expect(Yahm::Mapping.new
|
97
|
+
expect(Yahm::Mapping.new { map "/source/value", to: "/target", split_by: ";" }.apply_to(source: { value: ["foo", "foo;bar"] }))
|
65
98
|
.to eq({
|
66
99
|
:target => ["foo", "foo;bar"]
|
67
100
|
})
|
68
101
|
end
|
69
102
|
|
70
103
|
it "processes the source value by the lambda/proc given by \"processed_by\"" do
|
71
|
-
expect(Yahm::Mapping.new
|
104
|
+
expect(Yahm::Mapping.new { map "/source/value", to: "/target", processed_by: lambda { |v| v.downcase } }.apply_to(source: { value: "A STRING" }))
|
72
105
|
.to eq({
|
73
106
|
:target => "a string"
|
74
107
|
})
|
75
108
|
|
76
|
-
expect(Yahm::Mapping.new
|
109
|
+
expect(Yahm::Mapping.new { map "/source/value", to: "/target", processed_by: proc { |v| v.downcase } }.apply_to(source: { value: "A STRING" }))
|
77
110
|
.to eq({
|
78
111
|
:target => "a string"
|
79
112
|
})
|
80
113
|
end
|
81
114
|
|
82
115
|
it "returns nil if the source path does not exist" do
|
83
|
-
expect(Yahm::Mapping.new
|
84
|
-
.to eq({
|
116
|
+
expect(Yahm::Mapping.new { map "/source/value/min", to: "/target" }.apply_to(foo: "bar")).to eq({
|
85
117
|
:target => nil
|
86
118
|
})
|
87
119
|
end
|
88
120
|
|
89
121
|
it "accepts a lambda/proc as :to option" do
|
90
|
-
expect(Yahm::Mapping.new
|
122
|
+
expect(Yahm::Mapping.new { map "/source", to: lambda { |v, result| result.merge!(target: true) } }.apply_to(foo: "bar"))
|
91
123
|
.to eq({
|
92
124
|
:target => true
|
93
125
|
})
|
94
126
|
end
|
95
127
|
|
96
128
|
it "provides multiple values as an array of values to lambdas/procs" do
|
97
|
-
expect(Yahm::Mapping.new
|
129
|
+
expect(Yahm::Mapping.new { map "/source", to: "/array_length", processed_by: lambda { |v| v.length } }.apply_to(source: ["bar", "moep"]))
|
98
130
|
.to eq({
|
99
131
|
:array_length => 2
|
100
132
|
})
|
101
133
|
end
|
102
134
|
end
|
103
|
-
|
104
135
|
end
|
data/spec/yahm_spec.rb
CHANGED
@@ -1,8 +1,93 @@
|
|
1
1
|
require_relative "./spec_helper"
|
2
2
|
|
3
3
|
describe Yahm do
|
4
|
-
it "has a
|
5
|
-
expect(Yahm.const_defined? :
|
6
|
-
expect(Yahm::
|
4
|
+
it "has a class named Mapping" do
|
5
|
+
expect(Yahm.const_defined? :Mapping).to be_true
|
6
|
+
expect(Yahm::Mapping.class.is_a? Class)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#get" do
|
10
|
+
let(:object) do
|
11
|
+
{
|
12
|
+
hello: "world",
|
13
|
+
empty_array: [],
|
14
|
+
empty_hash: [],
|
15
|
+
"string_key" => "foobar",
|
16
|
+
some: {
|
17
|
+
really: [
|
18
|
+
{
|
19
|
+
nested: [
|
20
|
+
{
|
21
|
+
structure: "hello"
|
22
|
+
},
|
23
|
+
{
|
24
|
+
structure: "world"
|
25
|
+
}
|
26
|
+
]
|
27
|
+
}
|
28
|
+
]
|
29
|
+
},
|
30
|
+
foo: [
|
31
|
+
{
|
32
|
+
bar: 1
|
33
|
+
},
|
34
|
+
{
|
35
|
+
bar: 2
|
36
|
+
}
|
37
|
+
],
|
38
|
+
arr_of_arrs: [
|
39
|
+
[ [1,2] , [3,4,5] ],
|
40
|
+
[ 6,7 ],
|
41
|
+
8
|
42
|
+
]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns elements which match the given path" do
|
47
|
+
expect(Yahm.get(object, "/hello")).to eq("world")
|
48
|
+
expect(Yahm.get(object, "/empty_array")).to eq([])
|
49
|
+
expect(Yahm.get(object, "/empty_hash")).to eq([])
|
50
|
+
expect(Yahm.get(object, "/foo")).to eq([{:bar=>1}, {:bar=>2}])
|
51
|
+
expect(Yahm.get(object, "/foo[0]")).to eq({:bar=>1})
|
52
|
+
expect(Yahm.get(object, "/foo[1]")).to eq({:bar=>2})
|
53
|
+
expect(Yahm.get(object, "/foo[]/bar")).to eq([1,2])
|
54
|
+
expect(Yahm.get(object, "/arr_of_arrs[0][1][2]")).to eq([5])
|
55
|
+
expect(Yahm.get(object, "/some/really[]/nested[]/structure")).to eq(["hello", "world"])
|
56
|
+
expect(Yahm.get(object, "/arr_of_arrs[0]")).to eq([[1, 2], [3, 4, 5]])
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns nil if the path is invalid" do
|
60
|
+
expect(Yahm.get(object, "/hello_world")).to eq(nil)
|
61
|
+
expect(Yahm.get(object, "/foo[3]")).to eq(nil)
|
62
|
+
expect(Yahm.get(object, "/foo[]/bar/muff")).to eq(nil)
|
63
|
+
expect(Yahm.get(object, "/foo[]/bar[0]")).to eq(nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "allows specification of the key type using : or \"\"" do
|
67
|
+
expect(Yahm.get(object, '/"string_key"')).to eq("foobar")
|
68
|
+
expect(Yahm.get(object, '/:string_key"')).to eq(nil)
|
69
|
+
expect(Yahm.get(object, "/string_key")).to eq("foobar")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#set" do
|
74
|
+
it "sets the objects value according to path and value" do
|
75
|
+
Yahm.set(object = {}, "/foo[]/bar", [1,2,3])
|
76
|
+
expect(object).to eq({:foo=>[{:bar=>[1, 2, 3]}]})
|
77
|
+
end
|
78
|
+
|
79
|
+
it "allows specification of the key type using : or \"\"" do
|
80
|
+
Yahm.set(object = {}, "/:foo/\"bar\"", "foobar")
|
81
|
+
expect(object).to eq({:foo=>{"bar"=>"foobar"}})
|
82
|
+
end
|
83
|
+
|
84
|
+
it "extends existing arrays/hashes if present" do
|
85
|
+
Yahm.set(object = { foo: [{bar: [1,2,3]}] }, "/foo[]/bar", [4,5,6])
|
86
|
+
expect(object).to eq({:foo=>[{:bar=>[1, 2, 3]}, {:bar=>[4, 5, 6]}]})
|
87
|
+
|
88
|
+
Yahm.set(object = { foo: "bar" }, "/muff", "muffmuff")
|
89
|
+
expect(object).to eq({:foo=>"bar", :muff=>"muffmuff"})
|
90
|
+
end
|
91
|
+
|
7
92
|
end
|
8
93
|
end
|
data/yahm.gemspec
CHANGED
@@ -17,11 +17,9 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
+
spec.add_dependency "thread", ">= 0.1.4"
|
21
|
+
|
20
22
|
spec.add_development_dependency "bundler", "~> 1.5"
|
21
|
-
spec.add_development_dependency "
|
22
|
-
spec.add_development_dependency "pry-nav", "~> 0.2.3"
|
23
|
-
spec.add_development_dependency "pry-stack_explorer", "~> 0.4.9.1"
|
24
|
-
spec.add_development_dependency "pry-syntax-hacks", "~> 0.0.6"
|
25
|
-
spec.add_development_dependency "rspec", "~> 2.14.1"
|
23
|
+
spec.add_development_dependency "rspec", ">= 2.14.1"
|
26
24
|
spec.add_development_dependency "rake"
|
27
25
|
end
|
metadata
CHANGED
@@ -1,97 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yahm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Sievers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.5'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.5'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: pry
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 0.9.12.4
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 0.9.12.4
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: pry-nav
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 0.2.3
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 0.2.3
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: pry-stack_explorer
|
14
|
+
name: thread
|
57
15
|
requirement: !ruby/object:Gem::Requirement
|
58
16
|
requirements:
|
59
|
-
- - "
|
17
|
+
- - ">="
|
60
18
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.4
|
62
|
-
type: :
|
19
|
+
version: 0.1.4
|
20
|
+
type: :runtime
|
63
21
|
prerelease: false
|
64
22
|
version_requirements: !ruby/object:Gem::Requirement
|
65
23
|
requirements:
|
66
|
-
- - "
|
24
|
+
- - ">="
|
67
25
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.4
|
26
|
+
version: 0.1.4
|
69
27
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
28
|
+
name: bundler
|
71
29
|
requirement: !ruby/object:Gem::Requirement
|
72
30
|
requirements:
|
73
31
|
- - "~>"
|
74
32
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
33
|
+
version: '1.5'
|
76
34
|
type: :development
|
77
35
|
prerelease: false
|
78
36
|
version_requirements: !ruby/object:Gem::Requirement
|
79
37
|
requirements:
|
80
38
|
- - "~>"
|
81
39
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
40
|
+
version: '1.5'
|
83
41
|
- !ruby/object:Gem::Dependency
|
84
42
|
name: rspec
|
85
43
|
requirement: !ruby/object:Gem::Requirement
|
86
44
|
requirements:
|
87
|
-
- - "
|
45
|
+
- - ">="
|
88
46
|
- !ruby/object:Gem::Version
|
89
47
|
version: 2.14.1
|
90
48
|
type: :development
|
91
49
|
prerelease: false
|
92
50
|
version_requirements: !ruby/object:Gem::Requirement
|
93
51
|
requirements:
|
94
|
-
- - "
|
52
|
+
- - ">="
|
95
53
|
- !ruby/object:Gem::Version
|
96
54
|
version: 2.14.1
|
97
55
|
- !ruby/object:Gem::Dependency
|
@@ -117,16 +75,15 @@ extra_rdoc_files: []
|
|
117
75
|
files:
|
118
76
|
- ".gitignore"
|
119
77
|
- ".rspec"
|
78
|
+
- CHANGELOG.md
|
120
79
|
- Gemfile
|
121
80
|
- LICENSE.txt
|
122
81
|
- README.md
|
123
82
|
- Rakefile
|
124
83
|
- lib/yahm.rb
|
125
|
-
- lib/yahm/hash_mapper.rb
|
126
84
|
- lib/yahm/mapping.rb
|
127
85
|
- lib/yahm/version.rb
|
128
86
|
- spec/spec_helper.rb
|
129
|
-
- spec/yahm/hash_mapper_spec.rb
|
130
87
|
- spec/yahm/mapping_spec.rb
|
131
88
|
- spec/yahm_spec.rb
|
132
89
|
- yahm.gemspec
|
@@ -156,6 +113,5 @@ specification_version: 4
|
|
156
113
|
summary: Yet another ruby hash mapper
|
157
114
|
test_files:
|
158
115
|
- spec/spec_helper.rb
|
159
|
-
- spec/yahm/hash_mapper_spec.rb
|
160
116
|
- spec/yahm/mapping_spec.rb
|
161
117
|
- spec/yahm_spec.rb
|
data/lib/yahm/hash_mapper.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
module Yahm::HashMapper
|
2
|
-
def define_mapper(mapper_method_name, options = {}, &block)
|
3
|
-
mapping = Yahm::Mapping.new(&block)
|
4
|
-
|
5
|
-
define_method mapper_method_name do |hash|
|
6
|
-
translated_hash = mapping.translate_hash(hash, _self: self)
|
7
|
-
unless (setter_name = options[:call_setter]).nil?
|
8
|
-
self.send(setter_name, translated_hash)
|
9
|
-
end
|
10
|
-
|
11
|
-
translated_hash
|
12
|
-
end
|
13
|
-
|
14
|
-
if options[:private]
|
15
|
-
private mapper_method_name.to_sym
|
16
|
-
end
|
17
|
-
|
18
|
-
self
|
19
|
-
end
|
20
|
-
end
|
@@ -1,88 +0,0 @@
|
|
1
|
-
require_relative "../spec_helper"
|
2
|
-
|
3
|
-
describe Yahm::HashMapper do
|
4
|
-
context "when extending a class" do
|
5
|
-
let(:class_extended_with_hash_mapper) do
|
6
|
-
class MyClass
|
7
|
-
extend Yahm::HashMapper
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
it "adds a class method called #define_mapper" do
|
12
|
-
expect(class_extended_with_hash_mapper.methods).to include(:define_mapper)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe "#define_mapper" do
|
17
|
-
let(:class_extended_with_hash_mapper) do
|
18
|
-
Class.new do
|
19
|
-
extend Yahm::HashMapper
|
20
|
-
|
21
|
-
def method_of_extended_object(value)
|
22
|
-
true
|
23
|
-
end
|
24
|
-
|
25
|
-
define_mapper :my_mapper do
|
26
|
-
def mapper_local_method(value)
|
27
|
-
true
|
28
|
-
end
|
29
|
-
|
30
|
-
map "/source[0]", to: "target_1", processed_by: proc { |v| mapper_local_method(v) }
|
31
|
-
map "/source[1]", to: "target_2", processed_by: proc { |v| _self.method_of_extended_object(v) }
|
32
|
-
map "/source[2]", to: "target_3", processed_by: proc { |v| another_local_method(v) }
|
33
|
-
|
34
|
-
def another_local_method(value)
|
35
|
-
true
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
define_mapper :my_private_mapper, private: true do
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
it "adds a named mapper method to the instances of the extended class" do
|
45
|
-
expect(class_extended_with_hash_mapper.new).to respond_to(:my_mapper)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "provides the context of the defined mapper as \"self\" for \"processed_by\" callables" do
|
49
|
-
expect(class_extended_with_hash_mapper.new.my_mapper({source: ["foo", "bar"]})).to eq({
|
50
|
-
target_1: true,
|
51
|
-
target_2: true,
|
52
|
-
target_3: true
|
53
|
-
})
|
54
|
-
end
|
55
|
-
|
56
|
-
it "provides the context of the extended object as \"_self\" for \"processed_by\" callables" do
|
57
|
-
expect(class_extended_with_hash_mapper.new.my_mapper({source: ["foo", "bar"]})).to eq({
|
58
|
-
target_1: true,
|
59
|
-
target_2: true,
|
60
|
-
target_3: true
|
61
|
-
})
|
62
|
-
end
|
63
|
-
|
64
|
-
context "when called with an option called 'call_setter'" do
|
65
|
-
let(:class_extended_with_hash_mapper) do
|
66
|
-
Class.new do
|
67
|
-
extend Yahm::HashMapper
|
68
|
-
|
69
|
-
attr_accessor :my_result
|
70
|
-
|
71
|
-
define_mapper :my_mapper, call_setter: :my_result= do
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
it "calls a method with the given name with the result of the mapping" do
|
77
|
-
(instance = class_extended_with_hash_mapper.new).my_mapper({})
|
78
|
-
expect(instance.my_result).not_to be_nil
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
context "when called with private: true" do
|
83
|
-
it "adds a private named mapper method to the instances of the extended class" do
|
84
|
-
expect(class_extended_with_hash_mapper.new.private_methods).to include(:my_private_mapper)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|