wukong 0.1.1

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.
Files changed (143) hide show
  1. data/LICENSE.textile +107 -0
  2. data/README.textile +166 -0
  3. data/bin/cutc +30 -0
  4. data/bin/cuttab +5 -0
  5. data/bin/greptrue +8 -0
  6. data/bin/hdp-cat +3 -0
  7. data/bin/hdp-catd +3 -0
  8. data/bin/hdp-du +81 -0
  9. data/bin/hdp-get +3 -0
  10. data/bin/hdp-kill +3 -0
  11. data/bin/hdp-ls +10 -0
  12. data/bin/hdp-mkdir +3 -0
  13. data/bin/hdp-mv +3 -0
  14. data/bin/hdp-parts_to_keys.rb +77 -0
  15. data/bin/hdp-ps +3 -0
  16. data/bin/hdp-put +3 -0
  17. data/bin/hdp-rm +11 -0
  18. data/bin/hdp-sort +29 -0
  19. data/bin/hdp-stream +29 -0
  20. data/bin/hdp-stream-flat +18 -0
  21. data/bin/hdp-sync +17 -0
  22. data/bin/hdp-wc +67 -0
  23. data/bin/md5sort +20 -0
  24. data/bin/tabchar +5 -0
  25. data/bin/uniqc +3 -0
  26. data/bin/wu-hist +3 -0
  27. data/bin/wu-lign +177 -0
  28. data/bin/wu-sum +30 -0
  29. data/doc/INSTALL.textile +41 -0
  30. data/doc/LICENSE.textile +107 -0
  31. data/doc/README-tutorial.textile +163 -0
  32. data/doc/README-wulign.textile +59 -0
  33. data/doc/README-wutils.textile +128 -0
  34. data/doc/TODO.textile +61 -0
  35. data/doc/UsingWukong-part1-setup.textile +2 -0
  36. data/doc/UsingWukong-part2-scraping.textile +2 -0
  37. data/doc/UsingWukong-part3-parsing.textile +132 -0
  38. data/doc/code/api_response_example.txt +20 -0
  39. data/doc/code/parser_skeleton.rb +38 -0
  40. data/doc/hadoop-nfs.textile +51 -0
  41. data/doc/hadoop-setup.textile +29 -0
  42. data/doc/index.textile +124 -0
  43. data/doc/intro_to_map_reduce/MapReduceDiagram.graffle +0 -0
  44. data/doc/links.textile +42 -0
  45. data/doc/overview.textile +91 -0
  46. data/doc/pig/PigLatinExpressionsList.txt +122 -0
  47. data/doc/pig/PigLatinReferenceManual.html +19134 -0
  48. data/doc/pig/PigLatinReferenceManual.txt +1640 -0
  49. data/doc/tips.textile +116 -0
  50. data/doc/usage.textile +102 -0
  51. data/doc/utils.textile +48 -0
  52. data/examples/README.txt +17 -0
  53. data/examples/and_pig/sample_queries.rb +128 -0
  54. data/examples/apache_log_parser.rb +53 -0
  55. data/examples/count_keys.rb +56 -0
  56. data/examples/count_keys_at_mapper.rb +57 -0
  57. data/examples/graph/adjacency_list.rb +74 -0
  58. data/examples/graph/breadth_first_search.rb +79 -0
  59. data/examples/graph/gen_2paths.rb +68 -0
  60. data/examples/graph/gen_multi_edge.rb +103 -0
  61. data/examples/graph/gen_symmetric_links.rb +53 -0
  62. data/examples/package-local.rb +100 -0
  63. data/examples/package.rb +96 -0
  64. data/examples/pagerank/README.textile +6 -0
  65. data/examples/pagerank/gen_initial_pagerank_graph.pig +57 -0
  66. data/examples/pagerank/pagerank.rb +88 -0
  67. data/examples/pagerank/pagerank_initialize.rb +46 -0
  68. data/examples/pagerank/run_pagerank.sh +19 -0
  69. data/examples/rank_and_bin.rb +173 -0
  70. data/examples/run_all.sh +47 -0
  71. data/examples/sample_records.rb +44 -0
  72. data/examples/size.rb +60 -0
  73. data/examples/word_count.rb +95 -0
  74. data/lib/wukong.rb +11 -0
  75. data/lib/wukong/and_pig.rb +62 -0
  76. data/lib/wukong/and_pig/README.textile +12 -0
  77. data/lib/wukong/and_pig/as.rb +37 -0
  78. data/lib/wukong/and_pig/data_types.rb +30 -0
  79. data/lib/wukong/and_pig/functions.rb +50 -0
  80. data/lib/wukong/and_pig/generate.rb +85 -0
  81. data/lib/wukong/and_pig/generate/variable_inflections.rb +82 -0
  82. data/lib/wukong/and_pig/junk.rb +51 -0
  83. data/lib/wukong/and_pig/operators.rb +8 -0
  84. data/lib/wukong/and_pig/operators/compound.rb +29 -0
  85. data/lib/wukong/and_pig/operators/evaluators.rb +7 -0
  86. data/lib/wukong/and_pig/operators/execution.rb +15 -0
  87. data/lib/wukong/and_pig/operators/file_methods.rb +29 -0
  88. data/lib/wukong/and_pig/operators/foreach.rb +98 -0
  89. data/lib/wukong/and_pig/operators/groupies.rb +212 -0
  90. data/lib/wukong/and_pig/operators/load_store.rb +65 -0
  91. data/lib/wukong/and_pig/operators/meta.rb +42 -0
  92. data/lib/wukong/and_pig/operators/relational.rb +129 -0
  93. data/lib/wukong/and_pig/pig_struct.rb +48 -0
  94. data/lib/wukong/and_pig/pig_var.rb +95 -0
  95. data/lib/wukong/and_pig/symbol.rb +29 -0
  96. data/lib/wukong/and_pig/utils.rb +0 -0
  97. data/lib/wukong/bad_record.rb +18 -0
  98. data/lib/wukong/boot.rb +47 -0
  99. data/lib/wukong/datatypes.rb +24 -0
  100. data/lib/wukong/datatypes/enum.rb +123 -0
  101. data/lib/wukong/dfs.rb +80 -0
  102. data/lib/wukong/encoding.rb +111 -0
  103. data/lib/wukong/extensions.rb +15 -0
  104. data/lib/wukong/extensions/array.rb +18 -0
  105. data/lib/wukong/extensions/blank.rb +93 -0
  106. data/lib/wukong/extensions/class.rb +189 -0
  107. data/lib/wukong/extensions/date_time.rb +24 -0
  108. data/lib/wukong/extensions/emittable.rb +82 -0
  109. data/lib/wukong/extensions/hash.rb +120 -0
  110. data/lib/wukong/extensions/hash_like.rb +119 -0
  111. data/lib/wukong/extensions/hashlike_class.rb +47 -0
  112. data/lib/wukong/extensions/module.rb +2 -0
  113. data/lib/wukong/extensions/pathname.rb +27 -0
  114. data/lib/wukong/extensions/string.rb +65 -0
  115. data/lib/wukong/extensions/struct.rb +17 -0
  116. data/lib/wukong/extensions/symbol.rb +11 -0
  117. data/lib/wukong/logger.rb +53 -0
  118. data/lib/wukong/models/graph.rb +27 -0
  119. data/lib/wukong/rdf.rb +104 -0
  120. data/lib/wukong/schema.rb +37 -0
  121. data/lib/wukong/script.rb +265 -0
  122. data/lib/wukong/script/hadoop_command.rb +111 -0
  123. data/lib/wukong/script/local_command.rb +14 -0
  124. data/lib/wukong/streamer.rb +13 -0
  125. data/lib/wukong/streamer/accumulating_reducer.rb +89 -0
  126. data/lib/wukong/streamer/base.rb +76 -0
  127. data/lib/wukong/streamer/count_keys.rb +30 -0
  128. data/lib/wukong/streamer/count_lines.rb +26 -0
  129. data/lib/wukong/streamer/filter.rb +20 -0
  130. data/lib/wukong/streamer/line_streamer.rb +12 -0
  131. data/lib/wukong/streamer/list_reducer.rb +20 -0
  132. data/lib/wukong/streamer/preprocess_with_pipe_streamer.rb +22 -0
  133. data/lib/wukong/streamer/rank_and_bin_reducer.rb +145 -0
  134. data/lib/wukong/streamer/set_reducer.rb +14 -0
  135. data/lib/wukong/streamer/struct_streamer.rb +48 -0
  136. data/lib/wukong/streamer/summing_reducer.rb +29 -0
  137. data/lib/wukong/streamer/uniq_by_last_reducer.rb +44 -0
  138. data/lib/wukong/typed_struct.rb +12 -0
  139. data/lib/wukong/wukong_class.rb +21 -0
  140. data/spec/bin/hdp-wc_spec.rb +4 -0
  141. data/spec/spec_helper.rb +0 -0
  142. data/wukong.gemspec +179 -0
  143. metadata +214 -0
