htransform 0.8.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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +149 -0
- data/Rakefile +13 -0
- data/examples/example.rb +35 -0
- data/examples/example_htransform.rb +18 -0
- data/htransform.gemspec +21 -0
- data/lib/htransform/version.rb +3 -0
- data/lib/htransform.rb +198 -0
- data/spec/htransform_nested_spec.rb +108 -0
- data/spec/htransform_spec.rb +392 -0
- data/spec/spec_helper.rb +1 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
HTransform
|
2
|
+
======
|
3
|
+
|
4
|
+
HTransform provides a simple DSL to transform a supplied hash with an arbitrary structure to another hash with an even more arbitrary structure.
|
5
|
+
|
6
|
+
***Note:*** *HTransform does not simply modify the hash you have passed in. It actually creates a new hash from scratch. It only copies or does transformations on the fields you specify in the transform block.*
|
7
|
+
|
8
|
+
Support
|
9
|
+
=======
|
10
|
+
|
11
|
+
Should work for both 1.8 and 1.9 rubies.
|
12
|
+
|
13
|
+
Install
|
14
|
+
=======
|
15
|
+
|
16
|
+
Standard gem install.
|
17
|
+
|
18
|
+
gem install htransform
|
19
|
+
|
20
|
+
|
21
|
+
Put this in your Gemfile.
|
22
|
+
|
23
|
+
gem "htransform"
|
24
|
+
|
25
|
+
or
|
26
|
+
|
27
|
+
gem "htransform", :git =>
|
28
|
+
"git://github.com/joshkrueger/htransform.git"
|
29
|
+
|
30
|
+
Basic Usage
|
31
|
+
===========
|
32
|
+
|
33
|
+
All you have to do is create a new class and inherit from HTransform.
|
34
|
+
|
35
|
+
class ExampleHTransform < HTransform
|
36
|
+
end
|
37
|
+
|
38
|
+
Inside your new class you just use the hopefully not-convoluted DSL.
|
39
|
+
|
40
|
+
class ExampleHTransform < HTransform
|
41
|
+
transform do
|
42
|
+
input :from => "foo", :to => :bar
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Theres also shorthand for you lazy folks.
|
47
|
+
|
48
|
+
input "foo" => :bar
|
49
|
+
|
50
|
+
But how do I do more than just re-name my keys?
|
51
|
+
|
52
|
+
Let's start simple. Lets take the following hash
|
53
|
+
|
54
|
+
{ "foo" => "party" }
|
55
|
+
|
56
|
+
and have HTransform change it to
|
57
|
+
|
58
|
+
{ :foo => "Party" }
|
59
|
+
|
60
|
+
Its pretty easy!
|
61
|
+
|
62
|
+
input "foo" => :foo, :via => :capitalize
|
63
|
+
|
64
|
+
See! Nice and simple!
|
65
|
+
|
66
|
+
HTransform with a block!
|
67
|
+
|
68
|
+
input "foo" => :foo, :via => lambda { |x| x.capitalize }
|
69
|
+
|
70
|
+
Actually Using HTransform
|
71
|
+
================
|
72
|
+
|
73
|
+
So we have our ExampleHTransform we created above (with our capitalize transform), saved somewhere. Lets say its in your "lib" folder. Anywhere it's loadable via require should be fine.
|
74
|
+
|
75
|
+
require 'htransform'
|
76
|
+
require 'example_htransform'
|
77
|
+
|
78
|
+
contrived_input_hash = { "foo" => "bar" }
|
79
|
+
|
80
|
+
output_hash = ExampleHTransform.convert(contrived_input_hash)
|
81
|
+
|
82
|
+
and now output_hash should look like
|
83
|
+
|
84
|
+
{ :foo => "Bar" }
|
85
|
+
|
86
|
+
Slightly Less Basic Usage
|
87
|
+
=================
|
88
|
+
|
89
|
+
Want to combine multiple input keys? **HTransform CAN DO THAT**
|
90
|
+
|
91
|
+
input_multiple ["foo", "bar"] => :foo_bar
|
92
|
+
|
93
|
+
By default HTransform will just join the multiple elements with a space. i.e. "foo" and "bar" become "foo bar". Need something more complicated?
|
94
|
+
|
95
|
+
input_multiple ["foo", "bar"] => :foo_bar, :via => lambda { |x| x.map{ |v| v.capitalize }.join(" ") }
|
96
|
+
|
97
|
+
Here, "foo" and "bar" become "Foo Bar". Easy right?
|
98
|
+
|
99
|
+
Hrmm. What about nested hashes? No problem. Just define the nesting order in an array.
|
100
|
+
|
101
|
+
Lets start with nested inputs. Given the hash
|
102
|
+
|
103
|
+
{ "foo" => { "bar" => "sample text" } }
|
104
|
+
|
105
|
+
and a desired hash of
|
106
|
+
|
107
|
+
{ "example" => "sample text" }
|
108
|
+
|
109
|
+
we would give HTransform the following operation
|
110
|
+
|
111
|
+
input ["foo", "bar"] => "example"
|
112
|
+
|
113
|
+
If we want the opposite, all we do is reverse it.
|
114
|
+
|
115
|
+
input "example" => ["foo", "bar"]
|
116
|
+
|
117
|
+
Nested to nested?
|
118
|
+
|
119
|
+
input ["foo", "bar"] => ["baz", "qux"]
|
120
|
+
|
121
|
+
this turns
|
122
|
+
|
123
|
+
{ "foo" => { "bar" => "hello world" } }
|
124
|
+
|
125
|
+
into
|
126
|
+
|
127
|
+
{ "baz" => { "qux" => "hello world" } }
|
128
|
+
|
129
|
+
Want to combine multiple inputs when one or more are an array?
|
130
|
+
|
131
|
+
input [ [ "foo", "bar" ], "baz" ] => "combined nested hash"
|
132
|
+
|
133
|
+
Shorthand Limitations
|
134
|
+
==============
|
135
|
+
|
136
|
+
Hey, the shorthand isn't perfect and theres really only a couple scenarios I can think of. If you have a key on your input hash of :via, you can't use the shorthand.
|
137
|
+
|
138
|
+
input :via => :not_via
|
139
|
+
|
140
|
+
That won't work. Why? **I'm lazy** So just use the long format.
|
141
|
+
|
142
|
+
input :from => :via, :to => :not_via, :via => :capitalize
|
143
|
+
|
144
|
+
However, :from or :to will work just fine in the shorthand. The longhand syntax only works if both the :from and :to symbols are passed in.
|
145
|
+
|
146
|
+
input :from => "from"
|
147
|
+
|
148
|
+
That will work.
|
149
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
|
+
|
5
|
+
require "rspec"
|
6
|
+
require "rspec/core/rake_task"
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new do |t|
|
9
|
+
t.rspec_opts = %w(--format documentation --color)
|
10
|
+
t.pattern = 'spec/**/*_spec.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => ["spec"]
|
data/examples/example.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'htransform'
|
4
|
+
require 'example_transform'
|
5
|
+
|
6
|
+
in_hash = {
|
7
|
+
"fname" => "JOHN",
|
8
|
+
"lname" => "doe",
|
9
|
+
"phonenum" => "555-123-1122",
|
10
|
+
"email" => "j.doe@example.com",
|
11
|
+
"state_id" => {
|
12
|
+
"number" => "d123456",
|
13
|
+
"state" => "il",
|
14
|
+
"type" => "drivers"
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
desired_hash = {
|
19
|
+
:first_name => "John",
|
20
|
+
:last_name => "Doe",
|
21
|
+
:full_name => "John Doe",
|
22
|
+
:contact_info => {
|
23
|
+
:phone_number => "5551231122",
|
24
|
+
:email_address => "j.doe@example.com"
|
25
|
+
},
|
26
|
+
:state_identification => {
|
27
|
+
:issuing_state => "IL",
|
28
|
+
:type => "drivers",
|
29
|
+
:number => "D123456"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
result_hash = ExampleTransform.convert(in_hash)
|
34
|
+
|
35
|
+
puts "The Hash Transformation worked correctly!" if result_hash == desired_hash
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'htransform'
|
2
|
+
|
3
|
+
class ExampleTransform < HTransform
|
4
|
+
transform do
|
5
|
+
|
6
|
+
input "fname" => :first_name, :via => :capitalize
|
7
|
+
input :from => "lname", :to => :last_name, :via => lambda { |x| x.capitalize }
|
8
|
+
input "phonenum" => [:contact_info, :phone_number], :via => lambda { |p| p.gsub("-", "") }
|
9
|
+
input "email" => [:contact_info, :email_address]
|
10
|
+
|
11
|
+
input_multiple :from => ["fname", "lname"], :to => :full_name, :via => lambda { |x| x.map{ |v| v.capitalize }.join(" ") }
|
12
|
+
|
13
|
+
input ["state_id", "state"] => [:state_identification, :issuing_state], :via => :upcase
|
14
|
+
input ["state_id", "number"] => [:state_identification, :number], :via => :upcase
|
15
|
+
input ["state_id", "type"] => [:state_identification, :type]
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/htransform.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "htransform/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "htransform"
|
7
|
+
s.version = HTransform::VERSION
|
8
|
+
s.authors = ["Josh Krueger"]
|
9
|
+
s.email = ["joshsinbox@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{HTransform transforms your hashes}
|
12
|
+
s.description = %q{HTransform provides a simple DSL to transform arbitrary hashes into another, more arbitrary hash. Yay.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency("rspec", "~> 2.5.0")
|
20
|
+
s.add_development_dependency("rake")
|
21
|
+
end
|
data/lib/htransform.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require "htransform/version"
|
2
|
+
|
3
|
+
class HTransform
|
4
|
+
|
5
|
+
ValueNotPresentError = Class.new(StandardError)
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :transform_block
|
9
|
+
|
10
|
+
attr_reader :input_nest
|
11
|
+
attr_reader :output_nest
|
12
|
+
|
13
|
+
def convert(input_hash)
|
14
|
+
new.convert(input_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def transform(&trans_block)
|
18
|
+
@transform_block = trans_block
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@output_hash = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }
|
25
|
+
@input_nest = []
|
26
|
+
@output_nest = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def convert(input)
|
30
|
+
|
31
|
+
unless input.is_a? Hash
|
32
|
+
if input.respond_to? :to_hash
|
33
|
+
input = input.to_hash
|
34
|
+
else
|
35
|
+
raise ArgumentError "object is not a Hash or does not respond to to_hash"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@input_hash = input
|
40
|
+
instance_exec(&self.class.transform_block)
|
41
|
+
Hash[@output_hash]
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def set_val(dst, val)
|
47
|
+
|
48
|
+
dst = (@output_nest + [dst]).flatten unless @output_nest.empty?
|
49
|
+
|
50
|
+
if dst.is_a? Array
|
51
|
+
last_key = dst.pop
|
52
|
+
dst.reduce(@output_hash) { |h,k| h[k] }[last_key] = val
|
53
|
+
else
|
54
|
+
@output_hash[dst] = val
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_val(src)
|
59
|
+
return get_val!(src)
|
60
|
+
rescue ValueNotPresentError
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_val!(src)
|
65
|
+
|
66
|
+
src = (@input_nest + [src]).flatten unless @input_nest.empty?
|
67
|
+
|
68
|
+
if src.is_a? Array
|
69
|
+
src.reduce(@input_hash) { |h,k| h[k] }
|
70
|
+
else
|
71
|
+
if @input_hash.key?(src)
|
72
|
+
@input_hash[src]
|
73
|
+
else
|
74
|
+
raise ValueNotPresentError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def key_present?(src)
|
80
|
+
src = (@input_nest + [src]).flatten unless @input_nest.empty?
|
81
|
+
if src.is_a? Array
|
82
|
+
src.reduce(@input_hash) do |h, k|
|
83
|
+
h[k].is_a?(Hash) ? h[k] : h.has_key?(k)
|
84
|
+
end
|
85
|
+
else
|
86
|
+
@input_hash.key?(src)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_options(options)
|
91
|
+
from, to, via, default = nil
|
92
|
+
|
93
|
+
via = options.delete :via
|
94
|
+
default = options.delete :default
|
95
|
+
|
96
|
+
from, to = if options.key? :from and options.key? :to
|
97
|
+
[options[:from], options[:to]]
|
98
|
+
else
|
99
|
+
options.delete :from
|
100
|
+
options.delete :to
|
101
|
+
|
102
|
+
lazy_args = options.shift
|
103
|
+
if lazy_args.is_a? Array and lazy_args.length == 2
|
104
|
+
lazy_args
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
[from, to, via, default]
|
109
|
+
end
|
110
|
+
|
111
|
+
def passthrough(*options)
|
112
|
+
options.each do |k|
|
113
|
+
begin
|
114
|
+
input_value = get_val!(k)
|
115
|
+
set_val(k, input_value)
|
116
|
+
rescue ValueNotPresentError
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def input_multiple(options)
|
122
|
+
from, to, via, default = parse_options(options)
|
123
|
+
via = lambda { |x| x.join(" ") } if via.nil?
|
124
|
+
|
125
|
+
|
126
|
+
old_values = from.map{ |k| get_val(k) }
|
127
|
+
new_value = if via.is_a? Proc
|
128
|
+
via.call old_values
|
129
|
+
elsif via.is_a? Symbol
|
130
|
+
if methods.include? via.to_s or methods.include? via
|
131
|
+
via_method = method(via)
|
132
|
+
if via_method.arity != 1
|
133
|
+
via_method.call(*old_values)
|
134
|
+
else
|
135
|
+
via_method.call(old_values)
|
136
|
+
end
|
137
|
+
else
|
138
|
+
old_values.send(via)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
set_val(to, new_value)
|
144
|
+
end
|
145
|
+
|
146
|
+
def input(options)
|
147
|
+
from, to, via, default = parse_options(options)
|
148
|
+
return if !key_present?(from) && default.nil?
|
149
|
+
|
150
|
+
new_value = if get_val(from).nil? && !default.nil?
|
151
|
+
default.respond_to?(:call) ? default.call : default
|
152
|
+
elsif via.is_a? Proc
|
153
|
+
via.call get_val(from)
|
154
|
+
elsif via.is_a? Symbol
|
155
|
+
if methods.include? via.to_s or methods.include? via
|
156
|
+
via_method = method(via)
|
157
|
+
if via_method.arity != 1
|
158
|
+
method(via).call(*get_val(from))
|
159
|
+
else
|
160
|
+
method(via).call(get_val(from))
|
161
|
+
end
|
162
|
+
else
|
163
|
+
get_val(from).send(via)
|
164
|
+
end
|
165
|
+
else
|
166
|
+
get_val(from)
|
167
|
+
end
|
168
|
+
|
169
|
+
set_val(to, new_value)
|
170
|
+
end
|
171
|
+
|
172
|
+
def insert(options)
|
173
|
+
options.each { |key, value| set_val(key, value) }
|
174
|
+
end
|
175
|
+
|
176
|
+
def nested(options, &trans_block)
|
177
|
+
|
178
|
+
if options.is_a? Symbol
|
179
|
+
options = {:key => options, :type => "output"}
|
180
|
+
else
|
181
|
+
options[:type] = "output" unless options.key? :type
|
182
|
+
end
|
183
|
+
|
184
|
+
if options[:type] == "output" or options[:type] == "input"
|
185
|
+
type = options[:type]
|
186
|
+
if options[:key]
|
187
|
+
key = options[:key]
|
188
|
+
nest_var = "@#{type}_nest"
|
189
|
+
self.instance_variable_set(:"#{nest_var}", self.instance_variable_get(:"#{nest_var}") + [key].flatten)
|
190
|
+
|
191
|
+
instance_exec(&trans_block)
|
192
|
+
|
193
|
+
self.instance_variable_set(:"#{nest_var}", self.instance_variable_get(:"#{nest_var}") - [key].flatten)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe HTransform do
|
5
|
+
describe "convert" do
|
6
|
+
context "using the nested block" do
|
7
|
+
|
8
|
+
it "defaults to output nesting" do
|
9
|
+
class TestHTransform < HTransform
|
10
|
+
transform do
|
11
|
+
nested :key => :foo_nest do
|
12
|
+
input "foo" => :foo
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
input_hash = { "foo" => "bar" }
|
18
|
+
desired_hash = { :foo_nest => { :foo => "bar" } }
|
19
|
+
|
20
|
+
result = TestHTransform.convert(input_hash)
|
21
|
+
|
22
|
+
result.should == desired_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
it "uses output nested when specified" do
|
26
|
+
class TestHTransform < HTransform
|
27
|
+
transform do
|
28
|
+
nested :type => "output", :key => :foo_nest do
|
29
|
+
input "foo" => :foo
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
input_hash = { "foo" => "bar" }
|
35
|
+
desired_hash = { :foo_nest => { :foo => "bar" } }
|
36
|
+
|
37
|
+
result = TestHTransform.convert(input_hash)
|
38
|
+
|
39
|
+
result.should == desired_hash
|
40
|
+
end
|
41
|
+
|
42
|
+
it "uses input nesting when specified" do
|
43
|
+
class TestHTransform < HTransform
|
44
|
+
transform do
|
45
|
+
nested :type => "input", :key => :nested_data do
|
46
|
+
input "foo" => :foo
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
input_hash = { :nested_data => { "foo" => "bar" } }
|
52
|
+
desired_hash = { :foo => "bar" }
|
53
|
+
|
54
|
+
result = TestHTransform.convert(input_hash)
|
55
|
+
|
56
|
+
result.should == desired_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
it "can nest multiple nested blocks of different types" do
|
60
|
+
class TestHTransform < HTransform
|
61
|
+
transform do
|
62
|
+
nested :type => "input", :key => :nested_input do
|
63
|
+
input :foo_nest => :foo
|
64
|
+
input :bar_nest => :bar
|
65
|
+
|
66
|
+
nested :type => "input", :key => :more_nesting do
|
67
|
+
nested :type => "output", :key => :nested do
|
68
|
+
input :foobar => :foo_bar
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
nested :key => :animal_sounds do
|
74
|
+
passthrough :cat, :dog
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
input_hash = { :nested_input => { :foo_nest => "foo!", :bar_nest => "bar!", :more_nesting => { :foobar => "meh"} },
|
80
|
+
:cat => "meow!", :dog => "woof!" }
|
81
|
+
|
82
|
+
desired_hash = { :foo => "foo!", :bar => "bar!", :nested => { :foo_bar => "meh"} , :animal_sounds => { :cat => "meow!", :dog => "woof!" } }
|
83
|
+
|
84
|
+
result = TestHTransform.convert(input_hash)
|
85
|
+
|
86
|
+
result.should == desired_hash
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can use the shorthand method and default to output nesting" do
|
90
|
+
class TestHTransform < HTransform
|
91
|
+
transform do
|
92
|
+
nested :out_nest do
|
93
|
+
input "foo" => :foo
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
input_hash = { "foo" => "bar" }
|
99
|
+
desired_hash = { :out_nest => { :foo => "bar" } }
|
100
|
+
|
101
|
+
result = TestHTransform.convert(input_hash)
|
102
|
+
|
103
|
+
result.should == desired_hash
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,392 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe HTransform do
|
5
|
+
describe "convert" do
|
6
|
+
it "returns real hashes" do
|
7
|
+
class TestHTransform < HTransform
|
8
|
+
transform do
|
9
|
+
input "foo" => :baz
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
input_hash = { "foo" => "bar" }
|
14
|
+
result = TestHTransform.convert(input_hash)
|
15
|
+
result[:missing].should be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can call instance methods from :via" do
|
19
|
+
class TestHTransform < HTransform
|
20
|
+
transform do
|
21
|
+
input "foo" => :foo, :via => lambda { |value| my_method(value) }
|
22
|
+
end
|
23
|
+
def my_method(value); value.reverse; end
|
24
|
+
end
|
25
|
+
|
26
|
+
input_hash = { "foo" => "bar" }
|
27
|
+
TestHTransform.convert(input_hash).should == { :foo => "rab" }
|
28
|
+
end
|
29
|
+
|
30
|
+
context "via is a symbol" do
|
31
|
+
it "can call instance methods via a symbol" do
|
32
|
+
class TestHTransform < HTransform
|
33
|
+
transform do
|
34
|
+
input "foo" => :foo, :via => :single_arg_method
|
35
|
+
input_multiple ["num1", "num2"] => :diff, :via => :multi_arg_method
|
36
|
+
input :nums => :diff2, :via => :multi_arg_method
|
37
|
+
input :nums => :splat_1, :via => :splat_method
|
38
|
+
input "foo" => :foo_splat, :via => :splat_method
|
39
|
+
end
|
40
|
+
|
41
|
+
def single_arg_method(value); value.reverse; end
|
42
|
+
def multi_arg_method(value1, value2); value1 - value2; end
|
43
|
+
def splat_method(*arg); arg.join(", "); end
|
44
|
+
end
|
45
|
+
|
46
|
+
input_hash = { "foo" => "bar", "num1" => 100, "num2" => 50, :nums => [100, 50] }
|
47
|
+
|
48
|
+
desired_hash = {:foo => "rab", :diff => 50, :diff2 => 50, :splat_1 => "100, 50", :foo_splat => "bar" }
|
49
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "the input key is missing" do
|
54
|
+
it "does not set it in the output" do
|
55
|
+
class TestHTransform < HTransform
|
56
|
+
transform do
|
57
|
+
input "birth_date" => :birthday, :via => lambda { |d| d.strftime('%F') }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
input_hash = { "foo" => "bar" }
|
62
|
+
TestHTransform.convert(input_hash).should == {}
|
63
|
+
end
|
64
|
+
|
65
|
+
context "the input key is present but the value is nil" do
|
66
|
+
it "does set it in the input" do
|
67
|
+
class TestHTransform < HTransform
|
68
|
+
transform do
|
69
|
+
input "foo" => :bar
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
input_hash = { "foo" => nil }
|
74
|
+
desired_hash = { :bar => nil }
|
75
|
+
|
76
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can set a default value" do
|
81
|
+
class TestHTransform < HTransform
|
82
|
+
transform do
|
83
|
+
input "birth_date" => :birthday, :default => Date.today
|
84
|
+
input "death_date" => :deathday, :default => lambda { Date.today + 5 }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
input_hash = { "foo" => "bar" }
|
89
|
+
desired_hash = { :birthday => Date.today, :deathday => Date.today + 5 }
|
90
|
+
|
91
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "inserting keys" do
|
96
|
+
it "should insert the key regardless of the input hash" do
|
97
|
+
class TestHTransform < HTransform
|
98
|
+
transform do
|
99
|
+
insert :bar => 'bar', :quux => 'quux'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
input_hash = { "foo" => "foo" }
|
104
|
+
desired_hash = { :bar => 'bar', :quux => 'quux' }
|
105
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "the value has no transform" do
|
110
|
+
it "should produce the same key (string)" do
|
111
|
+
class TestHTransform < HTransform
|
112
|
+
transform do
|
113
|
+
input :from => "foo", :to => "foo"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
input_hash = { "foo" => "bar" }
|
118
|
+
|
119
|
+
TestHTransform.convert(input_hash).should == input_hash
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should produce the same key via shorthand (symbol)" do
|
123
|
+
class TestHTransform < HTransform
|
124
|
+
transform do
|
125
|
+
input :foo => :foo
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
input_hash = { :foo => :bar }
|
130
|
+
|
131
|
+
TestHTransform.convert(input_hash).should == input_hash
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should change the key (string to symbol)" do
|
135
|
+
class TestHTransform < HTransform
|
136
|
+
transform do
|
137
|
+
input "foo" => :foo
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
input_hash = { "foo" => "bar" }
|
142
|
+
desired_hash = { :foo => "bar" }
|
143
|
+
|
144
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should change the key (symbox to key)" do
|
148
|
+
class TestHTransform < HTransform
|
149
|
+
transform do
|
150
|
+
input :foo => "foo"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
desired_hash = { "foo" => "bar" }
|
155
|
+
input_hash = { :foo => "bar" }
|
156
|
+
|
157
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
context "the value should be capitalized" do
|
163
|
+
it "should produce the same key" do
|
164
|
+
class TestHTransform < HTransform
|
165
|
+
transform do
|
166
|
+
input "foo" => "foo", :via => :capitalize
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
input_hash = { "foo" => "bar" }
|
171
|
+
desired_hash = { "foo" => "Bar" }
|
172
|
+
|
173
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should produce a different key" do
|
177
|
+
class TestHTransform < HTransform
|
178
|
+
transform do
|
179
|
+
input "foo" => :foo, :via => lambda { |x| x.capitalize }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
input_hash = { "foo" => "bar" }
|
184
|
+
desired_hash = { :foo => "Bar" }
|
185
|
+
|
186
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
context "use nested input" do
|
192
|
+
it "should produce the same key without nesting" do
|
193
|
+
class TestHTransform < HTransform
|
194
|
+
transform do
|
195
|
+
input ["foo", "bar"] => "bar"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
input_hash = { "foo" => { "bar" => "FOOBAR!" } }
|
200
|
+
desired_hash = { "bar" => "FOOBAR!" }
|
201
|
+
|
202
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should produce a different key without nesting" do
|
206
|
+
class TestHTransform < HTransform
|
207
|
+
transform do
|
208
|
+
input ["foo", "bar"] => :foo_bar
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
input_hash = { "foo" => { "bar" => "FOOBAR!" } }
|
213
|
+
desired_hash = { :foo_bar => "FOOBAR!" }
|
214
|
+
|
215
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context "use nested output" do
|
220
|
+
it "should produce the same key, but nested" do
|
221
|
+
class TestHTransform < HTransform
|
222
|
+
transform do
|
223
|
+
input :foo => [:bar, :foo]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
input_hash = { :foo => "FOO!" }
|
228
|
+
desired_hash = { :bar => { :foo => "FOO!" } }
|
229
|
+
|
230
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should produce a different key, but nested" do
|
234
|
+
class TestHTransform < HTransform
|
235
|
+
transform do
|
236
|
+
input "foo" => [:bar, :foo]
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
input_hash = { "foo" => "FOO!" }
|
241
|
+
desired_hash = { :bar => { :foo => "FOO!" } }
|
242
|
+
|
243
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should produce a different key, but nested (with string keys)" do
|
247
|
+
class TestHTransform < HTransform
|
248
|
+
transform do
|
249
|
+
input "foo" => ["bar", "foo"]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
input_hash = { "foo" => "FOO!" }
|
254
|
+
desired_hash = { "bar" => { "foo" => "FOO!" } }
|
255
|
+
|
256
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should use nested input and produce the same key, but nested differently" do
|
260
|
+
class TestHTransform < HTransform
|
261
|
+
transform do
|
262
|
+
input ["foo", "bar"] => ["baz", "bar"]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
input_hash = { "foo" => { "bar" => "BAR!" } }
|
267
|
+
desired_hash = { "baz" => { "bar" => "BAR!" } }
|
268
|
+
|
269
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should use nested input and produce a different key, but nested differently" do
|
273
|
+
class TestHTransform < HTransform
|
274
|
+
transform do
|
275
|
+
input ["foo", "bar"] => [:baz, :bar]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
input_hash = { "foo" => { "bar" => "BAR!" } }
|
280
|
+
desired_hash = { :baz => { :bar => "BAR!" } }
|
281
|
+
|
282
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "use multiple inputs" do
|
287
|
+
it "should join two elements with a space and produce a single key" do
|
288
|
+
class TestHTransform < HTransform
|
289
|
+
transform do
|
290
|
+
input_multiple [:foo, :bar] => :foo_bar
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
input_hash = { :foo => "Foo", :bar => "Bar" }
|
295
|
+
desired_hash = { :foo_bar => "Foo Bar" }
|
296
|
+
|
297
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should join two nested elements with a dash, via a lambda and produce a single key" do
|
301
|
+
class TestHTransform < HTransform
|
302
|
+
transform do
|
303
|
+
input_multiple [[:foo, :bar], [:baz, :qux]] => :bar_qux, :via => lambda { |x| x.join("-") }
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
input_hash = { :foo => { :bar => "fbar" }, :baz => { :qux => "bqux" } }
|
308
|
+
desired_hash = { :bar_qux => "fbar-bqux" }
|
309
|
+
|
310
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
context "multiple htransforms" do
|
317
|
+
it "does not share transformations between unrelated subclasses" do
|
318
|
+
class HTransformA < HTransform
|
319
|
+
transform do
|
320
|
+
input "foo" => :foo
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
class HTransformB < HTransform
|
325
|
+
transform do
|
326
|
+
input "bar" => :bar
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
input_hash = { "foo" => "fooval", "bar" => "barval" }
|
331
|
+
HTransformA.convert(input_hash).should == { :foo => "fooval" }
|
332
|
+
HTransformB.convert(input_hash).should == { :bar => "barval" }
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
context "passthrough" do
|
337
|
+
|
338
|
+
it "ignores a passthrough'd key if it is absent" do
|
339
|
+
class TestHTransform < HTransform
|
340
|
+
transform do
|
341
|
+
passthrough :foo
|
342
|
+
input :bar => :bar_key
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
input_hash = { :bar => 'one' }
|
347
|
+
desired_hash = { :bar_key => 'one' }
|
348
|
+
|
349
|
+
result = TestHTransform.convert(input_hash)
|
350
|
+
result.should == desired_hash
|
351
|
+
end
|
352
|
+
|
353
|
+
it "does not ignore the passthrough key if it is present but nil" do
|
354
|
+
class TestHTransform < HTransform
|
355
|
+
transform do
|
356
|
+
passthrough :foo
|
357
|
+
input :bar => :bar_key
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
input_hash = { :bar => 'one', :foo => nil }
|
362
|
+
desired_hash = { :bar_key => 'one', :foo => nil }
|
363
|
+
|
364
|
+
result = TestHTransform.convert(input_hash)
|
365
|
+
result.should == desired_hash
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
|
370
|
+
context "non-hashes" do
|
371
|
+
it "works with objects that respond to to_hash" do
|
372
|
+
class TestHTransform < HTransform
|
373
|
+
transform do
|
374
|
+
passthrough :foo
|
375
|
+
input :bar => :bar_key
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
class NonHash
|
380
|
+
def to_hash
|
381
|
+
{ :foo => "FOO", :bar => 'BAR' }
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
input_object = NonHash.new
|
386
|
+
desired_hash = { :bar_key => 'BAR', :foo => "FOO" }
|
387
|
+
|
388
|
+
result = TestHTransform.convert(input_object)
|
389
|
+
result.should == desired_hash
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'htransform'
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: htransform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Krueger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-08-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.5.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.5.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: HTransform provides a simple DSL to transform arbitrary hashes into another,
|
47
|
+
more arbitrary hash. Yay.
|
48
|
+
email:
|
49
|
+
- joshsinbox@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- examples/example.rb
|
59
|
+
- examples/example_htransform.rb
|
60
|
+
- htransform.gemspec
|
61
|
+
- lib/htransform.rb
|
62
|
+
- lib/htransform/version.rb
|
63
|
+
- spec/htransform_nested_spec.rb
|
64
|
+
- spec/htransform_spec.rb
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
homepage: ''
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.23
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: HTransform transforms your hashes
|
90
|
+
test_files:
|
91
|
+
- spec/htransform_nested_spec.rb
|
92
|
+
- spec/htransform_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
has_rdoc:
|