graft 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/graft.rb ADDED
@@ -0,0 +1,7 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'hpricot'
4
+ require 'active_support/core_ext/blank'
5
+
6
+ require 'graft/attribute'
7
+ require 'graft/model'
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ module Graft
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+
8
+ def self.to_s # :nodoc:
9
+ [MAJOR, MINOR, TINY].join('.')
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # http://sneaq.net/textmate-wtf
2
+ $:.reject! { |e| e.include? 'TextMate' }
3
+
4
+ require 'rubygems'
5
+ require 'throat_punch'
6
+
7
+ require File.dirname(__FILE__) + '/../lib/graft'
@@ -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
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ module Graft
4
+ class SourceTest < Test::Unit::TestCase
5
+
6
+ describe "An instance of the Source class" do
7
+
8
+
9
+
10
+ end
11
+
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: 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-07-21 00:00:00 -04: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/attribute.rb
47
+ - lib/graft/model.rb
48
+ - lib/graft/version.rb
49
+ - lib/graft.rb
50
+ - test/test_helper.rb
51
+ - test/unit/attribute_test.rb
52
+ - test/unit/model_test.rb
53
+ - test/unit/source_test.rb
54
+ has_rdoc: true
55
+ homepage: http://sneaq.net/
56
+ licenses: []
57
+
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.3.4
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Simple XML to attribute mapping for your Ruby classes
83
+ test_files: []
84
+