ixtlan-babel 0.3.3 → 0.3.4

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.
@@ -0,0 +1,8 @@
1
+ require 'ixtlan/babel/serializer'
2
+ class DataMapper::Validations::ValidationErrorsSerializer < Ixtlan::Babel::Serializer
3
+
4
+ def to_hash( o = nil)
5
+ @model_or_models.to_hash
6
+ end
7
+
8
+ end
@@ -0,0 +1,6 @@
1
+ class DataMapper::Validations::ValidationErrorsSerializer < Ixtlan::Babel::Serializer
2
+ def to_hash( o = nil)
3
+ @model_or_models.to_hash
4
+ end
5
+
6
+ end
@@ -18,6 +18,7 @@
18
18
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
+ require 'ixtlan/babel/dm_validation_errors_serializer' if defined? DataMapper
21
22
  module Ixtlan
22
23
  module Babel
23
24
  class Factory
@@ -56,7 +57,7 @@ module Ixtlan
56
57
  def new_serializer( resource )
57
58
  if resource.respond_to?(:model)
58
59
  model = resource.model
59
- elsif resource.respond_to? :collect
60
+ elsif resource.respond_to?( :collect) && !resource.respond_to?( :to_hash)
60
61
  if resource.empty?
61
62
  return EmptyArraySerializer.new
62
63
  else
@@ -0,0 +1,95 @@
1
+ #
2
+ # Copyright (C) 2013 Christian Meier
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module Ixtlan
22
+ module Babel
23
+ class Factory
24
+
25
+ NANOSECONDS_IN_DAY = 86400*10**6
26
+
27
+ TIME_TO_S = Proc.new do |t|
28
+ t.strftime('%Y-%m-%dT%H:%M:%S.') + ("%06d" % t.usec) + t.strftime('%z')
29
+ end
30
+
31
+ DATE_TIME_TO_S = Proc.new do |dt|
32
+ dt.strftime('%Y-%m-%dT%H:%M:%S.') + ("%06d" % (dt.sec_fraction * NANOSECONDS_IN_DAY ) ) + dt.strftime('%z')
33
+ end
34
+
35
+ DEFAULT_MAP = {
36
+ 'DateTime' => DATE_TIME_TO_S,
37
+ 'ActiveSupport::TimeWithZone' => TIME_TO_S,
38
+ 'Time' => TIME_TO_S
39
+ }
40
+
41
+ class EmptyArraySerializer < Array
42
+ def use(arg)
43
+ self
44
+ end
45
+ end
46
+
47
+ def initialize(custom_serializers = {})
48
+ @map = DEFAULT_MAP.dup
49
+ @map.merge!(custom_serializers)
50
+ end
51
+
52
+ def add( clazz, &block )
53
+ @map[ clazz.to_s ] = block
54
+ end
55
+
56
+ def new_serializer( resource )
57
+ if resource.respond_to?(:model)
58
+ model = resource.model
59
+ elsif resource.respond_to?( :collect) && !resource.respond_to?( :to_hash)
60
+ if resource.empty?
61
+ return EmptyArraySerializer.new
62
+ else
63
+ p resource
64
+ p resource.class
65
+ r = resource.first
66
+ model = r.respond_to?( :model ) ? r.model : r.class
67
+ p model
68
+ end
69
+ else
70
+ model = resource.class
71
+ end
72
+ ser = const_retrieve( "#{model}Serializer" ).new( resource )
73
+ ser.add_custom_serializers( @map )
74
+ ser
75
+ end
76
+
77
+ def new( resource )
78
+ warn 'DEPRECATED use new_serializer instead'
79
+ new_serializer( resource )
80
+ end
81
+
82
+ def new_filter( clazz )
83
+ const_retrieve( "#{clazz}Filter" ).new( clazz )
84
+ end
85
+
86
+ def const_retrieve( const )
87
+ obj = Object
88
+ const.split(/::/).each do |part|
89
+ obj = obj.const_get( part )
90
+ end
91
+ obj
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,107 +1,58 @@
1
- require 'ixtlan/babel/config'
1
+ #
2
+ # Copyright (C) 2013 Christian Meier
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'ixtlan/babel/abstract_filter'
2
22
  module Ixtlan
3
23
  module Babel
4
- class HashFilter
24
+ class HashFilter < AbstractFilter
5
25
 
