Empact-roxml 2.4.3 → 2.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/History.txt +65 -0
  2. data/Manifest.txt +11 -6
  3. data/README.rdoc +48 -26
  4. data/Rakefile +5 -2
  5. data/TODO +30 -31
  6. data/examples/active_record.rb +70 -0
  7. data/examples/amazon.rb +1 -1
  8. data/examples/current_weather.rb +1 -1
  9. data/examples/library.rb +40 -0
  10. data/examples/posts.rb +8 -8
  11. data/examples/twitter.rb +2 -2
  12. data/examples/xml/active_record.xml +70 -0
  13. data/lib/roxml.rb +174 -174
  14. data/lib/roxml/definition.rb +165 -89
  15. data/lib/roxml/extensions/deprecation.rb +5 -0
  16. data/lib/roxml/extensions/string/conversions.rb +2 -3
  17. data/lib/roxml/hash_definition.rb +26 -25
  18. data/lib/roxml/xml.rb +15 -6
  19. data/lib/roxml/xml/parsers/libxml.rb +14 -6
  20. data/lib/roxml/xml/parsers/rexml.rb +16 -5
  21. data/lib/roxml/xml/references.rb +14 -17
  22. data/roxml.gemspec +14 -5
  23. data/spec/definition_spec.rb +563 -0
  24. data/spec/examples/active_record_spec.rb +40 -0
  25. data/spec/examples/library_spec.rb +41 -0
  26. data/spec/roxml_spec.rb +372 -0
  27. data/spec/shared_specs.rb +15 -0
  28. data/spec/spec_helper.rb +21 -4
  29. data/spec/string_spec.rb +15 -0
  30. data/spec/xml/parser_spec.rb +47 -0
  31. data/test/fixtures/book_valid.xml +1 -1
  32. data/test/fixtures/person_with_guarded_mothers.xml +3 -3
  33. data/test/mocks/mocks.rb +57 -45
  34. data/test/test_helper.rb +1 -1
  35. data/test/unit/definition_test.rb +161 -12
  36. data/test/unit/deprecations_test.rb +97 -0
  37. data/test/unit/to_xml_test.rb +30 -1
  38. data/test/unit/xml_bool_test.rb +15 -3
  39. data/test/unit/xml_construct_test.rb +6 -6
  40. data/test/unit/xml_hash_test.rb +18 -0
  41. data/test/unit/xml_initialize_test.rb +6 -3
  42. data/test/unit/xml_namespace_test.rb +1 -0
  43. data/test/unit/xml_object_test.rb +66 -5
  44. data/test/unit/xml_text_test.rb +3 -0
  45. metadata +57 -24
  46. data/test/unit/array_test.rb +0 -16
  47. data/test/unit/freeze_test.rb +0 -71
  48. data/test/unit/inheritance_test.rb +0 -63
  49. data/test/unit/overriden_output_test.rb +0 -33
  50. data/test/unit/roxml_test.rb +0 -60
  51. data/test/unit/string_test.rb +0 -11
@@ -1,8 +1,19 @@
1
1
  require File.join(File.dirname(__FILE__), 'hash_definition')
2
2
 
3
+ class Module
4
+ def bool_attr_reader(*attrs)
5
+ attrs.each do |attr|
6
+ define_method :"#{attr}?" do
7
+ instance_variable_get(:"@#{attr}") || false
8
+ end
9
+ end
10
+ end
11
+ end
12
+
3
13
  module ROXML
4
14
  class Definition # :nodoc:
5
- attr_reader :name, :type, :hash, :blocks, :accessor, :to_xml
15
+ attr_reader :name, :type, :wrapper, :hash, :blocks, :accessor, :to_xml
16
+ bool_attr_reader :name_explicit, :array, :cdata, :required, :frozen
6
17
 
7
18
  class << self
8
19
  def silence_xml_name_warning?
@@ -16,21 +27,24 @@ module ROXML
16
27
 
