peachy 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,7 @@
1
+ == 0.2.0
2
+
3
+ * Major improvements
4
+ * Can now treat only children as if they are the first element in an array.
5
+ * Console output is now configurable.
6
+ * Test specs are now included in gem.
7
+
data/README.rdoc CHANGED
@@ -6,26 +6,34 @@ on the fly to match the elements and attributes of the XML.
6
6
  Source available at http://github.com/njpearman/Peachy
7
7
 
8
8
  == Install
9
- There is no gem available for Peachy yet. This is the next bit of work to
10
- complete. In the meantime, if you wish to mess around with Peachy, download and
11
- include the source files in /lib within your project.
9
+ The Peachy Ruby gem is available as you would expect. Run:
10
+
11
+ gem install peachy
12
+
13
+ then
14
+
15
+ require 'peachy'
16
+
17
+ in irb/your codebase to get going.
12
18
 
13
19
  == Usage
14
20
  The Peachy::Proxy is the key class in Peachy. Create a new instance of a
15
21
  Peachy::Proxy passing in either a raw XML string, or a Nokogiri::XML instance
16
22
 
17
- proxy = Peachy::Proxy.new :xml => '<xml><node>Peachy</node></xml>'
23
+ proxy = Peachy::Proxy.new('<xml><node>Peachy</node></xml>')
18
24
 
19
25
  or
20
26
 
21
- proxy = Peachy::Proxy.new :nokogiri => Nokogiri::XML('<xml><node>Peachy</node></xml>')
27
+ proxy = Peachy::Proxy.new(Nokogiri::XML('<xml><node>Peachy</node></xml>'))
22
28
 
23
29
  Once you have a Proxy, it's straightforward to drill down through the XML by
24
30
  node name:
25
31
 
26
- puts 'Contents: ' + proxy.xml.node
32
+ puts 'Contents: ' + proxy.xml.node.value
27
33
  -> Contents: Peachy
28
34
 
35
+ Either .value or .to_s can be called to get the contents of the current node.
36
+
29
37
  Peachy expects method names to be called in the Ruby convention of lowercase with
30
38
  underscores. It will do it's best to match method names to elements and attributes
31
39
  following different conventions (currently, this is camelCaseNames, PascalCaseNames
@@ -40,12 +48,21 @@ be abstracted out at some point.
40
48
 
41
49
  === Elements and Attributes
42
50
 
43
- Currently, elements and attributes are accessed in exactly the same way; that is,
51
+ Currently, elements and attributes are accessed in almost exactly the same way;
44
52
  call a method on your current node matching the attribute or element name that
45
- is required next.
46
- As Peachy is just for slurping XML, it makes it easy to know how to access
47
- the property that you're after if the conventions are the same for noth elements
48
- and attributes.
53
+ is required next. Elements need to have .value or .to_s called on them to get
54
+ the contents of the element, however.
55
+
56
+ E.g.
57
+
58
+ proxy = Peachy::Proxy.new('<peachy version="1.0.0"><node>Peachy is here.</node></peachy>')
59
+ puts "Element contents: " + proxy.peachy.node.value
60
+ => Element contents: Peachy is here
61
+ puts "Element attribute: " + proxy.peachy.version
62
+ => Element attribute: 1.0.0
63
+
64
+ Peachy is just for slurping XML, so this convention should make it easy to know
65
+ how to access the property that you're after, be they elements or attributes.
49
66
 
50
67
  === No method name match
51
68
 
@@ -0,0 +1,8 @@
1
+ class AlreadyAnOnlyChild < Exception
2
+ def initialize node_name
3
+ super <<MESSAGE
4
+ The '#{node_name}' node has already been accessed as a single child, but you are now trying to use it as a collection.
5
+ Do not try to access Peachy::Proxies in a mixed manner in your implementation.
6
+ MESSAGE
7
+ end
8
+ end
@@ -1,5 +1,5 @@
1
1
  class NoMatchingXmlPart < Exception
2
- def initialize method_name
3
- super "#{method_name} is not contained as a child of this node."
2
+ def initialize method_name, node_name
3
+ super "#{method_name} is not contained as a child of the node #{node_name}."
4
4
  end
5
5
  end
data/lib/peachy.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), 'already_an_only_child')
1
2
  require File.join(File.dirname(__FILE__), 'invalid_proxy_parameters')
2
3
  require File.join(File.dirname(__FILE__), 'method_not_in_ruby_convention')
3
4
  require File.join(File.dirname(__FILE__), 'no_matching_xml_part')
@@ -5,8 +6,19 @@ require File.join(File.dirname(__FILE__), 'peachy/convention_checks')
5
6
  require File.join(File.dirname(__FILE__), 'peachy/string_styler')
6
7
  require File.join(File.dirname(__FILE__), 'peachy/method_name')
7
8
  require File.join(File.dirname(__FILE__), 'peachy/method_mask')
9
+ require File.join(File.dirname(__FILE__), 'peachy/morph_into_array')
10
+ require File.join(File.dirname(__FILE__), 'peachy/my_meta_class')
11
+ require File.join(File.dirname(__FILE__), 'peachy/node_child_matcher')
12
+ require File.join(File.dirname(__FILE__), 'peachy/simple_content')
8
13
  require File.join(File.dirname(__FILE__), 'peachy/proxy')
9
14
  require File.join(File.dirname(__FILE__), 'peachy/childless_proxy_with_attributes')