6
- def initialize(context_or_options = nil)
7
- use(context_or_options)
8
- end
9
-
10
- private
11
-
12
- def context
13
- @context ||= {}
14
- end
15
-
16
- public
17
-
18
- def add_custom_serializers(map)
19
- @map = map
20
- end
21
-
22
- def default_context_key( single = :single,
23
- collection = :collection )
24
- @single, @collection = single, collection
25
- end
26
-
27
- def []=( key, options )
28
- context[ key.to_sym ] = options if key
29
- end
30
-
31
- def []( key )
32
- context[ key.to_sym ] if key
33
- end
34
-
35
- def use( context_or_options )
36
- if context_or_options
37
- case context_or_options
38
- when Symbol
39
- if opts = context[ context_or_options ]
40
- @options = opts.dup
41
- end
42
- when Hash
43
- @options = context_or_options
44
- end
45
- else
46
- @options = nil
26
+ def filter( data )
27
+ if data
28
+ filter_data( data,
29
+ Context.new( options ) )
47
30
  end
48
- self
49
- end
50
-
51
- def filter( hash = {} )
52
- if hash
53
- filter_data( hash,
54
- Config.new( options_for( hash ) ) )
55
- end
56
- end
57
-
58
- def single_options
59
- @options || context[default_context_key[0]] || {}
60
- end
61
-
62
- def collection_options
63
- @options || context[default_context_key[1]] || {}
64
- end
65
-
66
- def root
67
- @root ||= single_options.key?(:root) ? single_options[:root].to_s : nil
68
31
  end
69
32
 
70
33
  private
71
34
 
72
- def options_for( hash )
73
- @options || (hash.is_a?(Array) ? collection_options : single_options)
74
- end
75
-
76
35
  def filter_array( array, options )
77
36
  array.collect do |item|
78
37
  if item.is_a?( Array ) || item.is_a?( Hash )
79
38
  filter_data( item, options )
80
39
  else
81
- item
40
+ serialize( item )
82
41
  end
83
42
  end
84
43
  end
85
44
 
86
- def serialize( data )
87
- if @map && ser = @map[ data.class.to_s ]
88
- ser.call(data)
89
- else
90
- data
91
- end
92
- end
93
-
94
- def filter_data( data, config )
45
+ def filter_data( data, context )
95
46
  result = {}
96
47
  data.each do |k,v|
97
48
  k = k.to_s
98
49
  case v
99
50
  when Hash
100
- result[ k ] = filter_data( v, config[ k ] ) if config.include?( k )
51
+ result[ k ] = filter_data( v, context[ k ] ) if context.include?( k )
101
52
  when Array
102
- result[ k ] = filter_array( v, config[ k ] ) if config.include?( k )
53
+ result[ k ] = filter_array( v, context[ k ] ) if context.include?( k )
103
54
  else
104
- result[ k ] = serialize( v ) if config.allowed?( k )
55
+ result[ k ] = serialize( v ) if context.allowed?( k )
105
56
  end
106
57
  end
107
58
  result
@@ -1,166 +1,69 @@
1
+ #
2
+ # Copyright (C) 2013 Christian Meier
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'ixtlan/babel/abstract_filter'
1
22
  module Ixtlan
2
23
  module Babel
3
- class HashFilter
4
-
5
- def initialize(context_or_options = nil)
6
- use(context_or_options)
7
- end
8
-
9
- private
10
-
11
- def context
12
- @context ||= {}
13
- end
14
-
15
- public
16
-
17
- def add_custom_serializers(map)
18
- @map = map
19
- end
20
-
21
- def default_context_key(single = :single, collection = :collection)
22
- @single, @collection = single, collection
23
- end
24
-
25
- def []=(key, options)
26
- context[key.to_sym] = options if key
27
- end
28
-
29
- def [](key)
30
- context[key.to_sym] if key
31
- end
32
-
33
- def use(context_or_options)
34
- if context_or_options
35
- case context_or_options
36
- when Symbol
37
- if opts = context[context_or_options]
38
- @options = opts.dup
39
- end
40
- when Hash
41
- @options = context_or_options
42
- end
43
- else
44
- @options = nil
24
+ class ModelFilter < AbstractFilter
25
+
26
+ def filter( model, &block )
27
+ if model
28
+ data = block.call( model )
29
+ filter_data( model, data,
30
+ Context.new( options ),
31
+ &block )
45
32
  end
46
- self
47
- end
48
-
49
- NO_MODEL = Object.new
50
- def NO_MODEL.send(*args)
51
- self
52
- end
53
- def NO_MODEL.[](*args)
54
- self
55
- end
56
-
57
- def filter(hash = {}, model = NO_MODEL, &block)
58
- filter_data(model, hash, hash.is_a?(Array) ? collection_options : single_options, &block) if hash
59
- end
60
-
61
- def single_options
62
- @options || context[default_context_key[0]] || {}
63
- end
64
-
65
- def collection_options
66
- @options || context[default_context_key[1]] || {}
67
- end
68
-
69
- def root
70
- @root ||= single_options.key?(:root) ? single_options[:root].to_s : nil
71
33
  end
72
34
 
