graft 0.1.1 → 0.2.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/Rakefile +1 -0
- data/lib/graft/core_ext/hash.rb +9 -0
- data/lib/graft/json.rb +14 -0
- data/lib/graft/json/attribute.rb +18 -0
- data/lib/graft/json/model.rb +28 -0
- data/lib/graft/model.rb +13 -44
- data/lib/graft/version.rb +2 -2
- data/lib/graft/xml.rb +19 -0
- data/lib/graft/xml/attribute.rb +52 -0
- data/lib/graft/xml/model.rb +49 -0
- data/lib/graft/xml/type.rb +91 -0
- data/test/test_helper.rb +4 -3
- data/test/unit/core_ext/hash_test.rb +29 -0
- data/test/unit/json/attribute_test.rb +51 -0
- data/test/unit/json/model_test.rb +86 -0
- data/test/unit/xml/attribute_test.rb +161 -0
- data/test/unit/{model_test.rb → xml/model_test.rb} +65 -47
- data/test/unit/xml/type_test.rb +65 -0
- metadata +26 -9
- data/lib/graft.rb +0 -13
- data/lib/graft/attribute.rb +0 -50
- data/lib/graft/type.rb +0 -89
- data/test/unit/attribute_test.rb +0 -159
- data/test/unit/source_test.rb +0 -16
- data/test/unit/type_test.rb +0 -63
data/Rakefile
CHANGED
@@ -22,6 +22,7 @@ spec = Gem::Specification.new do |s|
|
|
22
22
|
s.add_dependency('tzinfo', '>= 0.3.12')
|
23
23
|
s.add_dependency('builder', '>= 2.1.2')
|
24
24
|
s.add_dependency('activesupport', '>= 2.0')
|
25
|
+
s.add_dependency('json', '>= 1.1.7')
|
25
26
|
end
|
26
27
|
|
27
28
|
Rake::GemPackageTask.new(spec) do |pkg|
|
data/lib/graft/json.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/..'
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'graft/core_ext/hash'
|
5
|
+
|
6
|
+
require 'graft/model'
|
7
|
+
require 'graft/json/attribute'
|
8
|
+
require 'graft/json/model'
|
9
|
+
|
10
|
+
module Graft
|
11
|
+
def self.included(other)
|
12
|
+
other.send(:include, Graft::Json::Model)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Graft
|
2
|
+
module Json
|
3
|
+
class Attribute
|
4
|
+
|
5
|
+
attr_reader :name, :source
|
6
|
+
|
7
|
+
def initialize(name, source = nil)
|
8
|
+
@name = name
|
9
|
+
@source = source.nil? ? @name.to_s : source
|
10
|
+
end
|
11
|
+
|
12
|
+
def value_from(data)
|
13
|
+
data/source
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Graft
|
2
|
+
module Json
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
def attribute(name, options = {})
|
8
|
+
source = options[:from]
|
9
|
+
|
10
|
+
self.attributes << Graft::Json::Attribute.new(name, source)
|
11
|
+
class_eval "attr_accessor :#{name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def data_from(json_or_hash)
|
15
|
+
json_or_hash.is_a?(String) ? JSON.parse(json_or_hash) : json_or_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.included(other)
|
21
|
+
other.send(:extend, Graft::Model::ClassMethods)
|
22
|
+
other.send(:extend, Graft::Json::Model::ClassMethods)
|
23
|
+
other.send(:include, Graft::Model::InstanceMethods)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/graft/model.rb
CHANGED
@@ -1,69 +1,38 @@
|
|
1
1
|
module Graft
|
2
2
|
module Model
|
3
|
-
|
3
|
+
|
4
4
|
module ClassMethods
|
5
|
-
|
6
5
|
def attributes
|
7
6
|
@attributes ||= []
|
8
7
|
end
|
9
8
|
|
10
|
-
def
|
11
|
-
|
12
|
-
type = options[:type] || :string
|
13
|
-
|
14
|
-
self.attributes << Attribute.new(name, type, source)
|
15
|
-
class_eval "attr_accessor :#{name}"
|
16
|
-
end
|
17
|
-
|
18
|
-
def collection_from(xml, node)
|
19
|
-
(Hpricot.XML(xml)/node).map {|n| new n.to_s }
|
9
|
+
def collection_from(data_source, node)
|
10
|
+
(data_from(data_source)/node).map {|n| new n }
|
20
11
|
end
|
21
|
-
|
22
12
|
end
|
23
|
-
|
13
|
+
|
24
14
|
module InstanceMethods
|
25
15
|
|
26
|
-
def initialize(
|
27
|
-
self.
|
28
|
-
self.populate_from(self.
|
16
|
+
def initialize(source_data = nil)
|
17
|
+
self.source_data = source_data
|
18
|
+
self.populate_from(self.source_data) unless self.source_data.nil?
|
29
19
|
end
|
30
20
|
|
31
|
-
def
|
32
|
-
@
|
21
|
+
def source_data=(source_data)
|
22
|
+
@source_data = self.class.data_from(source_data)
|
33
23
|
end
|
34
24
|
|
35
|
-
def
|
36
|
-
@
|
25
|
+
def source_data
|
26
|
+
@source_data
|
37
27
|
end
|
38
28
|
|
39
|
-
def populate_from(
|
29
|
+
def populate_from(data_source)
|
40
30
|
self.class.attributes.each do |attribute|
|
41
|
-
value = attribute.value_from(
|
31
|
+
value = attribute.value_from(self.class.data_from(data_source))
|
42
32
|
self.send("#{attribute.name}=".to_sym, value) unless value.nil?
|
43
33
|
end
|
44
34
|
end
|
45
35
|
|
46
|
-
def to_hash
|
47
|
-
self.class.attributes.inject({}) {|h,a| h.merge(a.name.to_s => send(a.name)) }
|
48
|
-
end
|
49
|
-
|
50
|
-
def to_xml(tag_name)
|
51
|
-
xml = Builder::XmlMarkup.new
|
52
|
-
xml.instruct!
|
53
|
-
xml.tag! tag_name do
|
54
|
-
to_hash.each do |attribute, value|
|
55
|
-
xml.tag! attribute, value
|
56
|
-
end
|
57
|
-
end
|
58
|
-
xml.target!
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.included(other)
|
65
|
-
other.send(:extend, Graft::Model::ClassMethods)
|
66
|
-
other.send(:include, Graft::Model::InstanceMethods)
|
67
36
|
end
|
68
37
|
|
69
38
|
end
|
data/lib/graft/version.rb
CHANGED
data/lib/graft/xml.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/..'
|
2
|
+
|
3
|
+
require 'hpricot'
|
4
|
+
require 'builder'
|
5
|
+
require 'tzinfo'
|
6
|
+
require 'active_support/core_ext/blank'
|
7
|
+
require 'active_support/time_with_zone'
|
8
|
+
require 'active_support/inflector'
|
9
|
+
|
10
|
+
require 'graft/model'
|
11
|
+
require 'graft/xml/type'
|
12
|
+
require 'graft/xml/attribute'
|
13
|
+
require 'graft/xml/model'
|
14
|
+
|
15
|
+
module Graft
|
16
|
+
def self.included(other)
|
17
|
+
other.send(:include, Graft::Xml::Model)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Graft
|
2
|
+
module Xml
|
3
|
+
class Attribute
|
4
|
+
|
5
|
+
# TODO: Refactor the location / attribute logic into a Source class
|
6
|
+
|
7
|
+
attr_reader :name, :sources
|
8
|
+
|
9
|
+
def initialize(name, type = :string, sources = nil)
|
10
|
+
@name = name.to_sym
|
11
|
+
@type = type
|
12
|
+
|
13
|
+
@sources = Array(sources)
|
14
|
+
@sources << @name.to_s if @sources.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_class
|
18
|
+
"Graft::Xml::Type::#{@type.to_s.camelize}".constantize
|
19
|
+
end
|
20
|
+
|
21
|
+
def split(source)
|
22
|
+
location, attribute = source.split('@')
|
23
|
+
location = self.name.to_s if location.blank?
|
24
|
+
|
25
|
+
[location, attribute]
|
26
|
+
end
|
27
|
+
|
28
|
+
def node_for(document, source)
|
29
|
+
document.at(location(source)) || document.search("//[@#{attribute(source)}]").first
|
30
|
+
end
|
31
|
+
|
32
|
+
def attribute(source)
|
33
|
+
location, attribute = source.split('@')
|
34
|
+
attribute || location
|
35
|
+
end
|
36
|
+
|
37
|
+
def location(source)
|
38
|
+
split(source).first
|
39
|
+
end
|
40
|
+
|
41
|
+
def value_from(document)
|
42
|
+
values = sources.map do |source|
|
43
|
+
node = node_for(document, source)
|
44
|
+
(node.attributes[attribute(source)] || node.inner_text) unless node.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
type_class.new(values.compact.first).value
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Graft
|
2
|
+
module Xml
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
def attribute(name, options = {})
|
8
|
+
source = options[:from]
|
9
|
+
type = options[:type] || :string
|
10
|
+
|
11
|
+
self.attributes << Graft::Xml::Attribute.new(name, type, source)
|
12
|
+
class_eval "attr_accessor :#{name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def data_from(xml_or_document)
|
16
|
+
xml_or_document.is_a?(String) ? Hpricot.XML(xml_or_document) : xml_or_document
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
self.class.attributes.inject({}) {|h,a| h.merge(a.name.to_s => send(a.name)) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_xml(tag_name)
|
28
|
+
xml = Builder::XmlMarkup.new
|
29
|
+
xml.instruct!
|
30
|
+
xml.tag! tag_name do
|
31
|
+
to_hash.each do |attribute, value|
|
32
|
+
xml.tag! attribute, value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
xml.target!
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.included(other)
|
41
|
+
other.send(:extend, Graft::Model::ClassMethods)
|
42
|
+
other.send(:extend, Graft::Xml::Model::ClassMethods)
|
43
|
+
other.send(:include, Graft::Model::InstanceMethods)
|
44
|
+
other.send(:include, Graft::Xml::Model::InstanceMethods)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Graft
|
2
|
+
module Xml
|
3
|
+
|
4
|
+
# = Type
|
5
|
+
#
|
6
|
+
class Type
|
7
|
+
|
8
|
+
class ConversionError < StandardError; end
|
9
|
+
|
10
|
+
def initialize(source)
|
11
|
+
@source = source
|
12
|
+
end
|
13
|
+
|
14
|
+
def convertible?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def value
|
19
|
+
raise ConversionError unless (@source.blank? || convertible?)
|
20
|
+
@source.blank? ? nil : convert
|
21
|
+
end
|
22
|
+
|
23
|
+
# = String
|
24
|
+
#
|
25
|
+
class String < Type
|
26
|
+
|
27
|
+
def convert
|
28
|
+
@source
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# = Boolean
|
34
|
+
#
|
35
|
+
class Boolean < Type
|
36
|
+
def true_values
|
37
|
+
['true', '1']
|
38
|
+
end
|
39
|
+
|
40
|
+
def false_values
|
41
|
+
['false', '0']
|
42
|
+
end
|
43
|
+
|
44
|
+
def convertible?
|
45
|
+
(true_values + false_values).include?(@source)
|
46
|
+
end
|
47
|
+
|
48
|
+
def convert
|
49
|
+
true_values.include?(@source) ? true : false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# = Integer
|
54
|
+
#
|
55
|
+
class Integer < Type
|
56
|
+
def convertible?
|
57
|
+
!@source.match(/\d+/).nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def convert
|
61
|
+
@source.to_i
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# = Time
|
66
|
+
#
|
67
|
+
class Time < Type
|
68
|
+
|
69
|
+
def timestamp?
|
70
|
+
!@source.match(/^\d+$/).nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def convert
|
74
|
+
timestamp? ? ::Time.at(@source.to_i) : ::Time.parse(@source)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
# = Date
|
80
|
+
#
|
81
|
+
class Date < Type
|
82
|
+
|
83
|
+
def convert
|
84
|
+
::Date.parse(@source)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -4,13 +4,14 @@ $:.reject! { |e| e.include? 'TextMate' }
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'throat_punch'
|
6
6
|
|
7
|
-
require File.dirname(__FILE__) + '/../lib/graft'
|
7
|
+
require File.dirname(__FILE__) + '/../lib/graft/xml'
|
8
|
+
require File.dirname(__FILE__) + '/../lib/graft/json'
|
8
9
|
|
9
10
|
class Test::Unit::TestCase
|
10
11
|
|
11
12
|
def self.implementation_klass
|
12
13
|
class_name = self.to_s.match(/([a-zA-Z]+)Test$/)[1]
|
13
|
-
klass = Graft::Type.const_get(class_name)
|
14
|
+
klass = Graft::Xml::Type.const_get(class_name)
|
14
15
|
|
15
16
|
klass
|
16
17
|
end
|
@@ -30,7 +31,7 @@ class Test::Unit::TestCase
|
|
30
31
|
|
31
32
|
should "fail when converting '#{source}'" do
|
32
33
|
o = klass.new(source)
|
33
|
-
lambda { o.value }.should raise_error(Graft::Type::ConversionError)
|
34
|
+
lambda { o.value }.should raise_error(Graft::Xml::Type::ConversionError)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../test_helper'
|
2
|
+
|
3
|
+
class HashTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "An instance of Hash" do
|
6
|
+
|
7
|
+
should "be able to extract a value using the / operator" do
|
8
|
+
hash = {'key' => 'value'}
|
9
|
+
(hash/'key').should == 'value'
|
10
|
+
end
|
11
|
+
|
12
|
+
should "be able to extract nested values using the / operator" do
|
13
|
+
hash = {'user' => {'name' => 'luser'}}
|
14
|
+
(hash/'user/name').should == 'luser'
|
15
|
+
end
|
16
|
+
|
17
|
+
should "return nil when attempting to extract a value that doesn't exist" do
|
18
|
+
hash = {'user' => {'name' => 'luser'}}
|
19
|
+
(hash/'user/username').should be(nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "be able to fetch a value using a symbol" do
|
23
|
+
hash = {'key' => 'value'}
|
24
|
+
(hash/:key).should == 'value'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|