10
15
 
11
16
  module Peachy
17
+ def self.whine
18
+ @whine = true
19
+ end
20
+
21
+ def self.whiny?
22
+ return @whine
23
+ end
12
24
  end
@@ -1,6 +1,7 @@
1
1
  module Peachy
2
2
  class ChildlessProxyWithAttributes < Proxy
3
3
  def value
4
+ acts_as_only_child
4
5
  @nokogiri_node.content
5
6
  end
6
7
 
@@ -8,13 +9,8 @@ module Peachy
8
9
  def generate_method_for_xml method_name
9
10
  check_for_convention(method_name)
10
11
  match = find_match_by_attributes method_name, nokogiri_node
11
- raise NoMatchingXmlPart.new method_name if match.nil?
12
- return create_content_child(match) {|child| define_child method_name, child }
13
- end
14
-
15
- def find_match_by_attributes method_name, node
16
- mapped = method_name.variations.map {|variation| node.attribute variation }
17
- mapped.find {|match| match != nil }
12
+ raise NoMatchingXmlPart.new method_name, node_name if match.nil?
13
+ return create_value(match) {|child| define_child method_name, child }
18
14
  end
19
15
  end
20
16
  end
@@ -0,0 +1,38 @@
1
+ module Peachy
2
+ module MorphIntoArray
3
+ def you_use_me_like_an_array method_name, block_given, *args
4
+ return ((block_given or args.size > 0) and array_can?(method_name))
5
+ #method_name == :[] and args.one? and args.first == 0
6
+ end
7
+
8
+ def array_can? method_name
9
+ Array.instance_methods.include?(method_name.to_s)
10
+ end
11
+
12
+ def mimic object_to_mimic
13
+ eval_on_singleton_class do
14
+ # NetBeans complains about this syntax, but it's fine.
15
+ define_method(:method_missing) do |method_name, *args, &block|
16
+ puts "[Peachy::Proxy] #{node_name} can only do '#{method_name}' with '#{args}' if an Array can. You see, really, #{node_name} is an array now." if Peachy.whiny?
17
+ return object_to_mimic.send(method_name, *args, &block)
18
+ end
19
+ end
20
+ end
21
+
22
+ def morph_into_array to_add_to_array, method_to_invoke, *args, &block
23
+ puts "[Peachy::Proxy] Currently acts as #{@acts_as}" if Peachy.whiny?
24
+ raise AlreadyAnOnlyChild.new(node_name) if is_an_only_child
25
+ puts "[Peachy::Proxy] So #{node_name} should be an Array, then." if Peachy.whiny?
26
+ mimic [to_add_to_array]
27
+ return send(method_to_invoke, *args, &block)
28
+ end
29
+
30
+ def acts_as_only_child
31
+ @acts_as = :only_child
32
+ end
33
+
34
+ def is_an_only_child
35
+ @acts_as == :only_child
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,8 @@
1
+ module Peachy
2
+ # MyMetaClass is a convenience Module for meta programming within a Class.
3
+ module MyMetaClass
4
+ def eval_on_singleton_class &block
5
+ (class << self; self; end).class_eval &block
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,22 @@
1
+ module Peachy
2
+ module NodeChildMatcher
3
+ # Runs the XPath for the method name against the underlying XML DOM,
4
+ # returning nil if no element or attribute matching the method name is found
5
+ # in the children of the current location in the DOM.
6
+ def find_matches method_name, node #=nokogiri_node
7
+ matches = node.xpath(xpath_for(method_name))
8
+ return nil if matches.length < 1
9
+ return matches
10
+ end
11
+
12
+ def find_match_by_attributes method_name, node
13
+ mapped = method_name.variations.map {|variation| node.attribute variation }
14
+ mapped.find {|match| match != nil }
15
+ end
16
+
17
+ # Gets the XPath for all variations of the MethodName instance
18
+ def xpath_for method_name
19
+ method_name.variations.map {|variation| "./#{variation}" } * '|'
20
+ end
21
+ end
22
+ end
data/lib/peachy/proxy.rb CHANGED
@@ -5,23 +5,18 @@ module Peachy
5
5
  class Proxy
6
6
  alias_method :original_method_missing, :method_missing
7
7
  extend MethodMask
8
- include ConventionChecks
8
+ include ConventionChecks, MorphIntoArray, MyMetaClass, NodeChildMatcher
9
9
 
10
- # This hides all public methods on the class except for 'methods' and
10
+ # This hides all public methods on the class except for 'methods', 'nil?'
11
11
  # 'respond_to?' and 'inspect', which I've found are too useful to hide for
12
12
  # the time being.
13
- hide_public_methods ['methods', 'respond_to?', 'inspect']
14
-
15
- # Takes a hash as an argument. Valid keys are:
16
- # :xml -
17
- # used to pass raw XML into the Proxy, and the XML parser will be
18
- # created on the fly.
19
- # :nokogiri -
20
- # can be used to pass in a Nokogiri::XML instance, if one has
21
- # already been created.
13
+ hide_public_methods ['methods', 'nil?', 'respond_to?', 'inspect']
14
+
15
+ # Takes either a string containing XML or a Nokogiri::XML::Element as the
16
+ # single argument.
22
17
  def initialize arguments
23
- @nokogiri_node = arguments[:nokogiri]
24
- @xml = arguments[:xml]
18
+ @xml = arguments if arguments.kind_of? String
19
+ @nokogiri_node = arguments if arguments.kind_of? Nokogiri::XML::Element
25
20
  end