17
28
  def initialize(sym, *args, &block)
18
29
  @accessor = sym
19
- @opts = extract_options!(args)
20
- @default = @opts.delete(:else)
21
- @to_xml = @opts.delete(:to_xml)
22
- @name_explicit = @opts.has_key?(:from)
23
-
24
- if @opts.has_key?(:readonly)
25
- raise ArgumentError, "There is no 'readonly' option. You probably mean to use :frozen => true"
30
+ if @accessor.to_s.ends_with?('_on')
31
+ ActiveSupport::Deprecation.warn "In 3.0, attributes with names ending with _on will default to Date type, rather than :text"
32
+ end
33
+ if @accessor.to_s.ends_with?('_at')
34
+ ActiveSupport::Deprecation.warn "In 3.0, attributes with names ending with _at will default to DateTime type, rather than :text"
26
35
  end
27
36
 
28
- @opts.reverse_merge!(:as => [], :in => nil)
29
- @opts[:as] = [*@opts[:as]]
37
+ opts = extract_options!(args)
38
+ opts[:as] ||= :bool if @accessor.to_s.ends_with?('?')
30
39
 
31
- @type = extract_type(args)
32
- @opts[:as] << :bool if @accessor.to_s.ends_with?('?')
40
+ @array = opts[:as].is_a?(Array) || extract_from_as(opts, :array, "Please use [] around your usual type declaration")
41
+ @blocks = collect_blocks(block, opts[:as])
33
42
 
43
+ if opts.has_key?(:readonly)
44
+ raise ArgumentError, "There is no 'readonly' option. You probably mean to use :frozen => true"
45
+ end
46
+
47
+ @type = extract_type(args, opts)
34
48
  if @type.try(:xml_name_without_deprecation?)
35
49
  unless self.class.silence_xml_name_warning?
36
50
  warn "WARNING: As of 2.3, a breaking change has been in the naming of sub-objects. " +
@@ -38,14 +52,22 @@ module ROXML
38
52
  "Use :from on the parent declaration to override this behavior. Set ROXML::SILENCE_XML_NAME_WARNING to avoid this message."
39
53
  self.class.silence_xml_name_warning!
40
54
  end
41
- @opts[:from] ||= @type.tag_name
42
- else
43
- @opts[:from] ||= variable_name
55
+ opts[:from] ||= @type.tag_name
44
56
  end
45
57
 
46
- @blocks = collect_blocks(block, @opts[:as])
58
+ if opts[:from] == :content
59
+ opts[:from] = '.'
60
+ elsif opts[:from] == :name
61
+ opts[:from] = '*'
62
+ elsif opts[:from] == :attr
63
+ @type = :attr
64
+ opts[:from] = nil
65
+ elsif opts[:from].to_s.starts_with?('@')
66
+ @type = :attr
67
+ opts[:from].sub!('@', '')
68
+ end
47
69
 
48
- @name = @opts[:from].to_s
70
+ @name = (opts[:from] || variable_name).to_s
49
71
  @name = @name.singularize if hash? || array?
50
72
  if hash? && (hash.key.name? || hash.value.name?)
51
73
  @name = '*'
@@ -55,63 +77,43 @@ module ROXML
55
77
  end
56
78
 
57
79
  def variable_name
58
- accessor.to_s.ends_with?('?') ? accessor.to_s.chomp('?') : accessor.to_s
80
+ accessor.to_s.chomp('?')
59
81
  end
60
82
 
61
83
  def hash
62
- @hash ||= HashDefinition.new(@opts.delete(:hash), name) if hash?
84
+ if hash?
85
+ @type.wrapper ||= name
86
+ @type
87
+ end
63
88
  end
64
89
 
65
90
  def hash?
66
- @type == :hash
91
+ @type.is_a?(HashDefinition)
67
92
  end
68
93
 
69
94
  def name?
70
95
  @name == '*'
71
96
  end
72
97
 
