peachy 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +54 -0
- data/lib/invalid_proxy_parameters.rb +10 -0
- data/lib/method_not_in_ruby_convention.rb +12 -0
- data/lib/no_matching_xml_part.rb +5 -0
- data/lib/peachy/childless_proxy_with_attributes.rb +20 -0
- data/lib/peachy/convention_checks.rb +11 -0
- data/lib/peachy/method_mask.rb +11 -0
- data/lib/peachy/method_name.rb +31 -0
- data/lib/peachy/proxy.rb +150 -0
- data/lib/peachy/string_styler.rb +28 -0
- data/lib/peachy/version.rb +3 -0
- data/lib/peachy.rb +12 -0
- metadata +91 -0
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,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,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
|
data/lib/peachy/proxy.rb
ADDED
@@ -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
|
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
|
+
|