@@ -0,0 +1,189 @@
1
+ #
2
+ # This is taken in whole from the extlib gem. Thanks y'all.
3
+ #
4
+
5
+
6
+ # Copyright (c) 2004-2008 David Heinemeier Hansson
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to
13
+ # permit persons to whom the Software is furnished to do so, subject to
14
+ # the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be
17
+ # included in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+
27
+ # Allows attributes to be shared within an inheritance hierarchy, but where
28
+ # each descendant gets a copy of their parents' attributes, instead of just a
29
+ # pointer to the same. This means that the child can add elements to, for
30
+ # example, an array without those additions being shared with either their
31
+ # parent, siblings, or children, which is unlike the regular class-level
32
+ # attributes that are shared across the entire hierarchy.
33
+ class Class
34
+ # Defines class-level and instance-level attribute reader.
35
+ #
36
+ # @param *syms<Array> Array of attributes to define reader for.
37
+ # @return <Array[#to_s]> List of attributes that were made into cattr_readers
38
+ #
39
+ # @api public
40
+ #
41
+ # @todo Is this inconsistent in that it does not allow you to prevent
42
+ # an instance_reader via :instance_reader => false
43
+ def cattr_reader(*syms)
44
+ syms.flatten.each do |sym|
45
+ next if sym.is_a?(Hash)
46
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
47
+ unless defined? @@#{sym}
48
+ @@#{sym} = nil
49
+ end
50
+
51
+ def self.#{sym}
52
+ @@#{sym}
53
+ end
54
+
55
+ def #{sym}
56
+ @@#{sym}
57
+ end
58
+ RUBY
59
+ end
60
+ end
61
+
62
+ # Defines class-level (and optionally instance-level) attribute writer.
63
+ #
64
+ # @param <Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define writer for.
65
+ # @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
66
+ # @return <Array[#to_s]> List of attributes that were made into cattr_writers
67
+ #
68
+ # @api public
69
+ def cattr_writer(*syms)
70
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
71
+ syms.flatten.each do |sym|
72
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
73
+ unless defined? @@#{sym}
74
+ @@#{sym} = nil
75
+ end
76
+
77
+ def self.#{sym}=(obj)
78
+ @@#{sym} = obj
79
+ end
80
+ RUBY
81
+
82
+ unless options[:instance_writer] == false
83
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
84
+ def #{sym}=(obj)
85
+ @@#{sym} = obj
86
+ end
87
+ RUBY
88
+ end
89
+ end
90
+ end
91
+
92
+ # Defines class-level (and optionally instance-level) attribute accessor.
93
+ #
94
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define accessor for.
95
+ # @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
96
+ # @return <Array[#to_s]> List of attributes that were made into accessors
97
+ #
98
+ # @api public
99
+ def cattr_accessor(*syms)
100
+ cattr_reader(*syms)
101
+ cattr_writer(*syms)
102
+ end
103
+
104
+ # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
105
+ # each subclass has a copy of parent's attribute.
106
+ #
107
+ # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
108
+ # @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
109
+ #
110
+ # @api public
111
+ #
112
+ # @todo Do we want to block instance_reader via :instance_reader => false
113
+ # @todo It would be preferable that we do something with a Hash passed in
114
+ # (error out or do the same as other methods above) instead of silently
115
+ # moving on). In particular, this makes the return value of this function
116
+ # less useful.
117
+ def class_inheritable_reader(*ivars)
118
+ instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
119
+
120
+ ivars.each do |ivar|
121
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
122
+ def self.#{ivar}
123
+ return @#{ivar} if defined?(@#{ivar})
124
+ return nil if self.object_id == #{self.object_id}
125
+ ivar = superclass.#{ivar}
126
+ return nil if ivar.nil?
127
+ @#{ivar} = ivar.try_dup
128
+ end
129
+ RUBY
130
+
131
+ unless instance_reader == false
132
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
133
+ def #{ivar}
134
+ self.class.#{ivar}
135
+ end
136
+ RUBY
137
+ end
138
+ end
139
+ end
140
+
141
+ # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
142
+ # each subclass has a copy of parent's attribute.
143
+ #
144
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
145
+ # define inheritable writer for.
146
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
147
+ # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
148
+ #
149
+ # @api public
150
+ #
151
+ # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
152
+ # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
153
+ def class_inheritable_writer(*ivars)
154
+ instance_writer = ivars.pop[:instance_writer] if ivars.last.is_a?(Hash)
155
+ ivars.each do |ivar|
156
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
157
+ def self.#{ivar}=(obj)
158
+ @#{ivar} = obj
159
+ end
160
+ RUBY
161
+ unless instance_writer == false
162
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
163
+ def #{ivar}=(obj) self.class.#{ivar} = obj end
164
+ RUBY
165
+ end
166
+ end
167
+ end
168
+
169
+ # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
170
+ # each subclass has a copy of parent's attribute.
171
+ #
172
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
173
+ # define inheritable accessor for.
174
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
175
+ # @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
176
+ #
177
+ # @api public
178
+ def class_inheritable_accessor(*syms)
179
+ class_inheritable_reader(*syms)
180
+ class_inheritable_writer(*syms)
181
+ end
182
+ end
183
+
184
+
185
+ class Fixnum
186
+ def try_dup
187
+ self
188
+ end
189
+ end
@@ -0,0 +1,24 @@
1
+ require 'time'
2
+ DateTime.class_eval do
3
+ #
4
+ # Parses the time but never fails.
5
+ #
6
+ # A flattened time -- a 12-digit YYYYmmddHHMMMSS -- is treated as a UTC
7
+ # datetime.
8
+ #
9
+ def self.parse_safely dt
10
+ begin
11
+ if dt.to_s =~ /\A\d{12}Z?\z/
12
+ parse(dt+'Z', true)
13
+ else
14
+ parse(dt, true)
15
+ end
16
+ rescue
17
+ nil
18
+ end
19
+ end
20
+
21
+ def self.parse_and_flatten str
22
+ parse_safely(str).to_flat
23
+ end
24
+ end
@@ -0,0 +1,82 @@
1
+
2
+ Object.class_eval do
3
+ def to_flat() [to_s] end
4
+ end
5
+
6
+ module Enumerable
7
+ alias_method :to_flat, :to_a
8
+ end
9
+
10
+ Struct.class_eval do
11
+ #
12
+ # The last portion of the class in underscored form
13
+ # note memoization
14
+ #
15
+ def self.resource_name
16
+ @resource_name ||= self.to_s.gsub(%r{.*::}, '').underscore.to_sym
17
+ end
18
+ #
19
+ # Flatten for packing as resource name followed by all fields
20
+ #
21
+ def to_flat include_key=true
22
+ if include_key.is_a? Proc
23
+ sort_key = include_key.call(self)
24
+ elsif (! include_key.blank?) && respond_to?(:key)
25
+ sort_key = [self.class.resource_name, key].flatten.join("-")
26
+ else
27
+ sort_key = self.class.resource_name
28
+ end
29
+ [sort_key, *to_a] # .map(&:to_flat).flatten
30
+ end
31
+ end
32
+
33
+ module HashLike
34
+ #
35
+ # Flatten for packing as resource name followed by all fields
36
+ #
37
+ def to_flat include_key=true
38
+ if include_key.is_a? Proc
39
+ sort_key = include_key.call(self)
40
+ elsif include_key && respond_to?(:key)
41
+ sort_key = [self.class.resource_name, key].flatten.join("-")
42
+ else
43
+ sort_key = self.class.resource_name
44
+ end
45
+ [sort_key, *to_a] # .map(&:to_flat).flatten
46
+ end
47
+ end
48
+
49
+ Hash.class_eval do
50
+ def to_flat
51
+ map do |k, v|
52
+ [k.to_flat, v.to_flat].join(":")
53
+ end
54
+ end
55
+ end
56
+
57
+ class Time
58
+ # strftime() format to flatten a date
59
+ FLAT_FORMAT = "%Y%m%d%H%M%S"
60
+ # Flatten
61
+ def to_flat
62
+ strftime(FLAT_FORMAT)
63
+ end
64
+ end
65
+
66
+ class Date
67
+ # strftime() format to flatten a date
68
+ FLAT_FORMAT = "%Y%m%d"
69
+ # Flatten
70
+ def to_flat
71
+ strftime(FLAT_FORMAT)
72
+ end
73
+ end
74
+
75
+ class DateTime < Date
76
+ # strftime() format to flatten a date
77
+ FLAT_FORMAT = "%Y%m%d%H%M%S"
78
+ # Flatten
79
+ def to_flat
80
+ strftime(FLAT_FORMAT)
81
+ end
82
+ end
@@ -0,0 +1,120 @@
1
+ #
2
+ # h2. extensions/hash.rb -- hash extensions
3
+ #
4
+
5
+ require 'set'
6
+ class Hash
7
+
8
+ # Slice a hash to include only the given keys. This is useful for
9
+ # limiting an options hash to valid keys before passing to a method:
10
+ #
11
+ # def search(criteria = {})
12
+ # assert_valid_keys(:mass, :velocity, :time)
13
+ # end
14
+ #
15
+ # search(options.slice(:mass, :velocity, :time))
16
+ # Returns a new hash with only the given keys.
17
+ def slice(*keys)
18
+ allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
19
+ reject{|key,| !allowed.include?(key) }
20
+ end
21
+ #
22
+ # Replace the hash with only the given keys.
23
+ #
24
+ def slice!(*keys)
25
+ replace(slice(*keys))
26
+ end
27
+ #
28
+ # #values_of is an alias for #values_at, but can be called on a Hash, a
29
+ # Struct, or an instance of a class that includes HashLike
30
+ #
31
+ alias_method :values_of, :values_at
32
+
33
+ #
34
+ # Create a hash from an array of keys and corresponding values.
35
+ #
36
+ def self.zip(keys, values, default=nil, &block)
37
+ hash = block_given? ? Hash.new(&block) : Hash.new(default)
38
+ keys.zip(values){|key,val| hash[key]=val }
39
+ hash
40
+ end
41
+
42
+ # lambda for recursive merges
43
+ Hash::DEEP_MERGER = proc do |key,v1,v2|
44
+ (v1.respond_to?(:merge) && v2.respond_to?(:merge)) ? v1.merge(v2.compact, &Hash::DEEP_MERGER) : (v2.nil? ? v1 : v2)
45
+ end
46
+
47
+ #
48
+ # Merge hashes recursively.
49
+ # Nothing special happens to array values
50
+ #
51
+ # x = { :subhash => { 1 => :val_from_x, 222 => :only_in_x, 333 => :only_in_x }, :scalar => :scalar_from_x}
52
+ # y = { :subhash => { 1 => :val_from_y, 999 => :only_in_y }, :scalar => :scalar_from_y }
53
+ # x.deep_merge y
54
+ # => {:subhash=>{1=>:val_from_y, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_y}
55
+ # y.deep_merge x
56
+ # => {:subhash=>{1=>:val_from_x, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_x}
57
+ #
58
+ # Nil values always lose.
59
+ #
60
+ # x = {:subhash=>{:nil_in_x=>nil, 1=>:val1,}, :nil_in_x=>nil}
61
+ # y = {:subhash=>{:nil_in_x=>5}, :nil_in_x=>5}
62
+ # y.deep_merge x
63
+ # => {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
64
+ # x.deep_merge y
65
+ # => {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
66
+ #
67
+ def deep_merge hsh2
68
+ merge hsh2, &Hash::DEEP_MERGER
69
+ end
70
+
71
+ def deep_merge! hsh2
72
+ merge! hsh2, &Hash::DEEP_MERGER
73
+ end
74
+
75
+
76
+ #
77
+ # Treat hash as tree of hashes:
78
+ #
79
+ # x = { 1 => :val, :subhash => { 1 => :val1 } }
80
+ # x.deep_set(:subhash, 3, 4)
81
+ # # => { 1 => :val, :subhash => { 1 => :val1, 3 => 4 } }
82
+ # x.deep_set(:subhash, 1, :newval)
83
+ # # => { 1 => :val, :subhash => { 1 => :newval, 3 => 4 } }
84
+ #
85
+ #
86
+ def deep_set *args
87
+ hsh = self
88
+ head_keys = args[0..-3]
89
+ last_key = args[-2]
90
+ val = args[-1]
91
+ # grab last subtree (building out if necessary)
92
+ head_keys.each{|key| hsh = (hsh[key] ||= {}) }
93
+ # set leaf value
94
+ hsh[last_key] = val
95
+ end
96
+
97
+ # Stolen from ActiveSupport::CoreExtensions::Hash::ReverseMerge.
98
+ def reverse_merge(other_hash)
99
+ other_hash.merge(self)
100
+ end
101
+
102
+ # Stolen from ActiveSupport::CoreExtensions::Hash::ReverseMerge.
103
+ def reverse_merge!(other_hash)
104
+ replace(reverse_merge(other_hash))
105
+ end
106
+
107
+ #
108
+ # remove all key-value pairs where the value is nil
109
+ #
110
+ def compact
111
+ reject{|key,val| val.nil? }
112
+ end
113
+ #
114
+ # Replace the hash with its compacted self
115
+ #
116
+ def compact!
117
+ replace(compact)
118
+ end
119
+
120
+ end
@@ -0,0 +1,119 @@
1
+ module Wukong
2
+ #
3
+ # A hashlike has to
4
+ #
5
+ # *
6
+ # * The arguments to your initializer should be the same as the keys, in order
7
+ # If not, you must override #from_hash
8
+ #
9
+ #
10
+ module HashLike
11
+
12
+ # List of possible keys --
13
+ # delegates to the class
14
+ def keys
15
+ self.class.keys
16
+ end
17
+
18
+ #
19
+ # Return a Hash containing only values for the given keys.
20
+ #
21
+ # Since this is intended to mirror Hash#slice it will harmlessly ignore keys
22
+ # not present in the struct. They will be unset (hsh.include? is not true)
23
+ # as opposed to nil.
24
+ #
25
+ def slice *keys
26
+ keys.inject({}) do |hsh, key|
27
+ hsh[key] = send(key) if respond_to?(key)
28
+ hsh
29
+ end
30
+ end
31
+
32
+ #
33
+ # values_at like a hash
34
+ #
35
+ # Since this is intended to mirror Hash#values_at it will harmlessly ignore
36
+ # keys not present in the struct
37
+ #
38
+ def values_of *keys
39
+ keys.map{|key| self.send(key) if respond_to?(key) }
40
+ end
41
+
42
+ #
43
+ # Convert to a hash
44
+ #
45
+ def to_hash
46
+ slice(*self.class.members)
47
+ end
48
+
49
+ #
50
+ # Analagous to Hash#each_pair
51
+ #
52
+ def pairs
53
+ self.class.members.map{|attr| [attr, self[attr]] }
54
+ end
55
+
56
+ #
57
+ # Analagous to Hash#each_pair
58
+ #
59
+ def each_pair *args, &block
60
+ pairs.each(*args, &block)
61
+ end
62
+
63
+ #
64
+ # Analagous to Hash#merge
65
+ #
66
+ def merge *args
67
+ self.dup.merge! *args
68
+ end
69
+ def merge! hsh, &block
70
+ raise "can't handle block arg yet" if block
71
+ hsh.each_pair{|key, val| self.send("#{key}=", val) if self.respond_to?("#{key}=") }
72
+ self
73
+ end
74
+ alias_method :update, :merge!
75
+
76
+ #
77
+ # Merge hashes recursively.
78
+ # Nothing special happens to array values
79
+ #
80
+ # x = { :subhash => { 1 => :val_from_x, 222 => :only_in_x, 333 => :only_in_x }, :scalar => :scalar_from_x}
81
+ # y = { :subhash => { 1 => :val_from_y, 999 => :only_in_y }, :scalar => :scalar_from_y }
82
+ # x.deep_merge y
83
+ # => {:subhash=>{1=>:val_from_y, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_y}
84
+ # y.deep_merge x
85
+ # => {:subhash=>{1=>:val_from_x, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_x}
86
+ #
87
+ def deep_merge hsh2
88
+ merge hsh2, &Hash::DEEP_MERGER
89
+ end
90
+
91
+ module ClassMethods
92
+ #
93
+ # Instantiate an instance of the struct from a hash
94
+ #
95
+ # Specify has_symbol_keys if the supplied hash's keys are symbolic;
96
+ # otherwise they must be uniformly strings
97
+ #
98
+ def from_hash(hsh, has_symbol_keys=false)
99
+ keys = self.keys
100
+ keys = keys.map(&:to_sym) if has_symbol_keys
101
+ self.new *hsh.values_of(*keys)
102
+ end
103
+ #
104
+ # The last portion of the class in underscored form
105
+ # note memoization
106
+ #
107
+ def self.resource_name
108
+ @resource_name ||= self.to_s.gsub(%r{.*::}, '').underscore.to_sym
109
+ end
110
+ end
111
+
112
+ def self.included base
113
+ base.class_eval do
114
+ extend ClassMethods
115
+ end
116
+ end
117
+ end
118
+
119
+ end