muffins 0.0.1
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/features/basic_parsing.feature +33 -0
- data/features/nested_parsing.feature +37 -0
- data/features/non_primitive_typing.feature +36 -0
- data/features/specified_pathnames.feature +33 -0
- data/features/specified_typing.feature +33 -0
- data/features/step_definitions/application_steps.rb +52 -0
- data/features/support/env.rb +16 -0
- data/lib/muffins.rb +43 -0
- data/lib/muffins/mapping.rb +73 -0
- data/lib/muffins/mapping_decorator.rb +22 -0
- data/lib/muffins/version.rb +3 -0
- data/muffins.gemspec +36 -0
- data/spec/mapping_spec.rb +219 -0
- data/spec/muffins/mapping_decorator_spec.rb +46 -0
- data/spec/muffins_spec.rb +128 -0
- data/spec/spec_helper.rb +12 -0
- metadata +132 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Basic Parsing
|
2
|
+
As an XML mapping library
|
3
|
+
I'd like to be awesome at mapping
|
4
|
+
So I can get hella downloads
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given the following xml:
|
8
|
+
"""
|
9
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
10
|
+
<books>
|
11
|
+
<book>
|
12
|
+
<asin>1400079985</asin>
|
13
|
+
<title>War and Peace (Vintage Classics)</title>
|
14
|
+
</book>
|
15
|
+
<book>
|
16
|
+
<asin>1143035008</asin>
|
17
|
+
<title>Anna Karenina (Oprah's Book Club)</title>
|
18
|
+
</book>
|
19
|
+
</books>
|
20
|
+
"""
|
21
|
+
|
22
|
+
Scenario: I parse the xml
|
23
|
+
Given that base_path has been set to 'book'
|
24
|
+
And the following mappings have been defined:
|
25
|
+
| name |
|
26
|
+
| asin |
|
27
|
+
| title |
|
28
|
+
When I parse the xml
|
29
|
+
Then I should have 2 books
|
30
|
+
And the following values should be returned:
|
31
|
+
| asin | title |
|
32
|
+
| 1400079985 | War and Peace (Vintage Classics) |
|
33
|
+
| 1143035008 | Anna Karenina (Oprah's Book Club) |
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Feature: Nested Parsing
|
2
|
+
As an XML mapping library
|
3
|
+
I'd like to be awesome at mapping
|
4
|
+
So I can get hella downloads
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given the following xml:
|
8
|
+
"""
|
9
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
10
|
+
<books>
|
11
|
+
<book>
|
12
|
+
<asin>1400079985</asin>
|
13
|
+
<itemattributes>
|
14
|
+
<title>War and Peace (Vintage Classics)</title>
|
15
|
+
</itemattributes>
|
16
|
+
</book>
|
17
|
+
<book>
|
18
|
+
<asin>1143035008</asin>
|
19
|
+
<itemattributes>
|
20
|
+
<title>Anna Karenina (Oprah's Book Club)</title>
|
21
|
+
</itemattributes>
|
22
|
+
</book>
|
23
|
+
</books>
|
24
|
+
"""
|
25
|
+
Scenario: I parse the xml
|
26
|
+
Given that base_path has been set to 'book'
|
27
|
+
And the following mappings have been defined:
|
28
|
+
| name | within |
|
29
|
+
| asin | |
|
30
|
+
| title | itemattributes |
|
31
|
+
When I parse the xml
|
32
|
+
Then I should have 2 books
|
33
|
+
And the following values should be returned:
|
34
|
+
| asin | title |
|
35
|
+
| 1400079985 | War and Peace (Vintage Classics) |
|
36
|
+
| 1143035008 | Anna Karenina (Oprah's Book Club) |
|
37
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
Feature: Non-Primitive Typing
|
2
|
+
As an XML mapping library
|
3
|
+
I'd like to be awesome at mapping
|
4
|
+
So I can get hella downloads
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given the following xml:
|
8
|
+
"""
|
9
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
10
|
+
<books>
|
11
|
+
<book>
|
12
|
+
<asin>1400079985</asin>
|
13
|
+
<title>War and Peace (Vintage Classics)</title>
|
14
|
+
<similarproducts>
|
15
|
+
<product>
|
16
|
+
<asin>1143035008</asin>
|
17
|
+
<title>Anna Karenina (Oprah's Book Club)</title>
|
18
|
+
</product>
|
19
|
+
</similarproducts>
|
20
|
+
</book>
|
21
|
+
</books>
|
22
|
+
"""
|
23
|
+
|
24
|
+
Scenario: I parse the xml
|
25
|
+
Given that base_path has been set to 'book'
|
26
|
+
And the following mappings have been defined:
|
27
|
+
| name | type |
|
28
|
+
| asin | |
|
29
|
+
| title | |
|
30
|
+
| similarproducts | Product |
|
31
|
+
When I parse the xml
|
32
|
+
Then I should have 1 books
|
33
|
+
And the following values should be returned:
|
34
|
+
| asin | title |
|
35
|
+
| 1400079985 | War and Peace (Vintage Classics) |
|
36
|
+
And 'similarproducts' should return 1 instances of Product
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Specified Pathnames
|
2
|
+
As an XML mapping library
|
3
|
+
I'd like to be awesome at mapping
|
4
|
+
So I can get hella downloads
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given the following xml:
|
8
|
+
"""
|
9
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
10
|
+
<BOOKS>
|
11
|
+
<BOOK>
|
12
|
+
<ASIN>1400079985</ASIN>
|
13
|
+
<TITLE>War and Peace (Vintage Classics)</TITLE>
|
14
|
+
</BOOK>
|
15
|
+
<BOOK>
|
16
|
+
<ASIN>1143035008</ASIN>
|
17
|
+
<TITLE>Anna Karenina (Oprah's Book Club)</TITLE>
|
18
|
+
</BOOK>
|
19
|
+
</BOOKS>
|
20
|
+
"""
|
21
|
+
Scenario: I parse the xml
|
22
|
+
Given that base_path has been set to 'BOOK'
|
23
|
+
And the following mappings have been defined:
|
24
|
+
| name | to |
|
25
|
+
| asin | ASIN |
|
26
|
+
| title | TITLE |
|
27
|
+
When I parse the xml
|
28
|
+
Then I should have 2 books
|
29
|
+
And the following values should be returned:
|
30
|
+
| asin | title |
|
31
|
+
| 1400079985 | War and Peace (Vintage Classics) |
|
32
|
+
| 1143035008 | Anna Karenina (Oprah's Book Club) |
|
33
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: Specified Typing
|
2
|
+
As an XML mapping library
|
3
|
+
I'd like to be awesome at mapping
|
4
|
+
So I can get hella downloads
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given the following xml:
|
8
|
+
"""
|
9
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
10
|
+
<books>
|
11
|
+
<book>
|
12
|
+
<asin>1400079985</asin>
|
13
|
+
<title>War and Peace (Vintage Classics)</title>
|
14
|
+
</book>
|
15
|
+
<book>
|
16
|
+
<asin>1143035008</asin>
|
17
|
+
<title>Anna Karenina (Oprah's Book Club)</title>
|
18
|
+
</book>
|
19
|
+
</books>
|
20
|
+
"""
|
21
|
+
Scenario: I parse the xml
|
22
|
+
Given that base_path has been set to 'book'
|
23
|
+
And the following mappings have been defined:
|
24
|
+
| name | type |
|
25
|
+
| asin | Integer |
|
26
|
+
| title | String |
|
27
|
+
When I parse the xml
|
28
|
+
Then I should have 2 books
|
29
|
+
And the following data types should be returned:
|
30
|
+
| asin | title |
|
31
|
+
| Integer | String |
|
32
|
+
| Integer | String |
|
33
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Given /^the following xml:$/ do |doc|
|
2
|
+
@xml = Nokogiri::XML(doc)
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^that base_path has been set to '(.*?)'$/ do |base_path|
|
6
|
+
Book.base_path base_path
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^the following mappings have been defined:$/ do |table|
|
10
|
+
table.hashes.each do |options|
|
11
|
+
name = options.delete('name')
|
12
|
+
Book.map name, options
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
When /^I parse the xml$/ do
|
18
|
+
@books = Book.parse(@xml)
|
19
|
+
end
|
20
|
+
|
21
|
+
Then /^I should have (\d+) books$/ do |count|
|
22
|
+
@books.count.should == count.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
Then /^the following values should be returned:$/ do |table|
|
26
|
+
@books.count.times do |count|
|
27
|
+
book = @books[count]
|
28
|
+
|
29
|
+
table.hashes[count].each_pair do |key, value|
|
30
|
+
book.send(key).should == value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Then /^the following data types should be returned:$/ do |table|
|
36
|
+
@books.count.times do |count|
|
37
|
+
book = @books[count]
|
38
|
+
|
39
|
+
table.hashes[count].each_pair do |key, value|
|
40
|
+
type = value.constantize
|
41
|
+
book.send(key).should be_a(type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Then /^'(.*?)' should return (\d+) instances of (.*?)$/ do |method, count, type|
|
47
|
+
@books.each do |book|
|
48
|
+
products = book.send(method)
|
49
|
+
products.count.should == count.to_i
|
50
|
+
products.each {|p| p.should be_a(type.constantize) }
|
51
|
+
end
|
52
|
+
end
|
data/lib/muffins.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "muffins/mapping"
|
2
|
+
require "muffins/mapping_decorator"
|
3
|
+
require "muffins/version"
|
4
|
+
|
5
|
+
module Muffins
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def base_path(bp = nil)
|
13
|
+
@base_path = bp if bp
|
14
|
+
@base_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse(document, options = {})
|
18
|
+
document.css(base_path).collect do |node|
|
19
|
+
(new).tap do |object|
|
20
|
+
|
21
|
+
mappings.each do |mapping|
|
22
|
+
object.send("#{mapping.name}=", mapping.map(node))
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def within(path, &block)
|
30
|
+
yield MappingDecorator.new(path, self)
|
31
|
+
end
|
32
|
+
|
33
|
+
def map(symbol, options = {})
|
34
|
+
attr_accessor symbol unless new.respond_to?(symbol)
|
35
|
+
|
36
|
+
mappings << Mapping.new(symbol, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def mappings
|
40
|
+
@mappings ||= []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Muffins
|
2
|
+
class Mapping
|
3
|
+
|
4
|
+
PRIMITIVE_TYPES = [String, Float, Time, Date, DateTime, Integer]
|
5
|
+
|
6
|
+
attr_reader :name, :options
|
7
|
+
|
8
|
+
def initialize(name, options = {})
|
9
|
+
@name = name
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def map(document)
|
14
|
+
if collection?
|
15
|
+
([]).tap do |items|
|
16
|
+
document.css(path).each do |node|
|
17
|
+
items << typecast(node)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
typecast document.at_css(path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
path = options[:to].present? ? options[:to] : name
|
27
|
+
|
28
|
+
unless options[:within].blank?
|
29
|
+
path = "#{options[:within]} > #{path}"
|
30
|
+
end
|
31
|
+
|
32
|
+
@path ||= path
|
33
|
+
end
|
34
|
+
|
35
|
+
def collection?
|
36
|
+
!!options[:collection]
|
37
|
+
end
|
38
|
+
|
39
|
+
def primitive?
|
40
|
+
PRIMITIVE_TYPES.include? options[:type].constantize
|
41
|
+
end
|
42
|
+
|
43
|
+
def typecast(node)
|
44
|
+
value, constant = node.try(:text), "#{options[:type]}"
|
45
|
+
|
46
|
+
return value if value.blank? || constant.blank?
|
47
|
+
|
48
|
+
constant = constant.constantize
|
49
|
+
|
50
|
+
if primitive?
|
51
|
+
if constant == String
|
52
|
+
value.to_s
|
53
|
+
elsif constant == Float
|
54
|
+
value.to_f
|
55
|
+
elsif constant == Time
|
56
|
+
Time.parse(value) rescue Time.at(value.to_i)
|
57
|
+
elsif constant == Date
|
58
|
+
Date.parse(value)
|
59
|
+
elsif constant == DateTime
|
60
|
+
DateTime.parse(value)
|
61
|
+
elsif constant == Integer
|
62
|
+
value.to_i
|
63
|
+
else
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
else
|
68
|
+
constant.parse(node)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Muffins
|
2
|
+
class MappingDecorator
|
3
|
+
|
4
|
+
attr_reader :base_path, :object
|
5
|
+
|
6
|
+
def initialize(base_path, object)
|
7
|
+
@base_path = base_path
|
8
|
+
@object = object
|
9
|
+
end
|
10
|
+
|
11
|
+
def map(name, options = {})
|
12
|
+
options[:within] =
|
13
|
+
if options[:within]
|
14
|
+
"#{base_path} > #{options[:within]}"
|
15
|
+
else
|
16
|
+
base_path
|
17
|
+
end
|
18
|
+
|
19
|
+
object.map(name, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/muffins.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "muffins/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "muffins"
|
7
|
+
s.version = Muffins::VERSION
|
8
|
+
s.authors = ["Ryan Closner"]
|
9
|
+
s.email = ["ryan.closner@gmail.com"]
|
10
|
+
s.homepage = "https://rubygems.org/gems/muffins"
|
11
|
+
s.summary = %q{An Object to XML/HTML mapping library using Nokogiri}
|
12
|
+
s.description = %q{An Object to XML/HTML mapping library using Nokogiri}
|
13
|
+
|
14
|
+
s.rubyforge_project = "muffins"
|
15
|
+
|
16
|
+
{
|
17
|
+
'activesupport' => '~> 2.3.10',
|
18
|
+
'nokogiri' => '~> 1.4'
|
19
|
+
}.each do |lib, version|
|
20
|
+
s.add_runtime_dependency lib, version
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
{
|
25
|
+
'bundler' => '~> 1.0',
|
26
|
+
'cucumber' => '~> 0.10',
|
27
|
+
'rspec' => '~> 2.6',
|
28
|
+
}.each do |lib, version|
|
29
|
+
s.add_development_dependency lib, version
|
30
|
+
end
|
31
|
+
|
32
|
+
s.files = `git ls-files`.split("\n")
|
33
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
34
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Muffins
|
4
|
+
describe Mapping do
|
5
|
+
|
6
|
+
let(:name) { :foo }
|
7
|
+
let(:options) { {} }
|
8
|
+
|
9
|
+
let(:document) { mock(Nokogiri::XML::Document) }
|
10
|
+
|
11
|
+
let(:node) { mock(Nokogiri::XML::Node) }
|
12
|
+
let(:nodes) { [node] }
|
13
|
+
|
14
|
+
let(:value) { 'bar' }
|
15
|
+
let(:values) { [value] }
|
16
|
+
|
17
|
+
subject { Mapping.new(name, options) }
|
18
|
+
|
19
|
+
describe "#map" do
|
20
|
+
before(:each) do
|
21
|
+
subject.stub(:typecast).with(node).and_return(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "#collection? returns true" do
|
25
|
+
before(:each) do
|
26
|
+
subject.stub(:collection?).and_return(true)
|
27
|
+
|
28
|
+
document.stub(:css).and_return(nodes)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "typecasts the parsed values" do
|
32
|
+
subject.should_receive(:typecast).with(node).and_return(value)
|
33
|
+
subject.map(document)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns an array of typecasted values" do
|
37
|
+
subject.map(document).should == values
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#collection? returns false" do
|
42
|
+
before(:each) do
|
43
|
+
subject.stub(:collection?).and_return(false)
|
44
|
+
|
45
|
+
document.stub(:at_css).and_return(node)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "typecasts the parsed value" do
|
49
|
+
subject.should_receive(:typecast).with(node).and_return(value)
|
50
|
+
subject.map(document)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns the typecasted value" do
|
54
|
+
subject.map(document).should == value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#path" do
|
60
|
+
context "no options are passed" do
|
61
|
+
it "returns the mapping name" do
|
62
|
+
subject.path.should == name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "options[:to] is passed" do
|
67
|
+
let(:to) { 'bar' }
|
68
|
+
let(:options) { {:to => to} }
|
69
|
+
|
70
|
+
before(:each) do
|
71
|
+
subject.stub(:options).and_return(options)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns the passed path name" do
|
75
|
+
subject.path.should == to
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "options[:within] is passed" do
|
80
|
+
let(:within) { 'parent' }
|
81
|
+
let(:to) { 'child' }
|
82
|
+
|
83
|
+
let(:options) { {:within => within, :to => to} }
|
84
|
+
|
85
|
+
before(:each) do
|
86
|
+
subject.stub(:options).and_return(options)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "returns the css selector with parent and child" do
|
90
|
+
subject.path.should == "#{within} > #{to}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#collection?" do
|
96
|
+
context "options[:collection] is nil" do
|
97
|
+
it "returns false" do
|
98
|
+
subject.collection?.should be_false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "options[:collection] is false" do
|
103
|
+
|
104
|
+
let(:options) { {:collection => false} }
|
105
|
+
|
106
|
+
before(:each) do
|
107
|
+
subject.stub(:options).and_return(options)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns false" do
|
111
|
+
subject.collection?.should be_false
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "options[:collection] is true" do
|
116
|
+
|
117
|
+
let(:options) { {:collection => true} }
|
118
|
+
|
119
|
+
before(:each) do
|
120
|
+
subject.stub(:options).and_return(options)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "returns true" do
|
124
|
+
subject.collection?.should be_true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#primitive?" do
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "#typecast" do
|
133
|
+
|
134
|
+
before(:each) do
|
135
|
+
node.stub(:try).with(:text).and_return(value)
|
136
|
+
|
137
|
+
subject.stub(:options).and_return(options)
|
138
|
+
end
|
139
|
+
|
140
|
+
context "#primitive? returns true" do
|
141
|
+
|
142
|
+
before(:each) do
|
143
|
+
subject.stub(:primitive?).and_return(true)
|
144
|
+
end
|
145
|
+
|
146
|
+
context "options[:type] = String" do
|
147
|
+
let(:options) { {:type => String} }
|
148
|
+
|
149
|
+
it "returns an instance of String" do
|
150
|
+
subject.typecast(node).should be_a(String)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "options[:type] = Float" do
|
155
|
+
let(:options) { {:type => Float} }
|
156
|
+
let(:value) { '10.01' }
|
157
|
+
|
158
|
+
it "returns an instance of Float" do
|
159
|
+
subject.typecast(node).should be_a(Float)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "options[:type] = Time" do
|
164
|
+
let(:options) { {:type => Time} }
|
165
|
+
|
166
|
+
let(:value) { '00:00:00 -0700' }
|
167
|
+
|
168
|
+
it "returns an instance of Time" do
|
169
|
+
subject.typecast(node).should be_a(Time)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "options[:type] = Date" do
|
174
|
+
let(:options) { {:type => Date} }
|
175
|
+
|
176
|
+
let(:value) { '2011-01-01' }
|
177
|
+
|
178
|
+
it "returns an instance of Date" do
|
179
|
+
subject.typecast(node).should be_a(Date)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "options[:type] = DateTime" do
|
184
|
+
let(:options) { {:type => DateTime} }
|
185
|
+
|
186
|
+
let(:value) { '2011-01-01 00:00:00 -0700' }
|
187
|
+
|
188
|
+
it "returns an instance of DateTime" do
|
189
|
+
subject.typecast(node).should be_a(DateTime)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context "options[:type] = Integer" do
|
194
|
+
let(:options) { {:type => Integer} }
|
195
|
+
|
196
|
+
let(:value) { '12908234' }
|
197
|
+
|
198
|
+
it "returns an instance of Integer" do
|
199
|
+
subject.typecast(node).should be_a(Integer)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context "#primitive? returns false" do
|
205
|
+
let(:options) { {:type => Book} }
|
206
|
+
|
207
|
+
before(:each) do
|
208
|
+
subject.stub(:primitive?).and_return(false)
|
209
|
+
end
|
210
|
+
|
211
|
+
it "attempts to parse the nested set" do
|
212
|
+
Book.should_receive(:parse).with(node)
|
213
|
+
subject.typecast(node)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Muffins
|
4
|
+
describe MappingDecorator do
|
5
|
+
|
6
|
+
let(:object) do
|
7
|
+
class Book
|
8
|
+
include Muffins
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:base_path) { 'foo' }
|
13
|
+
let(:name) { 'bar' }
|
14
|
+
|
15
|
+
let(:decorator_options) { {} }
|
16
|
+
|
17
|
+
let(:nested_path) { 'foobar' }
|
18
|
+
|
19
|
+
subject { MappingDecorator.new(base_path, object) }
|
20
|
+
|
21
|
+
describe "#map" do
|
22
|
+
context "options[:within] is nil" do
|
23
|
+
|
24
|
+
let(:mapping_options) { {:within => base_path } }
|
25
|
+
|
26
|
+
it "sets options[:within] to the base_path" do
|
27
|
+
object.should_receive(:map).with(name, mapping_options)
|
28
|
+
subject.map(name, decorator_options)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "options[:within] is not nil" do
|
33
|
+
|
34
|
+
let(:mapping_options) { {:within => "#{base_path} > #{nested_path}"} }
|
35
|
+
|
36
|
+
let(:decorator_options) { {:within => nested_path} }
|
37
|
+
|
38
|
+
|
39
|
+
it "sets options[:within] to the 'base_path > nested_path'" do
|
40
|
+
object.should_receive(:map).with(name, mapping_options)
|
41
|
+
subject.map(name, decorator_options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Muffins do
|
4
|
+
|
5
|
+
context "when included in a class" do
|
6
|
+
|
7
|
+
subject do
|
8
|
+
class Book
|
9
|
+
include Muffins
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:book) { mock(Book) }
|
14
|
+
|
15
|
+
let(:mapping) { mock(Muffins::Mapping) }
|
16
|
+
let(:mapping_name) { :foo }
|
17
|
+
let(:mapping_options) { {} }
|
18
|
+
|
19
|
+
|
20
|
+
describe ".mappings" do
|
21
|
+
it "returns an array" do
|
22
|
+
subject.mappings.should be_an(Array)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ".base_path" do
|
27
|
+
context "value is passed" do
|
28
|
+
it "sets @base_path to the passed value" do
|
29
|
+
subject.base_path('book')
|
30
|
+
subject.instance_variable_get(:@base_path).should == 'book'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "no value is passed" do
|
35
|
+
it "returns the value of @base_path" do
|
36
|
+
subject.instance_variable_set(:@base_path, 'book')
|
37
|
+
subject.base_path.should == 'book'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".parse" do
|
43
|
+
let(:document) { mock(Nokogiri::XML::Document) }
|
44
|
+
let(:node) { mock(Nokogiri::XML::Node) }
|
45
|
+
|
46
|
+
let(:nodes) { [node] }
|
47
|
+
|
48
|
+
let(:base_path) { 'book' }
|
49
|
+
let(:mappings) { [mapping] }
|
50
|
+
|
51
|
+
let(:mapping_value) { 'bar' }
|
52
|
+
|
53
|
+
before(:each) do
|
54
|
+
subject.stub(:base_path).and_return(base_path)
|
55
|
+
subject.stub(:new).and_return(book)
|
56
|
+
subject.stub(:mappings).and_return(mappings)
|
57
|
+
|
58
|
+
book.stub("#{mapping_name}=")
|
59
|
+
|
60
|
+
document.stub(:css).with(base_path).and_return(nodes)
|
61
|
+
|
62
|
+
mapping.stub(:name).and_return(mapping_name)
|
63
|
+
mapping.stub(:map).with(node).and_return(mapping_value)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "sets klass#method_name to the mapped node value" do
|
67
|
+
book.should_receive("#{mapping_name}=").with(mapping_value)
|
68
|
+
subject.parse(document)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "returns an array of klasses" do
|
72
|
+
subject.parse(document).should == [book]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe ".within" do
|
77
|
+
it "yields an instance of Muffins::MappingDecorator" do
|
78
|
+
subject.within :foo do |foo|
|
79
|
+
foo.should be_a(Muffins::MappingDecorator)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe ".map" do
|
85
|
+
|
86
|
+
let(:mappings) { [] }
|
87
|
+
|
88
|
+
before(:each) do
|
89
|
+
Muffins::Mapping.stub(:new)
|
90
|
+
.with(mapping_name, mapping_options)
|
91
|
+
.and_return(mapping)
|
92
|
+
|
93
|
+
|
94
|
+
book.stub(:respond_to?).with(mapping_name)
|
95
|
+
subject.stub(:new).and_return(book)
|
96
|
+
|
97
|
+
subject.stub(:mappings).and_return(mappings)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "adds a new instance of Muffins::Mapping to .mappings" do
|
101
|
+
mappings.should_receive(:<<).with(mapping)
|
102
|
+
subject.map mapping_name, mapping_options
|
103
|
+
end
|
104
|
+
|
105
|
+
context "the mapping name is not a defined method" do
|
106
|
+
before(:each) do
|
107
|
+
book.stub(:respond_to?).with(mapping_name).and_return(false)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "defines an attr_accessor for the mapping name" do
|
111
|
+
subject.should_receive(:attr_accessor).with(mapping_name)
|
112
|
+
subject.map mapping_name
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "the mapping name is already a defined method" do
|
117
|
+
before(:each) do
|
118
|
+
book.stub(:respond_to?).with(mapping_name).and_return(true)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "does not overwrite the existing method" do
|
122
|
+
subject.should_not_receive(:attr_accessor).with(mapping_name)
|
123
|
+
subject.map mapping_name
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rspec'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'activesupport'
|
6
|
+
|
7
|
+
require File.expand_path('../../lib/muffins', __FILE__)
|
8
|
+
|
9
|
+
def fixture_file(filename)
|
10
|
+
File.read(File.dirname(__FILE__) + "/fixtures/#{filename}")
|
11
|
+
end
|
12
|
+
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: muffins
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Closner
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-07-09 00:00:00.000000000 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
requirement: &15895880 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.3.10
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *15895880
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: nokogiri
|
28
|
+
requirement: &15895400 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *15895400
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: bundler
|
39
|
+
requirement: &15894760 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '1.0'
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *15894760
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: cucumber
|
50
|
+
requirement: &15894320 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.10'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *15894320
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rspec
|
61
|
+
requirement: &15893880 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ~>
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.6'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *15893880
|
70
|
+
description: An Object to XML/HTML mapping library using Nokogiri
|
71
|
+
email:
|
72
|
+
- ryan.closner@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- Gemfile
|
79
|
+
- Rakefile
|
80
|
+
- features/basic_parsing.feature
|
81
|
+
- features/nested_parsing.feature
|
82
|
+
- features/non_primitive_typing.feature
|
83
|
+
- features/specified_pathnames.feature
|
84
|
+
- features/specified_typing.feature
|
85
|
+
- features/step_definitions/application_steps.rb
|
86
|
+
- features/support/env.rb
|
87
|
+
- lib/muffins.rb
|
88
|
+
- lib/muffins/mapping.rb
|
89
|
+
- lib/muffins/mapping_decorator.rb
|
90
|
+
- lib/muffins/version.rb
|
91
|
+
- muffins.gemspec
|
92
|
+
- spec/mapping_spec.rb
|
93
|
+
- spec/muffins/mapping_decorator_spec.rb
|
94
|
+
- spec/muffins_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
has_rdoc: true
|
97
|
+
homepage: https://rubygems.org/gems/muffins
|
98
|
+
licenses: []
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project: muffins
|
117
|
+
rubygems_version: 1.6.2
|
118
|
+
signing_key:
|
119
|
+
specification_version: 3
|
120
|
+
summary: An Object to XML/HTML mapping library using Nokogiri
|
121
|
+
test_files:
|
122
|
+
- features/basic_parsing.feature
|
123
|
+
- features/nested_parsing.feature
|
124
|
+
- features/non_primitive_typing.feature
|
125
|
+
- features/specified_pathnames.feature
|
126
|
+
- features/specified_typing.feature
|
127
|
+
- features/step_definitions/application_steps.rb
|
128
|
+
- features/support/env.rb
|
129
|
+
- spec/mapping_spec.rb
|
130
|
+
- spec/muffins/mapping_decorator_spec.rb
|
131
|
+
- spec/muffins_spec.rb
|
132
|
+
- spec/spec_helper.rb
|