26
21
 
27
22
  # Overloaded so that calls to methods representative of an XML element or
@@ -39,53 +34,82 @@ module Peachy
39
34
  # is made to call a method on a Peachy::Proxy that breaks this convention,
40
35
  # Peachy will simply raise a MethodNotInRubyConvention error.
41
36
  #
37
+ # Collections are referenced as an array following the name of the individual
38
+ # items of the collection.
39
+ #
40
+ # e.g.
41
+ # xml = <<XML
42
+ # <feed>
43
+ # <items>
44
+ # <item>Story 1</item>
45
+ # <item>Story 2</item>
46
+ # <item>Story 3</item>
47
+ # </items>
48
+ # </feed>
49
+ # XML
50
+ #
51
+ # @proxy = Peachy::Proxy xml
52
+ # @proxy.feed.items.item[0]
53
+ # => Story 1
54
+ # @proxy.feed.items.item[2]
55
+ # => Story 3
56
+ #
57
+ # Note that Peachy will try to manage the case where a
58
+ # single node is treated as the the first and only element in a collection.
59
+ # In this case, the pattern above will apply. Also, if an element is treated
60
+ # as a single child, but later treated as if it should be part of a
61
+ # collection, then an AlreadyASingleChild error will be raised. You can't
62
+ # expect a node to be both a single child AND a single element in a
63
+ # collection.
64
+ #
42
65
  # Any calls to undefined methods that include arguments or a block will be
43
66
  # deferred to the default implementation of method_missing.
44
- def method_missing method_name_symbol, *args, &block
45
- original_method_missing method_name_symbol, args, &block if args.any? or block_given?
46
- generate_method_for_xml MethodName.new(method_name_symbol)
67
+ #
68
+ def method_missing method_name, *args, &block
69
+ # check whether an Array method is called with arguments or a block
70
+ if you_use_me_like_an_array(method_name, block_given?, *args)
71
+ return morph_into_array(clone, method_name, *args, &block)
72
+ end
73
+
74
+ # standard method_missing for any other call with arguments or a block
75
+ original_method_missing method_name, args if args.any? or block_given?
76
+
77
+ # try to create a method for the element
78
+ child_proxy = generate_method_for_xml(MethodName.new(method_name))
79
+
80
+ if !child_proxy.nil?
81
+ # found a match, so flag as only child
82
+ acts_as_only_child
83
+ child_proxy
84
+ elsif array_can?(method_name)
85
+ # if child doesn't exist, see if the call might be a zero-argument
86
+ # Array call.
87
+ new_proxy = create_from_element(nokogiri_node)
88
+ morph_into_array(new_proxy, method_name)
89
+ else
90
+ # no matches, so throw
91
+ raise NoMatchingXmlPart.new(method_name, node_name)
92
+ end
47
93
  end
48
94
 
49
95
  private
50
96
  def generate_method_for_xml method_name
51
97
  check_for_convention(method_name)
52
- attribute_content = create_from_parent_with_attribute method_name, nokogiri_node
98
+ attribute_content = create_from_parent_with_attribute(method_name, nokogiri_node)
53
99
  return attribute_content unless attribute_content.nil?
54
- create_method_for_child_or_content method_name, nokogiri_node
55
- end
56
-
57
- def nokogiri_node
58
- raise InvalidProxyParameters.new(:xml => nil, :nokogiri => nil) if variables_are_nil?
59
- @nokogiri_node ||= Nokogiri::XML(@xml)
60
- end
61
-
62
- def variables_are_nil?
63
- @xml.nil? and @nokogiri_node.nil?
100
+ matches = find_matches(method_name, nokogiri_node)
101
+ matches.nil? ? nil : create_method_for_child_or_content(method_name, matches)
64
102
  end
65
103
 
66
- def create_method_for_child_or_content method_name, node
67
- matches = find_matches(method_name, node)
68
- return create_from_element_list method_name, matches if matches.size > 1
104
+ def create_method_for_child_or_content method_name, matches
105
+ return create_from_element_list(method_name, matches) if matches.size > 1
69
106
  return create_from_element(matches[0]) {|child| define_child method_name, child }
70
107
  end
71
108
 
72
- # Runs the xpath for the method name against the underlying XML DOM, raising
73
- # a NoMatchingXmlPart if no element or attribute matching the method name is
74
- # found in the children of the current location in the DOM.
75
- def find_matches method_name, node #=nokogiri_node
76
- matches = node.xpath(xpath_for(method_name))
77
- raise NoMatchingXmlPart.new(method_name) if matches.length < 1
78
- return matches
79
- end
80
-
81
- def xpath_for method_name
82
- method_name.variations.map {|variation| "./#{variation}" } * '|'
83
- end
84
-
85
109
  def create_from_parent_with_attribute method_name, node
86
110
  if there_are_child_nodes?(node) and node_has_attributes?(node)
87
111
  match = node.attribute(method_name.to_s)
88
- create_content_child(match) {|child| define_child method_name, child } unless match.nil?
112
+ create_value(match) {|child| define_child(method_name, child) } unless match.nil?
89
113
  end
90
114
  end
91
115
 
@@ -98,9 +122,9 @@ module Peachy
98
122
  end
99
123
 
100
124
  def create_from_element match, &block