73
- def name_explicit?
74
- @name_explicit
75
- end
76
-
77
98
  def content?
78
- @type == :content
79
- end
80
-
81
- def array?
82
- @opts[:as].include? :array
83
- end
84
-
85
- def cdata?
86
- @opts[:as].include? :cdata
87
- end
88
-
89
- def wrapper
90
- @opts[:in]
91
- end
92
-
93
- def required?
94
- @opts[:required]
95
- end
96
-
97
- def freeze?
98
- @opts[:frozen]
99
+ @name == '.'
99
100
  end
100
101
 
101
102
  def default
102
- @default ||= [] if array?
103
- @default ||= {} if hash?
103
+ if @default.nil?
104
+ @default = [] if array?
105
+ @default = {} if hash?
106
+ end
104
107
  @default.duplicable? ? @default.dup : @default
105
108
  end
106
109
 
107
110
  def to_ref(inst)
108
111
  case type
109
- when :attr then XMLAttributeRef
110
- when :content then XMLTextRef
111
- when :text then XMLTextRef
112
- when :hash then XMLHashRef
113
- when Symbol then raise ArgumentError, "Invalid type argument #{opts.type}"
114
- else XMLObjectRef
112
+ when :attr then XMLAttributeRef
113
+ when :text then XMLTextRef
114
+ when HashDefinition then XMLHashRef
115
+ when Symbol then raise ArgumentError, "Invalid type argument #{type}"
116
+ else XMLObjectRef
115
117
  end.new(self, inst)
116
118
  end
117
119
 
@@ -127,13 +129,13 @@ module ROXML
127
129
 
128
130
  BLOCK_TO_FLOAT = lambda do |val|
129
131
  all(val) do |v|
130
- Float(v) unless blank_string?(v)
132
+ Float(v) unless v.blank?
131
133
  end
132
134
  end
133
135
 
134
136
  BLOCK_TO_INT = lambda do |val|
135
137
  all(val) do |v|
136
- Integer(v) unless blank_string?(v)
138
+ Integer(v) unless v.blank?
137
139
  end
138
140
  end
139
141
 
@@ -147,30 +149,20 @@ module ROXML
147
149
  default
148
150
  end
149
151
  end
