wukong 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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