101
- return create_proxy match, &block if there_are_child_nodes?(match)
102
- return create_proxy_with_attributes match, &block if node_has_attributes?(match)
103
- return create_content_child match, &block
125
+ return create_proxy(match, &block) if there_are_child_nodes?(match)
126
+ return create_proxy_with_attributes(match, &block) if node_has_attributes?(match)
127
+ return create_content_child(match, &block)
104
128
  end
105
129
 
106
130
  def node_has_attributes? match
@@ -114,16 +138,20 @@ module Peachy
114
138
  match.children.any? {|child| child.kind_of? Nokogiri::XML::Element }
115
139
  end
116
140
 
141
+ def create_value match, &block
142
+ create_child(match.content, &block)
143
+ end
144
+
117
145
  def create_content_child match, &block
118
- create_child match.content, &block
146
+ create_child(SimpleContent.new(match.content, match.name), &block)
119
147
  end
120
148
 
121
149
  def create_proxy match, &block
122
- create_child Proxy.new(:nokogiri => match), &block
150
+ create_child(Proxy.new(match), &block)
123
151
  end
124
152
 
125
153
  def create_proxy_with_attributes match, &block
126
- create_child ChildlessProxyWithAttributes.new(:nokogiri => match), &block
154
+ create_child ChildlessProxyWithAttributes.new(match), &block
127
155
  end
128
156
 
129
157
  def create_child child
@@ -135,16 +163,26 @@ module Peachy
135
163
  define_method(method_name) { return child }
136
164
  end
137
165
 
138
- # I don't like this hacky way of getting hold of the singleton class to define
139
- # a method, but it's better than instance_eval'ing a dynamic string to define
140
- # a method.
141
166
  def define_method method_name, &block
142
- get_my_singleton_class.class_eval { define_method method_name.to_sym, &block }
167
+ eval_on_singleton_class { define_method(method_name.to_sym, &block) }
143
168
  yield
144
169
  end
170
+
171
+ def variables_are_nil?
172
+ @xml.nil? and @nokogiri_node.nil?
173
+ end
174
+
175
+ def node_name
176
+ nokogiri_node.name
177
+ end
178
+
179
+ def nokogiri_node
180
+ raise InvalidProxyParameters.new(:xml => nil, :nokogiri => nil) if variables_are_nil?
181
+ @nokogiri_node ||= Nokogiri::XML(@xml)
182
+ end
145
183
 
146
- def get_my_singleton_class
147
- (class << self; self; end)
184
+ def clone
185
+ create_from_element(nokogiri_node)
148
186
  end
149
187
  end
150
188
  end