73
35
  private
74
36
 
75
- def filter_data(model, data, options = {}, &block)
76
- model = NO_MODEL if model == data
77
- only = options[:only].collect { |o| o.to_s } if options[:only]
78
- except = (options[:except] || []).collect { |e| e.to_s }
79
-
80
- include =
81
- case options[:include]
82
- when Array
83
- options[:include].collect { |i| i.to_s }
84
- when Hash
85
- Hash[options[:include].collect {|k,v| [k.to_s, v]}]
37
+ def filter_array( models, options, &block )
38
+ models.collect do |i|
39
+ if i.respond_to? :attributes
40
+ filter_data(i, block.call(i), options, &block)
86
41
  else
87
- []
88
- end
89
- if model != NO_MODEL
90
- methods = (options[:methods] || []).collect { |e| e.to_s }
91
- methods.each do |m|
92
- data[m] = model.send(m.to_sym)
42
+ serialize( i )
93
43
  end
44
+ end
45
+ end
94
46
 
95
- include_methods = include.is_a?(Array) ? include : include.keys
96
- include_methods.each do |m|
97
- unless data.include?(m)
98
- raise "no block given to calculate the attributes from model" unless block
99
- models_or_model = model.send(m)
100
- if models_or_model.is_a?(Array) && models_or_model.first.is_a?(String)
101
- data[m] = models_or_model
102
- elsif models_or_model.respond_to?(:collect)
103
- data[m] = models_or_model.collect { |i| block.call(i) }
104
- else
105
- val = model.send(m)
106
- case val
107
- when Fixnum
108
- data[m] = val
109
- when String
110
- data[m] = val
111
- when TrueClass
112
- data[m] = val
113
- else
114
- data[m]= block.call(val)
115
- end
116
- end
117
- end
47
+ def setup_data(model, data, context)
48
+ context.methods.each do |m|
49
+ unless data.include?(m)
50
+ data[ m ] = model.send( m.to_sym )
118
51
  end
119
52
  end
120
- methods ||= []
53
+ end
54
+
55
+ def filter_data(model, data, context, &block)
56
+ setup_data(model, data, context)
57
+
121
58
  result = {}
122
59
  data.each do |k,v|
123
- case v
124
- when Hash
125
- if include.include?(k.to_s)
126
- case include
127
- when Array
128
- result[k.to_s] = filter_data(model.send(k), v, &block)
129
- when Hash
130
- result[k.to_s] = filter_data(model.send(k), v, include[k.to_s], &block)
131
- end
132
- end
133
- when Array
134
- if include.include?(k.to_s)
135
- models = model.send(k)
136
- j = -1
137
- case include
138
- when Array
139
- result[k.to_s] = v.collect do |i|
140
- j += 1
141
- if i.is_a?(Array) || i.is_a?(Hash)
142
- filter_data(models[j], i, &block)
143
- else
144
- i
145
- end
146
- end
147
- when Hash
148
- opts = include[k.to_s]
149
- result[k.to_s] = v.collect do |i|
150
- j += 1
151
- ndata = i.is_a?(Hash)? i : block.call(i)
152
- filter_data(models[j], ndata, opts, &block)
153
- end
154
- end
155
- end
60
+ k = k.to_s
61
+ if v.respond_to? :attributes
62
+ result[ k ] = filter_data( v, block.call(v), context[ k ], &block ) if context.include?( k )
63
+ elsif v.is_a? Array
64
+ result[ k ] = filter_array( v, context[ k ], &block ) if context.include?( k )
156
65
  else
157
- if methods.include?(k.to_s) || (only && only.include?(k.to_s)) || (only.nil? && !except.include?(k.to_s))
158
- if @map && ser = @map[v.class.to_s]
159
- result[k.to_s] = ser.call(v)
160
- else
161
- result[k.to_s] = v
162
- end
163
- end
66
+ result[ k ] = serialize( v ) if context.allowed?( k ) && ! v.respond_to?( :attributes )
164
67
  end
165
68
  end
166
69
  result
