reagent-graft 0.1.0
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.
- data/README.rdoc +44 -0
- data/Rakefile +40 -0
- data/lib/graft/attribute.rb +44 -0
- data/lib/graft/model.rb +47 -0
- data/lib/graft/version.rb +13 -0
- data/lib/graft.rb +7 -0
- data/test/test_helper.rb +7 -0
- data/test/unit/attribute_test.rb +133 -0
- data/test/unit/model_test.rb +88 -0
- data/test/unit/source_test.rb +13 -0
- metadata +84 -0
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= Graft
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
Graft provides an easy way to map XML data onto your Ruby classes.
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
Stable:
|
10
|
+
|
11
|
+
$ sudo gem install graft
|
12
|
+
|
13
|
+
Bleeding edge:
|
14
|
+
|
15
|
+
$ sudo gem install reagent-graft --source=http://gems.github.com
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
TBD
|
20
|
+
|
21
|
+
== License
|
22
|
+
|
23
|
+
Copyright (c) 2009 Patrick Reagan (reaganpr@gmail.com)
|
24
|
+
|
25
|
+
Permission is hereby granted, free of charge, to any person
|
26
|
+
obtaining a copy of this software and associated documentation
|
27
|
+
files (the "Software"), to deal in the Software without
|
28
|
+
restriction, including without limitation the rights to use,
|
29
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
30
|
+
copies of the Software, and to permit persons to whom the
|
31
|
+
Software is furnished to do so, subject to the following
|
32
|
+
conditions:
|
33
|
+
|
34
|
+
The above copyright notice and this permission notice shall be
|
35
|
+
included in all copies or substantial portions of the Software.
|
36
|
+
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
38
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
39
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
40
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
41
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
42
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
43
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
44
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
require 'lib/graft/version'
|
6
|
+
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
spec = Gem::Specification.new do |s|
|
10
|
+
s.name = 'graft'
|
11
|
+
s.version = Graft::Version.to_s
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.extra_rdoc_files = %w(README.rdoc)
|
14
|
+
s.rdoc_options = %w(--main README.rdoc)
|
15
|
+
s.summary = "Simple XML to attribute mapping for your Ruby classes"
|
16
|
+
s.author = 'Patrick Reagan'
|
17
|
+
s.email = 'reaganpr@gmail.com'
|
18
|
+
s.homepage = 'http://sneaq.net/'
|
19
|
+
s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib,test}/**/*")
|
20
|
+
|
21
|
+
s.add_dependency('hpricot', '~> 0.6.0')
|
22
|
+
s.add_dependency('activesupport', '~> 2.0')
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
26
|
+
pkg.gem_spec = spec
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::TestTask.new do |t|
|
30
|
+
t.libs << 'test'
|
31
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
32
|
+
t.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Generate the gemspec to serve this Gem from Github'
|
36
|
+
task :github do
|
37
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
38
|
+
File.open(file, 'w') {|f| f << spec.to_ruby }
|
39
|
+
puts "Created gemspec: #{file}"
|
40
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Graft
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# TODO: Refactor the location / attribute logic into a Source class
|
5
|
+
|
6
|
+
attr_reader :name, :sources
|
7
|
+
|
8
|
+
def initialize(name, sources = nil)
|
9
|
+
@name = name.to_sym
|
10
|
+
|
11
|
+
@sources = Array(sources)
|
12
|
+
@sources << @name.to_s if @sources.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def split(source)
|
16
|
+
location, attribute = source.split('@')
|
17
|
+
location = self.name.to_s if location.blank?
|
18
|
+
|
19
|
+
[location, attribute]
|
20
|
+
end
|
21
|
+
|
22
|
+
def node_for(document, source)
|
23
|
+
document.at(location(source)) || document.search("//[@#{attribute(source)}]").first
|
24
|
+
end
|
25
|
+
|
26
|
+
def attribute(source)
|
27
|
+
location, attribute = source.split('@')
|
28
|
+
attribute || location
|
29
|
+
end
|
30
|
+
|
31
|
+
def location(source)
|
32
|
+
split(source).first
|
33
|
+
end
|
34
|
+
|
35
|
+
def value_from(document)
|
36
|
+
values = sources.map do |source|
|
37
|
+
node = node_for(document, source)
|
38
|
+
(node.attributes[attribute(source)] || node.inner_text) unless node.nil?
|
39
|
+
end
|
40
|
+
values.compact.first
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/lib/graft/model.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Graft
|
2
|
+
module Model
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def attributes
|
7
|
+
@attributes ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def attribute(name, options = {})
|
11
|
+
self.attributes << Attribute.new(name, options[:from])
|
12
|
+
class_eval "attr_accessor :#{name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
|
19
|
+
def initialize(document = nil)
|
20
|
+
self.document = document
|
21
|
+
self.populate_from(self.document) unless self.document.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
def document=(document)
|
25
|
+
@document = document.is_a?(String) ? Hpricot.XML(document) : document
|
26
|
+
end
|
27
|
+
|
28
|
+
def document
|
29
|
+
@document
|
30
|
+
end
|
31
|
+
|
32
|
+
def populate_from(document)
|
33
|
+
self.class.attributes.each do |attribute|
|
34
|
+
value = attribute.value_from(document)
|
35
|
+
self.send("#{attribute.name}=".to_sym, value) unless value.nil?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.included(other)
|
42
|
+
other.send(:extend, Graft::Model::ClassMethods)
|
43
|
+
other.send(:include, Graft::Model::InstanceMethods)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/graft.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
module Graft
|
4
|
+
class AttributeTest < Test::Unit::TestCase
|
5
|
+
describe "An instance of the Attribute class" do
|
6
|
+
|
7
|
+
it "should know the name of the attribute" do
|
8
|
+
attr = Attribute.new('foo')
|
9
|
+
attr.name.should == :foo
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should have a default source" do
|
13
|
+
attr = Attribute.new(:foo)
|
14
|
+
attr.sources.should == ['foo']
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be able to assign multiple sources" do
|
18
|
+
attr = Attribute.new(:foo, ['foo1', 'foo2'])
|
19
|
+
attr.sources.should == ['foo1', 'foo2']
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should pull the location from the source" do
|
23
|
+
attr = Attribute.new('foo')
|
24
|
+
attr.location('foo').should == 'foo'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return the location when splitting" do
|
28
|
+
attr = Attribute.new('foo')
|
29
|
+
attr.split('foo').should == ['foo', nil]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return the name for the location when splitting if the location isn't specified" do
|
33
|
+
attr = Attribute.new('foo')
|
34
|
+
attr.split('@bar').should == ['foo', 'bar']
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should allow the setting of the location information" do
|
38
|
+
attr = Attribute.new('foo', 'bar')
|
39
|
+
attr.sources.should == ['bar']
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should allow the setting of the attribute value" do
|
43
|
+
attr = Attribute.new('foo')
|
44
|
+
attr.attribute('@bogon').should == 'bogon'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should use the location as the attribute" do
|
48
|
+
attr = Attribute.new('foo')
|
49
|
+
attr.attribute('foo').should == 'foo'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should use the attribute for the attribute if specified" do
|
53
|
+
attr = Attribute.new(:id, '@nsid')
|
54
|
+
attr.attribute('@nsid').should == 'nsid'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should be able to retrieve the node from the path" do
|
58
|
+
document = Hpricot.XML('<name>Bassdrive</name>')
|
59
|
+
expected = document.at('name')
|
60
|
+
|
61
|
+
attr = Attribute.new(:name)
|
62
|
+
attr.node_for(document, 'name').should == expected
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should be able to retrieve the node that contains the specified attribute" do
|
66
|
+
document = Hpricot.XML('<user id="1337" />')
|
67
|
+
expected = document.at('user')
|
68
|
+
|
69
|
+
attr = Attribute.new(:id)
|
70
|
+
attr.node_for(document, '@id').should == expected
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be able to retrieve the node for the specified attribute" do
|
74
|
+
document = Hpricot.XML('<user nsid="1337" />')
|
75
|
+
expected = document.at('user')
|
76
|
+
|
77
|
+
attr = Attribute.new(:id, '@nsid')
|
78
|
+
attr.node_for(document, '@nsid').should == expected
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be able to pull simple values from an XML document" do
|
82
|
+
document = Hpricot.XML('<name>Bassdrive</name>')
|
83
|
+
attr = Attribute.new(:name)
|
84
|
+
attr.value_from(document).should == 'Bassdrive'
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should be able to pull an attribute value from the current XML node" do
|
88
|
+
document = Hpricot.XML('<user id="1337" />')
|
89
|
+
attr = Attribute.new(:id)
|
90
|
+
attr.value_from(document).should == '1337'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should be able to pull a specific attribute value from the current XML node" do
|
94
|
+
document = Hpricot.XML('<user nsid="1337" />')
|
95
|
+
attr = Attribute.new(:id, '@nsid')
|
96
|
+
attr.value_from(document).should == '1337'
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should be able to pull an attribute value for a node and attribute" do
|
100
|
+
document = Hpricot.XML('<station><genre slug="dnb">Drum & Bass</genre></station>')
|
101
|
+
attr = Attribute.new(:slug, 'station/genre@slug')
|
102
|
+
attr.value_from(document).should == 'dnb'
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should be able to pull a value from a nested XML node" do
|
106
|
+
document = Hpricot.XML('<rsp><user>blip</user></rsp>')
|
107
|
+
attr = Attribute.new(:user)
|
108
|
+
attr.value_from(document).should == 'blip'
|
109
|
+
end
|
110
|
+
|
111
|
+
# it "should be able to pull a value from an attribute once it's found a node by another attribute" do
|
112
|
+
# document = Hpricot.XML('<node type="blip" value="bleep" />')
|
113
|
+
# attr = Attribute.new(:blip, "node[@type='blip]@value")
|
114
|
+
#
|
115
|
+
# attr.value_from(document).should == 'bleep'
|
116
|
+
# end
|
117
|
+
|
118
|
+
it "should return nil if it cannot find the specified node" do
|
119
|
+
document = Hpricot.XML('<user id="1" />')
|
120
|
+
attr = Attribute.new(:photoset, '@nsid')
|
121
|
+
attr.value_from(document).should be(nil)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should be able to try a series of nodes to find a value" do
|
125
|
+
document = Hpricot.XML('<photoid>123</photoid>')
|
126
|
+
|
127
|
+
attr = Attribute.new(:id, ['photo@nsid', 'photoid'])
|
128
|
+
attr.value_from(document).should == '123'
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class EmptyModel
|
4
|
+
include Graft::Model
|
5
|
+
end
|
6
|
+
|
7
|
+
class ModelWithAttributes
|
8
|
+
include Graft::Model
|
9
|
+
|
10
|
+
attribute :name
|
11
|
+
attribute :description, :from => 'desc'
|
12
|
+
attribute :rating, :from => 'rating@value'
|
13
|
+
attribute :size, :from => "node[@type='size']@value"
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class ModelTest < Test::Unit::TestCase
|
18
|
+
|
19
|
+
describe "The EmptyModel class" do
|
20
|
+
it "should have an empty list of attributes if none are supplied" do
|
21
|
+
EmptyModel.attributes.should == []
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "The ModelWithAttributes class" do
|
27
|
+
it "should know the names of all its attributes" do
|
28
|
+
ModelWithAttributes.attributes.map {|a| a.name.to_s }.should == %w(name description rating size)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "An instance of the ModelWithAttributes class" do
|
33
|
+
|
34
|
+
before { @simple_xml = '<name>Graft</name>' }
|
35
|
+
|
36
|
+
it "should have default reader method for :name" do
|
37
|
+
ModelWithAttributes.new.respond_to?(:name).should be(true)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be able to populate its data on initialization" do
|
41
|
+
xml = Hpricot.XML(@simple_xml)
|
42
|
+
ModelWithAttributes.new(xml).name.should == 'Graft'
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should have a reference to the original document" do
|
46
|
+
xml = Hpricot.XML(@simple_xml)
|
47
|
+
ModelWithAttributes.new(xml).document.should == xml
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should be able to populate from an XML string" do
|
51
|
+
ModelWithAttributes.new(@simple_xml).name.should == 'Graft'
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when populating data from an XML document" do
|
55
|
+
|
56
|
+
before do
|
57
|
+
xml = <<-XML
|
58
|
+
<name>Graft</name>
|
59
|
+
<desc>A sweet Ruby library</desc>
|
60
|
+
<rating value="100" />
|
61
|
+
<node type="color" value="blue" />
|
62
|
+
<node type="size" value="large" />
|
63
|
+
XML
|
64
|
+
|
65
|
+
@model = ModelWithAttributes.new
|
66
|
+
@model.populate_from(Hpricot.XML(xml))
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have the correct value for :name" do
|
70
|
+
@model.name.should == 'Graft'
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should have the correct value for :description" do
|
74
|
+
@model.description.should == 'A sweet Ruby library'
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should have the correct value for :rating" do
|
78
|
+
@model.rating.should == '100'
|
79
|
+
end
|
80
|
+
|
81
|
+
# it "should have the correct value for :size" do
|
82
|
+
# @model.size.should == 'large'
|
83
|
+
# end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reagent-graft
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrick Reagan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-06 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.6.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "2.0"
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: reaganpr@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.rdoc
|
43
|
+
files:
|
44
|
+
- README.rdoc
|
45
|
+
- Rakefile
|
46
|
+
- lib/graft
|
47
|
+
- lib/graft/attribute.rb
|
48
|
+
- lib/graft/model.rb
|
49
|
+
- lib/graft/version.rb
|
50
|
+
- lib/graft.rb
|
51
|
+
- test/test_helper.rb
|
52
|
+
- test/unit
|
53
|
+
- test/unit/attribute_test.rb
|
54
|
+
- test/unit/model_test.rb
|
55
|
+
- test/unit/source_test.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://sneaq.net/
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --main
|
61
|
+
- README.rdoc
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.2.0
|
80
|
+
signing_key:
|
81
|
+
specification_version: 2
|
82
|
+
summary: Simple XML to attribute mapping for your Ruby classes
|
83
|
+
test_files: []
|
84
|
+
|