@@ -0,0 +1,26 @@
1
+ module Peachy
2
+ class SimpleContent
3
+ alias_method :original_method_missing, :method_missing
4
+ attr_reader :node_name
5
+ include MorphIntoArray, MyMetaClass
6
+ [:to_s, :name].each {|method_name| define_method(method_name) { value } }
7
+
8
+ def initialize value, node_name
9
+ @value = value
10
+ @node_name = node_name
11
+ end
12
+
13
+ def value
14
+ acts_as_only_child
15
+ @value
16
+ end
17
+
18
+ def method_missing method_name, *args, &block
19
+ if you_use_me_like_an_array(method_name, block_given?, *args)
20
+ new_content = SimpleContent.new(@value, @node_name)
21
+ return morph_into_array(new_content, method_name, *args, &block)
22
+ end
23
+ original_method_missing method_name, *args
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Peachy
2
- VERSION = '0.1.2'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,18 @@
1
+ describe "attributes on a parent node" do
2
+ before(:each) do
3
+ @proxy = Peachy::Proxy.new '<root><test_node name="Test"><child>Check meh.</child><test_node></root>'
4
+ end
5
+
6
+ it "should return the child when accessing it by name" do
7
+ @proxy.root.test_node.child.value.should == 'Check meh.'
8
+ end
9
+
10
+ it "should return the parent attribute by name" do
11
+ @proxy.root.test_node.name.should == 'Test'
12
+ end
13
+
14
+ it "should define the attribute name as a method" do
15
+ @proxy.root.test_node.name
16
+ @proxy.root.test_node.methods.should include 'name'
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ describe "interpreting element and attribute names that are defined in camelCase" do
2
+ before(:each) do
3
+ @proxy = Peachy::Proxy.new '<root><testNode>Check meh.</testNode><secondNode id="2" recordLabel="Wall of Sound">Check meh, too.</secondNode></root>'
4
+ end
5
+
6
+ it "should match a method to an element by camel case" do
7
+ @proxy.root.test_node.value.should == 'Check meh.'
8
+ end
9
+
10
+ it "should define a method from a camel cased element name" do
11
+ @proxy.root.test_node.value.should == 'Check meh.'
12
+ @proxy.root.methods.should include 'test_node'
13
+ end
14
+
15
+ it "should match a method to an attribute by camel case" do
16
+ @proxy.root.second_node.record_label.should == "Wall of Sound"
17
+ end
18
+
19
+ it "should define a method from camel cased attribute name" do
20
+ @proxy.root.second_node.record_label
21
+ @proxy.root.second_node.methods.should include 'record_label'
22
+ end
23
+ end
24
+
@@ -0,0 +1,48 @@
1
+ describe "a childless element with attributes referenced as the first part of a collection" do
2
+ before(:each) do
3
+ xml = <<XML
4
+ <xml>
5
+ <list>
6
+ <item id="1">Hello</item>
7
+ </list>
8
+ </xml>
9
+ XML
10
+ @proxy = Peachy::Proxy.new xml
11
+ end
12
+
13
+ it "should infer that the element matches the first index of an array" do
14
+ @proxy.xml.list.item[0].value.should == 'Hello'
15
+ end
16
+
17
+ it "should make attributes available on the single child" do
18
+ @proxy.xml.list.item[0].id.should == '1'
19
+ end
20
+
21
+ it "should quack like an array" do
22
+ @proxy.xml.list.item[0]
23
+
24
+ element_as_array = @proxy.xml.list.item
25
+ element_as_array.any?.should be_true
26
+ element_as_array.one?.should be_true
27
+ element_as_array.empty?.should be_false
28
+ element_as_array.size.should == 1
29
+ element_as_array[0].value.should == 'Hello'
30
+ end
31
+
32
+ it "should raise an error if the element value has already been accessed as an only child" do
33
+ @proxy.xml.list.item.value
34
+ lambda { @proxy.xml.list.item[0] }.should raise_error AlreadyAnOnlyChild, <<MSG
35
+ The 'item' node has already been accessed as a single child, but you are now trying to use it as a collection.
36
+ Do not try to access Peachy::Proxies in a mixed manner in your implementation.
37
+ MSG
38
+ end
39
+
40
+ it "should raise an error if the element's attributes have already been accessed as an only child" do
41
+ @proxy.xml.list.item.id
42
+ lambda { @proxy.xml.list.item[0] }.should raise_error AlreadyAnOnlyChild, <<MSG
43
+ The 'item' node has already been accessed as a single child, but you are now trying to use it as a collection.
44
+ Do not try to access Peachy::Proxies in a mixed manner in your implementation.
45
+ MSG
46
+ end
47
+ end
48
+
@@ -0,0 +1,36 @@
1
+ describe "collections with children as arrays" do
2
+ before(:each) do
3
+ xml = <<STR
4
+ <xml>
5
+ <list>
6
+ <item>
7
+ <child>one</child>
8
+ </item>
9
+ <item>
10
+ <child>two</child>
11
+ </item>
12
+ <item>
13
+ <child>three</child>
14
+ </item>
15
+ </list>
16
+ </xml>
17
+ STR
18
+ @peachy_proxy = Peachy::Proxy.new xml
19
+ end
20
+
21
+ it "should create an array with the correct size" do
22
+ @peachy_proxy.xml.list.item.size.should == 3
23
+ end
24
+
25
+ it "should define a method for the name of the list items" do
26
+ @peachy_proxy.xml.list.item
27
+ @peachy_proxy.xml.list.methods.should include 'item'
28
+ end
29
+
30
+ it "should create the children as expected" do
31
+ @peachy_proxy.xml.list.item[0].child.value.should == "one"
32
+ @peachy_proxy.xml.list.item[1].child.value.should == "two"
33
+ @peachy_proxy.xml.list.item[2].child.value.should == "three"
34
+ end
35
+ end
36
+
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe "an element referenced as the first part of a collection" do
4
+ before(:each) do
5
+ xml = <<XML
6
+ <xml>
7
+ <list>
8
+ <item id="1">
9
+ <child>one</child>
10
+ </item>
11
+ </list>
12
+ </xml>
13
+ XML
14
+ @proxy = Peachy::Proxy.new xml
15
+ end
16
+
17
+ it "should infer that the element matches the first index of an array" do
18
+ @proxy.xml.list.item[0].child.value.should == 'one'
19
+ end
20
+
21
+ it "should make attributes available on the single child" do
22
+ @proxy.xml.list.item[0].id.should == '1'
23
+ end
24
+
25
+ it "should quack like an Array after an array element is referenced" do
26
+ @proxy.xml.list.item[0]
27
+
28
+ element_as_array = @proxy.xml.list.item
29
+ element_as_array.any?.should be_true
30
+ element_as_array.one?.should be_true
31
+ element_as_array.empty?.should be_false
32
+ element_as_array.size.should == 1
33
+ (element_as_array.map {|item| item.child.value }).should == ['one']
34
+ element_as_array[0].child.value.should == 'one'
35
+ end
36
+
37
+ it "should quack like an array when an Array method is sent to node with a block" do
38
+ @values = []
39
+ @proxy.xml.list.item.each {|node| @values << node.id}
40
+ @values.should == ['1']
41
+ end
42
+
43
+ it "should quack like an array when an Array method is sent to node with no arguments" do
44
+ @values = []
45
+ @proxy.xml.list.item.size.should == 1
46
+ end
47
+
48
+ it "should raise an error if the element has already been accessed as a single child" do
49
+ @proxy.xml.list.item.child
50
+ lambda { @proxy.xml.list.item[0] }.should raise_error AlreadyAnOnlyChild, <<MSG
51
+ The 'item' node has already been accessed as a single child, but you are now trying to use it as a collection.
52
+ Do not try to access Peachy::Proxies in a mixed manner in your implementation.
53
+ MSG
54
+ end
55
+ end
56
+
@@ -0,0 +1,15 @@
1
+ describe "interpretting element names that contain hyphens" do
2
+ before(:each) do
3
+ @proxy = Peachy::Proxy.new '<test-node>Check meh.</test-node>'
4
+ end
5
+
6
+ it "should try to match a method to a node with hyphens" do
7
+ @proxy.test_node.value.should == 'Check meh.'
8
+ end
9
+
10
+ it "should define a method for the element name" do
11
+ @proxy.test_node
12
+ @proxy.methods.should include 'test_node'
13
+ end
14
+ end
15
+
@@ -0,0 +1,23 @@
1
+ describe "inferring a method from an attribute" do
2
+ before(:each) do
3
+ @proxy = Peachy::Proxy.new '<test-node id="1" another="yellow">Check meh.</test-node>'
4
+ end
5
+
6
+ it "should be able to access node contents by 'value'" do
7
+ @proxy.test_node.value.should == 'Check meh.'
8
+ end
9
+
10
+ it "should be able to generate a method representing an attribute" do
11
+ @proxy.test_node.id.should == '1'
12
+ end
13
+
14
+ it "should define a method for the attribute name" do
15
+ @proxy.test_node.another
16
+ @proxy.test_node.methods.should include 'another'
17
+ end
18
+
19
+ it "should raise an error if method name doesn't match an attribute name" do
20
+ lambda { @proxy.test_node.missing }.should raise_error NoMatchingXmlPart, "missing is not contained as a child of the node test-node."
21
+ end
22
+ end
23
+
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ describe "inferring a method from an element name" do
3
+ before(:each) do
4
+ xml = '<testnode>Check meh.</testnode>'
5
+ @proxy = Peachy::Proxy.new xml
6
+ @another_proxy = Peachy::Proxy.new xml
7
+ end
8
+
9
+ it "should defer method_missing to the base class implementation if arguments are passed with the missing method" do
10
+ lambda { @proxy.my_call "argument" }.should raise_error NoMethodError
11
+ end
12
+
13
+ it "should defer method_missing to the base class implementation if block is given with method_missing" do
14
+ lambda { @proxy.my_call() { puts "Blockhead." } }.should raise_error NoMethodError
15
+ end
16
+
17
+ it "should not expose the original method_missing alias publically" do
18
+ @proxy.methods.include?('original_method_missing').should be_false
19
+ end
20
+
21
+ it 'should add a reader for the method to the underlying class if the method does map to underlying XML node' do
22
+ @proxy.testnode
23
+ @proxy.methods.include?('testnode').should be_true
24
+ end
25
+
26
+ it 'should define the method on an instance, not on the class' do
27
+ @proxy.testnode
28
+ @proxy.methods.should include 'testnode'
29
+ @another_proxy.methods.should_not include 'testnode'
30
+ end
31
+
32
+ it "should return the node contents when the node isn't defined as a method and the contents of the node is at the lowest point of the tree" do
33
+ @proxy.testnode.value.should == 'Check meh.'
34
+ end
35
+
36
+ it "should define a method that returns the node contents is at the lowest point of the tree" do
37
+ contents_when_not_defined = @proxy.testnode.value
38
+ contents_after_defined = @proxy.testnode.value
39
+ contents_after_defined.should == 'Check meh.'
40
+ contents_after_defined.should == contents_when_not_defined
41
+ end
42
+
43
+ it "should only contain the expected public and protected methods" do
44
+ @proxy.methods.should include('inspect')
45
+ @proxy.methods.should include('methods')
46
+ @proxy.methods.should include('nil?')
47
+ @proxy.methods.should include('respond_to?')
48
+ @proxy.methods.should_not include('id')
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+ describe "how to use Peachy::MethodName" do
2
+ before(:each) do
3
+ @method_name = Peachy::MethodName.new 'method_name'
4
+ end
5
+
6
+ it "should return the original string as to_s" do
7
+ @method_name.to_s.should == "method_name"
8
+ end
9
+
10
+ it "should return the expected symbol as to_sym" do
11
+ @method_name.to_sym.should == :method_name
12
+ end
13
+
14
+ it "should return the expected number of variations in format" do
15
+ @method_name.variations.size.should == 4
16
+ end
17
+
18
+ it "should return the expected variation formats" do
19
+ variations = @method_name.variations
20
+
21
+ variations.should include 'method_name'
22
+ variations.should include 'methodName'
23
+ variations.should include 'method-name'
24
+ variations.should include 'MethodName'
25
+ end
26
+
27
+ it "should be possible to create a MethodName from a symbol" do
28
+ @method_name = Peachy::MethodName.new :this_method
29
+ variations = @method_name.variations
30
+
31
+ @method_name.to_s.should == 'this_method'
32
+ @method_name.to_sym.should == :this_method
33
+
34
+ variations.should include 'this_method'
35
+ variations.should include 'thisMethod'
36
+ variations.should include 'this-method'
37
+ variations.should include 'ThisMethod'
38
+ end
39
+
40
+ it "should not include duplicates in variations" do
41
+ @method_name = Peachy::MethodName.new :method
42
+ variations = @method_name.variations
43
+
44
+ variations.size.should == 2
45
+ variations.should include 'method'
46
+ variations.should include 'Method'
47
+ end
48
+ end
49
+
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require 'ruby-debug'
3
+
4
+ describe "nested elements should be handled corectly by Peachy" do
5
+ before(:each) do
6
+ @proxy = Peachy::Proxy.new '<root><first><second>Check meh.</second></first></root>'
7
+ end
8
+
9
+ it "should be able to dot through the DOM hierachy" do
10
+ @proxy.root.first.second.value.should == 'Check meh.'
11
+ end
12
+
13
+ it "should define methods for the ancestors" do
14
+ @proxy.root.first.second
15
+ @proxy.root.methods.should include 'first'
16
+ @proxy.root.first.methods.should include 'second'
17
+ end
18
+ end
19
+
@@ -0,0 +1,22 @@
1
+ describe "nodes with multiple children should be handled correctly" do
2
+ before(:each) do
3
+ @peachy_proxy = Peachy::Proxy.new '<testnode><child>Check meh.</child><second_child><ancestor>Check meh two times.</ancestor></second_child></testnode>'
4
+ @node_to_test = @peachy_proxy.testnode
5
+ end
6
+
7
+ it "should recurse the Proxy to children so that a child will have the correct value" do
8
+ @node_to_test.child.value.should == "Check meh."
9
+ end
10
+
11
+ it "should define a method with the child name on the proxy" do
12
+ @node_to_test.child
13
+ @node_to_test.second_child
14
+ @node_to_test.methods.should include 'child'
15
+ @node_to_test.methods.should include 'second_child'
16
+ end
17
+
18
+ it "should recurse the Proxy to ancestors so that a child will have the correct value" do
19
+ @node_to_test.second_child.ancestor.value.should == "Check meh two times."
20
+ end
21
+ end
22
+
@@ -0,0 +1,24 @@
1
+ describe "interpreting element and attribute names that are defined in PascalCase" do
2
+ before(:each) do
3
+ @proxy = Peachy::Proxy.new '<Root><TestNode>Check meh.</TestNode><SecondNode Id="2" RecordLabel="Wall of Sound">Check meh, too.</SecondNode></Root>'
4
+ end
5
+
6
+ it "should match a method to an element by pascal case" do
7
+ @proxy.root.test_node.value.should == 'Check meh.'
8
+ end
9
+
10
+ it "should define a method from a pascal cased element name" do
11
+ @proxy.root.test_node.value.should == 'Check meh.'
12
+ @proxy.root.methods.should include 'test_node'
13
+ end
14
+
15
+ it "should match a method to an attribute by pascal case" do
16
+ @proxy.root.second_node.record_label.should == "Wall of Sound"
17
+ end
18
+
19
+ it "should define a method from pascal cased attribute name" do
20
+ @proxy.root.second_node.record_label
21
+ @proxy.root.second_node.methods.should include 'record_label'
22
+ end
23
+ end
24
+
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ describe "Peachy::SimpleContent wrapper for the contents of a simple XML element" do
3
+ before(:each) do
4
+ @node_name = 'parent_node'
5
+ @content = Peachy::SimpleContent.new 'the value', @node_name
6
+ end
7
+
8
+ it "should return the expected content" do
9
+ @content.value.should == 'the value'
10
+ end
11
+
12
+ it "should to_s in the expected way" do
13
+ @content.to_s.should == 'the value'
14
+ end
15
+
16
+ it "should return the value from the index of 0" do
17
+ item = @content[0]
18
+ item.value.should == 'the value'
19
+ end
20
+
21
+ it "should make the name of the parent node available" do
22
+ @content.node_name.should == @node_name
23
+ end
24
+
25
+ it "should raise an error if the SimpleContent is treated as an Array after being treated as SimpleContent" do
26
+ @content.value
27
+ lambda { @content[0].value }.should raise_error AlreadyAnOnlyChild, <<MESSAGE
28
+ The 'parent_node' node has already been accessed as a single child, but you are now trying to use it as a collection.
29
+ Do not try to access Peachy::Proxies in a mixed manner in your implementation.
30
+ MESSAGE
31
+ end
32
+
33
+ it "should behave as other objects when a method does not exist" do
34
+ lambda { @content.how_random }.should raise_error NoMethodError
35
+ end
36
+
37
+ it "should behave as other objects when a method with a parameter does not exist" do
38
+ lambda { @content.how_random("Hello") }.should raise_error NoMethodError
39
+ end
40
+
41
+ it "should behave as other objects when a method with a block does not exist" do
42
+ lambda { @content.how_random() { puts "Boo!" } }.should raise_error NoMethodError
43
+ end
44
+ end
45
+
@@ -0,0 +1,36 @@
1
+ describe "a simple element referenced as the first part of a collection" do
2
+ before(:each) do
3
+ xml = <<XML
4
+ <xml>
5
+ <list>
6
+ <item>Hello</item>
7
+ </list>
8
+ </xml>
9
+ XML
10
+ @proxy = Peachy::Proxy.new xml
11
+ end
12
+
13
+ it "should infer that the element matches the first index of an array" do
14
+ @proxy.xml.list.item[0].value.should == 'Hello'
15
+ end
16
+
17
+ it "should quack like an array" do
18
+ @proxy.xml.list.item[0]
19
+
20
+ element_as_array = @proxy.xml.list.item
21
+ element_as_array.any?.should be_true
22
+ element_as_array.one?.should be_true
23
+ element_as_array.empty?.should be_false
24
+ element_as_array.size.should == 1
25
+ (element_as_array.map {|item| item.value }).should == ['Hello']
26
+ element_as_array[0].value.should == 'Hello'
27
+ end
28
+
29
+ it "should raise an error if the element value has already been accessed as an only child" do
30
+ @proxy.xml.list.item.value
31
+ lambda { @proxy.xml.list.item[0] }.should raise_error AlreadyAnOnlyChild, <<MSG
32
+ The 'item' node has already been accessed as a single child, but you are now trying to use it as a collection.
33
+ Do not try to access Peachy::Proxies in a mixed manner in your implementation.
34
+ MSG
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ describe "a simple XML collection should be interpretted as an array" do
2
+ before(:each) do
3
+ xml = "<xml><stuff><item>first</item><item>second</item><item>third</item></stuff></xml>"
4
+ @proxy = Peachy::Proxy.new xml
5
+ end
6
+
7
+ it "should set the correct sized array for a collection" do
8
+ @proxy.xml.stuff.item.size.should == 3
9
+ end
10
+
11
+ it "should define a method for the item list name" do
12
+ @proxy.xml.stuff.item
13
+ @proxy.xml.stuff.methods.should include 'item'
14
+ end
15
+
16
+ it "should set each array item to the content for the list item" do
17
+ @proxy.xml.stuff.item[0].value.should == 'first'
18
+ @proxy.xml.stuff.item[1].value.should == 'second'
19
+ @proxy.xml.stuff.item[2].value.should == 'third'
20
+ end
21
+ end
22
+
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/peachy'))
2
+
3
+ Spec::Runner.configure do |config|
4
+ Peachy.whine
5
+ end
@@ -0,0 +1,26 @@
1
+ describe "using Peachy::Proxy incorrectly" do
2
+ before(:each) do
3
+ @proxy = Peachy::Proxy.new '<testnode>Check meh.</testnode>'
4
+ end
5
+
6
+ it 'should raise a well written error if the method does not map to anything in the underlying XML node' do
7
+ lambda { @proxy.missing }.should raise_error(NoMatchingXmlPart, 'missing is not contained as a child of the node document.')
8
+ end
9
+
10
+ it "should raise an error if the method name is not in Ruby convention" do
11
+ lambda { @proxy.testNode }.should raise_error MethodNotInRubyConvention, <<EOF
12
+ You've tried to infer testNode using Peachy, but Peachy doesn't currently like defining methods that break Ruby convention.
13
+ Please use methods matching ^[a-z]+(?:_[a-z]+)?{0,}$ and Peachy will try to do the rest with your XML.*/
14
+ EOF
15
+ end
16
+
17
+ it "should throw an InvalidProxyParameters error if an incorrect type of parameter was passed as the initializer argument" do
18
+ invalid_proxy = Peachy::Proxy.new Hash.new
19
+ lambda { invalid_proxy.boom }.should raise_error InvalidProxyParameters, <<EOF
20
+ The parameters that you passed to the Proxy were invalid.
21
+ :nokogiri = nil
22
+ :xml = nil
23
+ EOF
24
+ end
25
+ end
26
+
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
7
  - 2