@@ -0,0 +1,160 @@
1
+ #
2
+ # Copyright (C) 2013 Christian Meier
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'multi_json'
22
+ require 'ixtlan/babel/hash_filter'
23
+ require 'ixtlan/babel/model_filter'
24
+ require 'ixtlan/babel/filter_config'
25
+ module Ixtlan
26
+ module Babel
27
+ class Serializer
28
+
29
+ def id
30
+ if @model_or_models.is_a? Array
31
+ super
32
+ else
33
+ @model_or_models.id
34
+ end
35
+ end
36
+
37
+ def initialize(model_or_models)
38
+ @model_or_models = model_or_models
39
+ end
40
+
41
+ def respond_to?(method)
42
+ @model_or_models.respond_to?(method)
43
+ end
44
+
45
+ def method_missing(method, *args, &block)
46
+ @model_or_models.send(method, *args, &block)
47
+ end
48
+
49
+ def add_custom_serializers(map)
50
+ filter.add_custom_serializers(map)
51
+ end
52
+
53
+ private
54
+
55
+ def self.config
56
+ @config ||= FilterConfig.new
57
+ end
58
+
59
+ def filter
60
+ @filter ||= @model_or_models.is_a?( Hash ) ? HashFilter.new : ModelFilter.new
61
+ end
62
+
63
+ protected
64
+
65
+ # for rails
66
+ def self.model(model = nil)
67
+ @model_class = model if model
68
+ @model_class ||= self.to_s.sub(/Serializer$/, '').constantize
69
+ end
70
+
71
+ # for rails
72
+ def self.model_name
73
+ model.model_name
74
+ end
75
+
76
+ def self.default_context_key(single = :single, collection = :collection)
77
+ config.default_context_key(single, collection)
78
+ end
79
+
80
+ def self.add_context(key, options = {})
81
+ config[key] = options
82
+ end
83
+
84
+ def self.root( root )
85
+ config.root = root
86
+ end
87
+
88
+ public
89
+
90
+ def use( context_or_options )
91
+ @context_or_options = context_or_options
92
+ self
93
+ end
94
+
95
+ def to_hash(options = nil)
96
+ setup_filter( options )
97
+ if collection?
98
+ @model_or_models.collect do |m|
99
+ filter_model( m )
100
+ end
101
+ else
102
+ filter_model( @model_or_models )
103
+ end
104
+ end
105
+
106
+ def to_json(options = nil)
107
+ to_hash(options).to_json
108
+ end
109
+
110
+ def setup_filter(options)
111
+ o = if collection?
112
+ self.class.config.collection_options( @context_or_options )
113
+ else
114
+ self.class.config.single_options( @context_or_options )
115
+ end
116
+ filter.options = o.merge!( options || {} )
117
+ filter.options[:root] ||= self.class.config.root
118
+ end
119
+ private :setup_filter
120
+
121
+ def collection?
122
+ @is_collection ||= @model_or_models.respond_to?(:collect) && ! @model_or_models.is_a?(Hash)
123
+ end
124
+ private :collection?
125
+
126
+ def to_xml(options = nil)
127
+ setup_filter
128
+
129
+ result = to_hash
130
+
131
+ root = config.root
132
+
133
+ if root && result.is_a?(Array) && root.respond_to?(:pluralize)
134
+ root = root.pluralize
135
+ end
136
+ result.to_xml :root => root
137
+ end
138
+
139
+ def to_yaml(options = nil)
140
+ to_hash(options).to_yaml
141
+ end
142
+
143
+ protected
144
+
145
+ def attr(model)
146
+ model.attributes if model
147
+ end
148
+
149
+ private
150
+
151
+ def filter_model( model )
152
+ if root = filter.options[:root]
153
+ { root.to_s => filter.filter( model ){ |model| attr(model) } }
154
+ else
155
+ filter.filter( model ){ |model| attr(model) }
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: ixtlan-babel
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.3
5
+ version: 0.3.4
6
6
  platform: ruby
7
7
  authors:
8
8
  - Christian Meier
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-28 00:00:00.000000000 Z
12
+ date: 2013-03-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -117,6 +117,7 @@ files:
117
117
  - lib/ixtlan-babel.rb
118
118
  - lib/ixtlan/babel/deserializer.rb
119
119
  - lib/ixtlan/babel/hash_filter.rb
120
+ - lib/ixtlan/babel/serializer.rb~
120
121
  - lib/ixtlan/babel/params_filter.rb
121
122
  - lib/ixtlan/babel/abstract_filter.rb
122
123
  - lib/ixtlan/babel/no_timestamp_serializer.rb
@@ -125,9 +126,12 @@ files:
125
126
  - lib/ixtlan/babel/context.rb
126
127
  - lib/ixtlan/babel/factory.rb
127
128
  - lib/ixtlan/babel/model_filter.rb~
129
+ - lib/ixtlan/babel/dm_validation_errors_serializer.rb
128
130
  - lib/ixtlan/babel/hash_filter.rb~
129
131
  - lib/ixtlan/babel/filter_config.rb~
130
132
  - lib/ixtlan/babel/hash_only_filter.rb~
133
+ - lib/ixtlan/babel/factory.rb~
134
+ - lib/ixtlan/babel/dm_validation_errors_serializer.rb~
131
135
  - lib/ixtlan/babel/context.rb~
132
136
  - lib/ixtlan/babel/model_filter.rb
133
137
  - lib/ixtlan/babel/hash_filter.rb-