150
-
151
- def self.blank_string?(value)
152
- value.is_a?(String) && value.blank?
153
- end
154
-
155
- BLOCK_SHORTHANDS = {
152
+
153
+ CORE_BLOCK_SHORTHANDS = {
154
+ # Core Shorthands
156
155
  :integer => BLOCK_TO_INT, # deprecated
157
156
  Integer => BLOCK_TO_INT,
158
157
  :float => BLOCK_TO_FLOAT, # deprecated
159
158
  Float => BLOCK_TO_FLOAT,
160
- Date => lambda do |val|
161
- if defined?(Date)
162
- all(val) {|v| Date.parse(v) unless blank_string?(v) }
163
- end
164
- end,
165
- DateTime => lambda do |val|
166
- if defined?(DateTime)
167
- all(val) {|v| DateTime.parse(v) unless blank_string?(v) }
159
+ Fixnum => lambda do |val|
160
+ all(val) do |v|
161
+ v.to_i unless v.blank?
168
162
  end
169
163
  end,
170
164
  Time => lambda do |val|
171
- if defined?(Time)
172
- all(val) {|v| Time.parse(v) unless blank_string?(v) }
173
- end
165
+ all(val) {|v| Time.parse(v) unless v.blank? }
174
166
  end,
175
167
 
176
168
  :bool => nil,
@@ -186,22 +178,54 @@ module ROXML
186
178
  end
187
179
  }
188
180
 
181
+ def self.block_shorthands
182
+ # dynamically load these shorthands at class definition time, but
183
+ # only if they're already availbable
184
+ returning CORE_BLOCK_SHORTHANDS do |blocks|
185
+ blocks.reverse_merge!(BigDecimal => lambda do |val|
186
+ all(val) do |v|
187
+ BigDecimal.new(v) unless v.blank?
188
+ end
189
+ end) if defined?(BigDecimal)
190
+
191
+ blocks.reverse_merge!(DateTime => lambda do |val|
192
+ if defined?(DateTime)
193
+ all(val) {|v| DateTime.parse(v) unless v.blank? }
194
+ end
195
+ end) if defined?(DateTime)
196
+
197
+ blocks.reverse_merge!(Date => lambda do |val|
198
+ if defined?(Date)
199
+ all(val) {|v| Date.parse(v) unless v.blank? }
200
+ end
201
+ end) if defined?(Date)
202
+ end
203
+ end
204
+
189
205
  def collect_blocks(block, as)
190
- ActiveSupport::Deprecation.warn ":as => :float is deprecated. Use :as => Float instead" if as.include?(:float)
191
- ActiveSupport::Deprecation.warn ":as => :integer is deprecated. Use :as => Integer instead" if as.include?(:integer)
206
+ ActiveSupport::Deprecation.warn ":as => :float is deprecated. Use :as => Float instead" if as == :float
207
+ ActiveSupport::Deprecation.warn ":as => :integer is deprecated. Use :as => Integer instead" if as == :integer
208
+
209
+ if as.is_a?(Array)
210
+ unless as.one? || as.empty?
211
+ raise ArgumentError, "multiple :as types (#{as.map(&:inspect).join(', ')}) is not supported. Use a block if you want more complicated behavior."
212
+ end
192
213
 
193
- shorthands = as & BLOCK_SHORTHANDS.keys
194
- if shorthands.size > 1
195
- raise ArgumentError, "multiple block shorthands supplied #{shorthands.map(&:to_s).join(', ')}"
214
+ as = as.first
196
215
  end
197
216
 
198
- shorthand = shorthands.first
199
- if shorthand == :bool
217
+ if as == :bool
200
218
  # if a second block is present, and we can't coerce the xml value
201
219
  # to bool, we need to be able to pass it to the user-provided block
202
- shorthand = block ? :bool_combined : :bool_standalone
220
+ as = (block ? :bool_combined : :bool_standalone)
203
221
  end
204
- [BLOCK_SHORTHANDS[shorthand], block].compact
222
+ as = self.class.block_shorthands.fetch(as) do
223
+ unless as.respond_to?(:from_xml) || as.try(:first).respond_to?(:from_xml) || (as.is_a?(Hash) && !(as.keys & HASH_KEYS).empty?)
224
+ ActiveSupport::Deprecation.warn "#{as.inspect} is not a valid type declaration. ROXML will raise in this case in version 3.0" unless as.nil?
225
+ end
226
+ nil
227
+ end
228
+ [as, block].compact
205
229
  end
206
230
 
207
231
  def extract_options!(args)
@@ -210,21 +234,60 @@ module ROXML
210
234
  args.push(opts)
211
235
  opts = {}
212
236
  end
237
+
238
+ @default = opts.delete(:else)
239
+ @to_xml = opts.delete(:to_xml)
240
+ @name_explicit = opts.has_key?(:from) && opts[:from].is_a?(String)
241
+ @cdata = opts.delete(:cdata)
242
+ @required = opts.delete(:required)
243
+ @frozen = opts.delete(:frozen)
244
+ @wrapper = opts.delete(:in)
245
+
246
+ @cdata ||= extract_from_as(opts, :cdata, "Please use :cdata => true")
247
+
248
+ if opts[:as].is_a?(Array) && opts[:as].size > 1
249
+ ActiveSupport::Deprecation.warn ":as should point to a single item. #{opts[:as].join(', ')} should be declared some other way."
250
+ end
251
+
213
252
  opts
214
253
  end
215
254
 
216
- def extract_type(args)
217
- types = (@opts.keys & TYPE_KEYS)
255
+ def extract_from_as(opts, entry, message)
256
+ # remove with deprecateds...
257
+ if [*opts[:as]].include?(entry)
258
+ ActiveSupport::Deprecation.warn ":as => #{entry.inspect} is deprecated. #{message}"
259
+ if opts[:as] == entry
260
+ opts[:as] = nil
261
+ else
262
+ opts[:as].delete(entry)
263
+ end
264
+ true
265
+ end
266
+ end
267
+
268
+ def extract_type(args, opts)
269
+ types = (opts.keys & TYPE_KEYS)
218
270
  # type arg
219
271
  if args.one? && types.empty?
220
272
  type = args.first
221
273
  if type.is_a? Array
222
- @opts[:as] << :array
223
- return type.first
274
+ ActiveSupport::Deprecation.warn "Array declarations should be passed as the :as parameter, for future release."
275
+ @array = true
276
+ return type.first || :text
224
277
  elsif type.is_a? Hash
225
- @opts[:hash] = type
226
- return :hash
278
+ ActiveSupport::Deprecation.warn "Hash declarations should be passed as the :as parameter, for future release."
279
+ return HashDefinition.new(type)
280
+ elsif type == :content
281
+ ActiveSupport::Deprecation.warn ":content as a type declaration is deprecated. Use :from => '.' or :from => :content instead"
282
+ opts[:from] = :content
283
+ return :text
284
+ elsif type == :attr
285
+ ActiveSupport::Deprecation.warn ":attr as a type declaration is deprecated. Use :from => '@attr_name' or :from => :attr instead"
286
+ opts[:from].sub!('@', '') if opts[:from].to_s.starts_with?('@') # this is added back next line...
287
+ opts[:from] = opts[:from].nil? ? :attr : "@#{opts[:from]}"
288
+ return :attr
227
289
  else
290
+ ActiveSupport::Deprecation.warn "Type declarations should be passed as the :as parameter, for future release."
228
291
  return type
229
292
  end
230
293
  end
@@ -234,9 +297,22 @@ module ROXML
234
297
  "an options hash, with the type and options optional"
235
298
  end
236
299
 
300
+ if opts[:as].is_a?(Hash)
301
+ return HashDefinition.new(opts[:as])
302
+ elsif opts[:as].respond_to?(:from_xml)
303
+ return opts[:as]
304
+ elsif opts[:as].is_a?(Array) && opts[:as].first.respond_to?(:from_xml)
305
+ @array = true
306
+ return opts[:as].first
307
+ end
308
+
237
309
  # type options
238
310
  if types.one?
239
- @opts[:from] = @opts.delete(types.first)
311
+ opts[:from] = opts.delete(types.first)
312
+ if opts[:from] == :content
313
+ opts[:from] = 'content'
314
+ ActiveSupport::Deprecation.warn ":content is now a reserved as an alias for '.'. Use the string 'content' instead"
315
+ end
240
316
  types.first
241
317
  elsif types.empty?
242
318
  :text
@@ -245,4 +321,4 @@ module ROXML
245
321
  end
246
322
  end
247
323
  end
248
- end
324
+ end
@@ -6,6 +6,11 @@ require 'active_support/version'
6
6
  module ActiveSupport # :nodoc:all
7
7
  module Deprecation
8
8
  class << self
9
+ def warn_with_internals_exclusion(message = nil, callstack = caller)
10
+ warn_without_internals_exclusion(message, callstack.reject {|line| line =~ /\/roxml(-[\d\.]+)?\/lib\// })
11
+ end
12
+ alias_method_chain :warn, :internals_exclusion
13
+
9
14
  if VERSION::MAJOR <= 2 && VERSION::MINOR <= 1
10
15
  def deprecation_message(callstack, message = nil)
11
16
  message ||= "You are using deprecated behavior which will be removed from the next major or minor release"
@@ -17,6 +17,7 @@ module ROXML
17
17
  self
18
18
  end
19
19
  end
20
+ deprecate :to_utf
20
21
 
21
22
  #
22
23
  # Convert this string to iso-8850-1
@@ -29,14 +30,12 @@ module ROXML
29
30
  self
30
31
  end
31
32
  end
33
+ deprecate :to_latin
32
34
  end
33
35
  end
34
36
  end
35
37
  end
36
38
 
37
- class Object
38
- end
39
-
40
39
  class String
41
40
  def between(separator, &block)
42
41
  split(separator).collect(&block).join(separator)
@@ -3,20 +3,21 @@ module ROXML
3
3
  TYPE_KEYS = [:attr, :text, :hash, :content].freeze
4
4
 
5
5
  class HashDefinition # :nodoc:
6
- attr_reader :key, :value, :wrapper
6
+ attr_reader :key, :value
7
+ attr_accessor :wrapper
7
8
 
8
- def initialize(opts, wrapper)
9
+ def initialize(opts)
9
10
  unless (invalid_keys = opts.keys - HASH_KEYS).empty?
10
11
  raise ArgumentError, "Invalid Hash description keys: #{invalid_keys.join(', ')}"
11
12
  end
12
13
 
13
- @wrapper = wrapper
14
14
  if opts.has_key? :attrs
15
- @key = to_hash_args(opts, :attr, opts[:attrs][0])
16
- @value = to_hash_args(opts, :attr, opts[:attrs][1])
15
+ ActiveSupport::Deprecation.warn(":as => {:attrs} is going away in 3.0. Use explicit :key and :value instead.")
16
+ @key = to_hash_args(opts, :from => "@#{opts[:attrs][0]}")
17
+ @value = to_hash_args(opts, :from => "@#{opts[:attrs][1]}")
17
18
  else
18
- @key = to_hash_args opts, *fetch_element(opts, :key)
19
- @value = to_hash_args opts, *fetch_element(opts, :value)
19
+ @key = to_hash_args opts, fetch_element(opts, :key)
20
+ @value = to_hash_args opts, fetch_element(opts, :value)
20
21
  end
21
22
  end
22
23
 
@@ -24,32 +25,32 @@ module ROXML
24
25
  def fetch_element(opts, what)
25
26
  case opts[what]
26
27
  when Hash
27
- raise ArgumentError, "Hash #{what} is over-specified: #{opts[what].inspect}" unless opts[what].keys.one?
28
- type = opts[what].keys.first
29
- [type, opts[what][type]]
30
- when :content
31
- [:content, opts[:name]]
32
- when :name
33
- [:name, '*']
34
- when String
35
- [:text, opts[what]]
36
- when Symbol
37
- [:text, opts[what]]
28
+ if opts[what].keys.one?
29
+ ActiveSupport::Deprecation.warn(":as => {:key => {Type => 'name'} ... } is going away in 3.0. Use explicit :key => {:from => 'name', :as => Type} instead.")
30
+ type = opts[what].keys.first
31
+ case type
32
+ when :attr
33
+ {:from => "@#{opts[what][type]}"}
34
+ when :text
35
+ {:from => opts[what][type]}
36
+ else
37
+ {:as => type, :from => opts[what][type]}
38
+ end
39
+ else
40
+ opts[what]
41
+ end
42
+ when String, Symbol
43
+ {:from => opts[what]}
38
44
  else
39
45
  raise ArgumentError, "unrecognized hash parameter: #{what} => #{opts[what]}"
40
46
  end
41
47
  end
42
48
 
43
- def to_hash_args(args, type, name)
49
+ def to_hash_args(args, opts)
44
50
  args = [args] unless args.is_a? Array
45
51
 
46
52
  if args.one? && !(args.first.keys & HASH_KEYS).empty?
47
- opts = {type => name}
48
- if type == :content
49
- opts[:type] = :text
50
- (opts[:as] ||= []) << :content
51
- end
52
- Definition.new(name, opts)
53
+ Definition.new(nil, opts)
53
54
  else
54
55
  opts = args.extract_options!
55
56
  raise opts.inspect