9
- version: 0.1.2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - NJ Pearman
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-05 00:00:00 +01:00
17
+ date: 2010-05-12 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -32,8 +32,8 @@ dependencies:
32
32
  type: :runtime
33
33
  version_requirements: *id001
34
34
  description: |
35
- Peachy is an XML slurper that sits on to of existing XML parsers. It dynamically
36
- creates object-style hierachies for simple ingration of XML data sources.
35
+ Peachy is an XML slurper that sits on top of existing XML parsers. It dynamically
36
+ creates object-style hierachies for simple integration of XML data sources.
37
37
 
38
38
  email:
39
39
  - n.pearman@gmail.com
@@ -48,14 +48,37 @@ files:
48
48
  - lib/peachy/childless_proxy_with_attributes.rb
49
49
  - lib/peachy/method_name.rb
50
50
  - lib/peachy/method_mask.rb
51
+ - lib/peachy/simple_content.rb
51
52
  - lib/peachy/version.rb
52
53
  - lib/peachy/proxy.rb
54
+ - lib/peachy/morph_into_array.rb
55
+ - lib/peachy/my_meta_class.rb
53
56
  - lib/peachy/string_styler.rb
57
+ - lib/peachy/node_child_matcher.rb
54
58
  - lib/no_matching_xml_part.rb
