transmogrifier 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NGEzMmI4YjQ5ODEyZGE3NDJiYWE4OGU5MTg1MDY4NDk5YmI2MDFmNA==
5
+ data.tar.gz: !binary |-
6
+ NjA2ZGNlOGQzMWQ2MmYyMWZmMjVlNzg3MjZjYWMwYjY1YTQ4ZTliOQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZDg5MWEwOGE4YTkwMmUzNzA0ZjliMjJiZDJjYzhlOGEwNzI0OGRkMTMyYTM4
10
+ NjI0YjE1N2NhZmZkYTE1MWEwYmEwYjBiZGVmNzUyNDlmODQyZDQ0YmI2ZWVm
11
+ NTNmZTFlOGRjMzZkOWU1NWRlZjQwZGZlNzVlMWZhOTFhZWVhYjA=
12
+ data.tar.gz: !binary |-
13
+ Y2JiMTQ5ZDhjNjU1Y2YwYzk4ZWYwYzlmMmQxMTkzNTU3MGFlY2M5ZGQ0NTU3
14
+ NjE4NDYyNDRmZDRlMmMwMDU3ZGEwMDEwZTZlN2Q2Mjk3MzMyYjljMDJhNDMy
15
+ MDY4OTEyZWZlZWUyNGFkYWEwMjgwM2ZhMmE0ZDU5NmU2MjNkMTE=
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in transmogrifier.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 John Foley
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Transmogrifier
2
+
3
+ Transmogrifier is a tool that allows you to decalaritively migrate a hash from one schema to another. It works by specifying a set of rules to apply to the hash and then running them in order.
4
+
5
+ ## Usage
6
+ ### Available Rules
7
+ #### Appending a key
8
+ ```ruby
9
+ engine = Transmogrifier::Engine.new
10
+ append = Transmogrifier::Rules::Append.new("", "new_key", "new_value")
11
+
12
+ engine.add_rule(append)
13
+
14
+ input_hash = {"key" => "value"}
15
+ output_hash = engine.run(input_hash)
16
+
17
+ # output_hash => {"key" => "value", "new_key" => "new_value"}
18
+ ```
19
+
20
+ #### Deleting a key
21
+ ```ruby
22
+ engine = Transmogrifier::Engine.new
23
+ delete = Transmogrifier::Rules::Delete("", "extra_key")
24
+
25
+ engine.add_rule(delete)
26
+
27
+ input_hash = {"key" => "value", "extra_key" => "some_value"}
28
+ output_hash = engine.run(input_hash)
29
+
30
+ # output_hash => {"key" => "value"}
31
+ ```
32
+
33
+ #### Moving a key
34
+ ```ruby
35
+ engine = Transmogrifier::Engine.new
36
+ move = Transmogrifier::Rules::Move.new("", "key", "nested")
37
+
38
+ engine.add_rule(:move, "", "key", "nested")
39
+
40
+ input_hash = {"key" => "value", "nested" => {"nested_key" => "nested_value"}}
41
+ output_hash = transmogrifier.run(input_hash)
42
+
43
+ # output_hash => {"nested" => {"nested_key" => "nested_value", "key" => "value"}}
44
+ ```
45
+
46
+ ### Programmatically loading rules
47
+ Rules can be specified in ruby code, but they can also be loaded with an array.
48
+ ```ruby
49
+ rules = [
50
+ {
51
+ "type" => "append",
52
+ "selector" => "top",
53
+ "object" => {"some" => "attributes"},
54
+ },
55
+
56
+ {
57
+ "type" => "move",
58
+ "selector" => "top",
59
+ "from" => "key1",
60
+ "to" => "key2",
61
+ },
62
+
63
+ {
64
+ "type" => "delete",
65
+ "selector" => "top",
66
+ "name" => "key3",
67
+ },
68
+ ]
69
+
70
+ engine = Transmogrifier::Engine.from_rules_array(rules)
71
+
72
+
73
+ input_hash = {
74
+ "top" => {
75
+ "key1" => "value1",
76
+ "key3" => "value2",
77
+ },
78
+ }
79
+ output_hash = engine.run(input_hash)
80
+
81
+ # output_hash =>
82
+ # {
83
+ # "top" => {
84
+ # "some" => "attributes",
85
+ # "key2" => "value1",
86
+ # },
87
+ # }
88
+ ```
89
+
90
+ ### Selectors
91
+ Selectors are a string of hash keys seperated by dots that tell the Engine where to apply a given rule. For example, given the following structure:
92
+ ```ruby
93
+ {
94
+ "key" => "value",
95
+ "nested" => {
96
+ "second_level" => {
97
+ "deep" => "buried_value",
98
+ },
99
+ },
100
+ }
101
+ ```
102
+ the selector `nested.second_level.deep` will apply to `buried_value`. Rules can also be applied to hashes inside of an array. Given the structure:
103
+ ```ruby
104
+ {
105
+ "key" => "value",
106
+ "array" => [
107
+ {"name" => "not me"},
108
+ {"name" => "this one!"},
109
+ ],
110
+ }
111
+ ```
112
+ the hash with the name `this one!` can be operated on with `array.[name=this one!]`. Arrays can also wildcard match all children. For example to match both hashes in the array above, use the selector `array.[]`.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ require "transmogrifier/version"
2
+ require "transmogrifier/rules"
3
+ require "transmogrifier/selector"
4
+ require "transmogrifier/nodes"
5
+ require "transmogrifier/engine"
@@ -0,0 +1,28 @@
1
+ module Transmogrifier
2
+ class Engine
3
+ def self.from_rules_array(rules_array)
4
+ new(
5
+ rules_array.map do |rule|
6
+ type = rule["type"].capitalize
7
+ selector = rule["selector"]
8
+ options = [rule["object"], rule["from"], rule["to"], rule["name"]].compact
9
+ Transmogrifier::Rules.const_get(type).new(selector, *options)
10
+ end
11
+ )
12
+ end
13
+
14
+ def initialize(rules=[])
15
+ @rules = rules
16
+ end
17
+
18
+ def add_rule(rule)
19
+ @rules << rule
20
+ end
21
+
22
+ def run(input_hash)
23
+ output_hash = input_hash.dup
24
+ @rules.each { |rule| output_hash = rule.apply!(output_hash) }
25
+ output_hash
26
+ end
27
+ end
28
+ end
@@ -0,0 +1 @@
1
+ Dir[File.dirname(__FILE__) + '/nodes/*.rb'].each { |file| require file }
@@ -0,0 +1,36 @@
1
+ require_relative "node"
2
+
3
+ module Transmogrifier
4
+ class ArrayNode < Node
5
+ extend Forwardable
6
+
7
+ def initialize(array)
8
+ @array = array
9
+ end
10
+
11
+ def find_all(keys)
12
+ first_key, *remaining_keys = keys
13
+
14
+ if first_key.nil?
15
+ [self]
16
+ else
17
+ find_nodes(first_key).flat_map { |x| Node.for(x).find_all(remaining_keys) }
18
+ end
19
+ end
20
+
21
+ def delete(key)
22
+ matching_nodes = find_nodes(key)
23
+ raise "Multiple nodes match #{key}, deletion criteria ambiguous" if matching_nodes.length > 1
24
+ @array.delete(matching_nodes.first)
25
+ end
26
+
27
+ def_delegator :@array, :<<, :append
28
+ def_delegator :@array, :to_a, :raw
29
+
30
+ private
31
+
32
+ def find_nodes(attributes)
33
+ @array.select { |node| node.merge(Hash[attributes]) == node }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ require_relative "node"
2
+
3
+ module Transmogrifier
4
+ class HashNode < Node
5
+ extend Forwardable
6
+
7
+ def initialize(hash)
8
+ @hash = hash
9
+ end
10
+
11
+ def find_all(keys)
12
+ first_key, *remaining_keys = keys
13
+
14
+ if first_key.nil?
15
+ [self]
16
+ elsif child = @hash[first_key]
17
+ Node.for(child).find_all(remaining_keys)
18
+ else
19
+ []
20
+ end
21
+ end
22
+
23
+ def_delegator :@hash, :delete
24
+ def_delegator :@hash, :merge!, :append
25
+ def_delegator :@hash, :to_hash, :raw
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module Transmogrifier
2
+ class Node
3
+ def self.for(obj)
4
+ case obj
5
+ when Hash
6
+ HashNode.new(obj)
7
+ when Array
8
+ ArrayNode.new(obj)
9
+ else
10
+ ValueNode.new(obj)
11
+ end
12
+ end
13
+
14
+ def initialize(obj)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def raw
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def delete(key_or_name)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def append(node)
27
+ raise NotImplementedError
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "node"
2
+
3
+ module Transmogrifier
4
+ class ValueNode < Node
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def find_all(keys)
10
+ return [self] if keys.empty?
11
+ raise "cannot find children of ValueNode satisfying non-empty selector #{keys}"
12
+ end
13
+
14
+ def raw
15
+ @value
16
+ end
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ Dir[File.dirname(__FILE__) + '/rules/*.rb'].each { |file| require file }
@@ -0,0 +1,19 @@
1
+ module Transmogrifier
2
+ module Rules
3
+ class Append
4
+ def initialize(parent_selector, hash)
5
+ @parent_selector, @hash = parent_selector, hash
6
+ end
7
+
8
+ def apply!(input_hash)
9
+ top = Node.for(input_hash)
10
+ parent_keys = Selector.from_string(@parent_selector).keys
11
+
12
+ parents = top.find_all(parent_keys)
13
+ parents.each { |parent| parent.append(@hash) }
14
+
15
+ top.raw
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Transmogrifier
2
+ module Rules
3
+ class Delete
4
+ def initialize(parent_selector, selector_to_delete)
5
+ @parent_selector, @selector_to_delete = parent_selector, selector_to_delete
6
+ end
7
+
8
+ def apply!(input_hash)
9
+ top = Node.for(input_hash)
10
+ parent_keys = Selector.from_string(@parent_selector).keys
11
+ child_key = Selector.from_string(@selector_to_delete).keys.first
12
+
13
+ parents = top.find_all(parent_keys)
14
+ parents.each { |parent| parent.delete(child_key) }
15
+
16
+ top.raw
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ module Transmogrifier
2
+ module Rules
3
+ class Move
4
+ def initialize(parent_selector, from, to)
5
+ @parent_selector, @from, @to = parent_selector, from, to
6
+ end
7
+
8
+ def apply!(input_hash)
9
+ top = Node.for(input_hash)
10
+ *from_keys, from_key = Selector.from_string(@from).keys
11
+ *to_keys, to_key = Selector.from_string(@to).keys
12
+
13
+ parents = top.find_all(Selector.from_string(@parent_selector).keys)
14
+ parents.each do |parent|
15
+ to_parent = parent.find_all(to_keys).first
16
+ deleted_object = parent.find_all(from_keys).first.delete(from_key)
17
+
18
+ if to_child = to_parent.find_all([to_key]).first
19
+ to_child.append(deleted_object)
20
+ else
21
+ to_parent.append({to_key => deleted_object})
22
+ end
23
+ end
24
+
25
+ top.raw
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module Transmogrifier
2
+ class Selector
3
+ FILTER_REGEX = /\[(.*)\]/
4
+
5
+ def self.from_string(string)
6
+ new(
7
+ string.split(".").map do |str|
8
+ match = str.scan(FILTER_REGEX).flatten.first
9
+ match ? match.split(",").map { |s| s.split("=") } : str
10
+ end
11
+ )
12
+ end
13
+
14
+ attr_reader :keys
15
+
16
+ def initialize(keys)
17
+ @keys = keys
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Transmogrifier
2
+ VERSION = "0.0.1"
3
+ end