peachy 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,54 @@
1
+ = Peachy
2
+
3
+ Peachy dynamically slurps XML from an underlying XML DOM, creating ruby methods
4
+ on the fly to match the elements and attributes of the XML.
5
+
6
+ Source available at http://github.com/njpearman/Peachy
7
+
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.
12
+
13
+ == Usage
14
+ The Peachy::Proxy is the key class in Peachy. Create a new instance of a
15
+ Peachy::Proxy passing in either a raw XML string, or a Nokogiri::XML instance
16
+
17
+ proxy = Peachy::Proxy.new :xml => '<xml><node>Peachy</node></xml>'
18
+
19
+ or
20
+
21
+ proxy = Peachy::Proxy.new :nokogiri => Nokogiri::XML('<xml><node>Peachy</node></xml>')
22
+
23
+ Once you have a Proxy, it's straightforward to drill down through the XML by
24
+ node name:
25
+
26
+ puts 'Contents: ' + proxy.xml.node
27
+ -> Contents: Peachy
28
+
29
+ Peachy expects method names to be called in the Ruby convention of lowercase with
30
+ underscores. It will do it's best to match method names to elements and attributes
31
+ following different conventions (currently, this is camelCaseNames, PascalCaseNames
32
+ or hyphen-separated-names)
33
+
34
+ More detailed usage examples can be found in the .rb files in the /test directory.
35
+
36
+ === XML parsing
37
+
38
+ Currently, Peachy is hard wired to use Nokogiri for XML parsing, but this will
39
+ be abstracted out at some point.
40
+
41
+ === Elements and Attributes
42
+
43
+ Currently, elements and attributes are accessed in exactly the same way; that is,
44
+ 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.
49
+
50
+ === No method name match
51
+
52
+ Peachy is currently short-tempered, in that if no element or attribute match is
53
+ found when drilling down through proxies, a NoMatchingXmlPart error will be
54
+ raised.
@@ -0,0 +1,10 @@
1
+ class InvalidProxyParameters < Exception
2
+ def initialize parameters_hash={}
3
+ parameters_string = []
4
+ parameters_hash.each {|pair| parameters_string << ":#{pair.first} = #{pair.last.nil?? 'nil' : pair.last}" }
5
+ super <<MESSAGE
6
+ The parameters that you passed to the Proxy were invalid.
7
+ #{parameters_string.sort * "\n"}
8
+ MESSAGE
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ class MethodNotInRubyConvention < Exception
2
+ def initialize method_name
3
+ super(MessageTemplate.gsub /method_name/, method_name.to_s)
4
+ end
5
+
6
+ private
7
+
8
+ MessageTemplate = <<EOF
9
+ You've tried to infer method_name using Peachy, but Peachy doesn't currently like defining methods that break Ruby convention.
10
+ Please use methods matching ^[a-z]+(?:_[a-z]+)?{0,}$ and Peachy will try to do the rest with your XML.*/
11
+ EOF
12
+ end
@@ -0,0 +1,5 @@
1
+ class NoMatchingXmlPart < Exception
2
+ def initialize method_name
3
+ super "#{method_name} is not contained as a child of this node."
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ module Peachy
2
+ class ChildlessProxyWithAttributes < Proxy
3
+ def value
4
+ @nokogiri_node.content
5
+ end
6
+
7
+ private
8
+ def generate_method_for_xml method_name
9
+ check_for_convention(method_name)
10
+ 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 }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ module Peachy
2
+ module ConventionChecks
3
+ def check_for_convention method_name
4
+ raise MethodNotInRubyConvention.new(method_name) unless matches_convention(method_name)
5
+ end
6
+
7
+ def matches_convention method_name
8
+ method_name.to_s =~ /^[a-z]+(?:_[a-z]+){0,}$/
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Peachy
2
+ module MethodMask
3
+ def hide_public_methods exceptions
4
+ methods_to_hide = public_instance_methods.clone
5
+ exceptions.each do |to_stay_public|
6
+ methods_to_hide.delete(to_stay_public)
7
+ end
8
+ private *methods_to_hide
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Peachy
2
+ class MethodName
3
+ include StringStyler
4
+
5
+ def initialize method_name
6
+ @method_name = method_name.to_s
7
+ end
8
+
9
+ # Returns an array of distinct valid variations in the method name.
10
+ # The valid varations are the underlying method name, plus all variations
11
+ # provided by methods defined in the StringStyler module.
12
+ def variations
13
+ (variation_methods.inject([@method_name]) do |array, method|
14
+ array << send(method)
15
+ end).uniq
16
+ end
17
+
18
+ def to_s
19
+ return @method_name
20
+ end
21
+
22
+ def to_sym
23
+ return @method_name.to_sym
24
+ end
25
+
26
+ private
27
+ def variation_methods
28
+ Peachy::StringStyler.private_instance_methods
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,150 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+
4
+ module Peachy
5
+ class Proxy
6
+ alias_method :original_method_missing, :method_missing
7
+ extend MethodMask
8
+ include ConventionChecks
9
+
10
+ # This hides all public methods on the class except for 'methods' and
11
+ # 'respond_to?' and 'inspect', which I've found are too useful to hide for
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.
22
+ def initialize arguments
23
+ @nokogiri_node = arguments[:nokogiri]
24
+ @xml = arguments[:xml]
25
+ end
26
+
27
+ # Overloaded so that calls to methods representative of an XML element or
28
+ # attribute can be generated dynamically.
29
+ #
30
+ # For example, if a proxy is referenced in code as proxy.first_node, and
31
+ # first_node does match a child of the DOM encapsulated by proxy, then
32
+ # first_node will be generated on proxy.
33
+ #
34
+ # However, if first_node does not represent a child in the underlying DOM
35
+ # then proxy will raise a NoMatchingXmlPart error.
36
+ #
37
+ # The method name used to access an XML node has to follow the standard Ruby
38
+ # convention for method names, i.e. ^[a-z]+(?:_[a-z]+)?{0,}$. If an attempt
39
+ # is made to call a method on a Peachy::Proxy that breaks this convention,
40
+ # Peachy will simply raise a MethodNotInRubyConvention error.
41
+ #
42
+ # Any calls to undefined methods that include arguments or a block will be
43
+ # 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)
47
+ end
48
+
49
+ private
50
+ def generate_method_for_xml method_name
51
+ check_for_convention(method_name)
52
+ attribute_content = create_from_parent_with_attribute method_name, nokogiri_node
53
+ 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?
64
+ end
65
+
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
69
+ return create_from_element(matches[0]) {|child| define_child method_name, child }
70
+ end
71
+
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
+ def create_from_parent_with_attribute method_name, node
86
+ if there_are_child_nodes?(node) and node_has_attributes?(node)
87
+ match = node.attribute(method_name.to_s)
88
+ create_content_child(match) {|child| define_child method_name, child } unless match.nil?
89
+ end
90
+ end
91
+
92
+ def create_from_element_list method_name, matches
93
+ define_method(method_name) { return matches_to_array matches }
94
+ end
95
+
96
+ def matches_to_array matches
97
+ matches.inject([]) {|array, child| array << create_from_element(child) }
98
+ end
99
+
100
+ 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
104
+ end
105
+
106
+ def node_has_attributes? match
107
+ match.attribute_nodes.size > 0
108
+ end
109
+
110
+ # Determines whether the given element contains any child elements or not.
111
+ # The choice of implementation is based on performance tests between using
112
+ # XPath and a Ruby iterator.
113
+ def there_are_child_nodes? match
114
+ match.children.any? {|child| child.kind_of? Nokogiri::XML::Element }
115
+ end
116
+
117
+ def create_content_child match, &block
118
+ create_child match.content, &block
119
+ end
120
+
121
+ def create_proxy match, &block
122
+ create_child Proxy.new(:nokogiri => match), &block
123
+ end
124
+
125
+ def create_proxy_with_attributes match, &block
126
+ create_child ChildlessProxyWithAttributes.new(:nokogiri => match), &block
127
+ end
128
+
129
+ def create_child child
130
+ yield child if block_given?
131
+ return child
132
+ end
133
+
134
+ def define_child method_name, child
135
+ define_method(method_name) { return child }
136
+ end
137
+
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
+ def define_method method_name, &block
142
+ get_my_singleton_class.class_eval { define_method method_name.to_sym, &block }
143
+ yield
144
+ end
145
+
146
+ def get_my_singleton_class
147
+ (class << self; self; end)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,28 @@
1
+ module Peachy
2
+ # Included in Peachy::MethodName. All methods in this module expect an instance
3
+ # variable named @method_name to be defined.
4
+ module StringStyler
5
+ private
6
+ module Stripper
7
+ # This is a bit rubbish. Need to include the method somewhere other than
8
+ # the private instance block in StringStyler.
9
+ def strip_underscores_and_upcase string
10
+ string.gsub(/_([a-z])/){|s| s.upcase}.delete('_')
11
+ end
12
+ end
13
+
14
+ include Stripper
15
+
16
+ def as_camel_case
17
+ strip_underscores_and_upcase(@method_name)
18
+ end
19
+
20
+ def as_pascal_case
21
+ strip_underscores_and_upcase(@method_name.capitalize)
22
+ end
23
+
24
+ def as_hyphen_separated
25
+ @method_name.gsub(/_/, '-')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Peachy
2
+ VERSION = '0.1.2'
3
+ end
data/lib/peachy.rb ADDED
@@ -0,0 +1,12 @@
1
+ require File.join(File.dirname(__FILE__), 'invalid_proxy_parameters')
2
+ require File.join(File.dirname(__FILE__), 'method_not_in_ruby_convention')
3
+ require File.join(File.dirname(__FILE__), 'no_matching_xml_part')
4
+ require File.join(File.dirname(__FILE__), 'peachy/convention_checks')
5
+ require File.join(File.dirname(__FILE__), 'peachy/string_styler')
6
+ require File.join(File.dirname(__FILE__), 'peachy/method_name')
7
+ require File.join(File.dirname(__FILE__), 'peachy/method_mask')
8
+ require File.join(File.dirname(__FILE__), 'peachy/proxy')
9
+ require File.join(File.dirname(__FILE__), 'peachy/childless_proxy_with_attributes')
10
+
11
+ module Peachy
12
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: peachy
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 2
9
+ version: 0.1.2
10
+ platform: ruby
11
+ authors:
12
+ - NJ Pearman
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-05 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: nokogiri
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 3
30
+ - 3
31
+ version: 1.3.3
32
+ type: :runtime
33
+ version_requirements: *id001
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.
37
+
38
+ email:
39
+ - n.pearman@gmail.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - README.rdoc
46
+ files:
47
+ - lib/peachy/convention_checks.rb
48
+ - lib/peachy/childless_proxy_with_attributes.rb
49
+ - lib/peachy/method_name.rb
50
+ - lib/peachy/method_mask.rb
51
+ - lib/peachy/version.rb
52
+ - lib/peachy/proxy.rb
53
+ - lib/peachy/string_styler.rb
54
+ - lib/no_matching_xml_part.rb
55
+ - lib/invalid_proxy_parameters.rb
56
+ - lib/method_not_in_ruby_convention.rb
57
+ - lib/peachy.rb
58
+ - README.rdoc
59
+ has_rdoc: true
60
+ homepage: http://github.com/njpearman/peachy
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --main
66
+ - README.rdoc
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.6
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Peachy gives a very simple object-style interface on top of an XML DOM.
90
+ test_files: []
91
+