55
59
  - lib/invalid_proxy_parameters.rb
56
60
  - lib/method_not_in_ruby_convention.rb
61
+ - lib/already_an_only_child.rb
57
62
  - lib/peachy.rb
63
+ - spec/hyphen_separated_names_spec.rb
64
+ - spec/elements_referenced_as_collections_spec.rb
65
+ - spec/simple_content_wrapper_for_Peachy_spec.rb
66
+ - spec/collections_with_children_as_arrays_spec.rb
67
+ - spec/method_name_spec.rb
68
+ - spec/childless_elements_referenced_as_collections_spec.rb
69
+ - spec/simple_xml_collections_as_arrays_spec.rb
70
+ - spec/simple_element_referenced_as_collections_spec.rb
71
+ - spec/using_peachy_proxy_incorrectly_spec.rb
72
+ - spec/inferring_a_method_from_an_attribute_spec.rb
73
+ - spec/camel_case_names_spec.rb
74
+ - spec/attributes_on_a_parent_node_spec.rb
75
+ - spec/pascal_case_names_spec.rb
76
+ - spec/inferring_a_method_from_element_name_spec.rb
77
+ - spec/spec_helper.rb
78
+ - spec/nested_elements_spec.rb
79
+ - spec/nodes_with_children_spec.rb
58
80
  - README.rdoc
81
+ - History.txt
59
82
  has_rdoc: true
60
83
  homepage: http://github.com/njpearman/peachy
61
84
  licenses: []