muffins 0.0.5 → 0.0.6
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/.rvmrc +1 -0
- data/README.md +15 -22
- data/Rakefile +1 -1
- data/lib/muffins.rb +13 -35
- data/lib/muffins/class_methods.rb +60 -0
- data/lib/muffins/document.rb +38 -0
- data/lib/muffins/mapping.rb +24 -59
- data/lib/muffins/mapping_parent.rb +27 -0
- data/lib/muffins/version.rb +1 -1
- data/muffins.gemspec +15 -17
- data/spec/fixtures/books.html +23 -0
- data/spec/fixtures/books.xml +11 -0
- data/spec/muffins/document_spec.rb +46 -0
- data/spec/muffins/mapping_parent_spec.rb +33 -0
- data/spec/muffins/mapping_spec.rb +29 -266
- data/spec/muffins_spec.rb +43 -103
- data/spec/spec_helper.rb +1 -4
- metadata +30 -59
- data/features/basic_parsing.feature +0 -33
- data/features/nested_parsing.feature +0 -37
- data/features/non_primitive_typing.feature +0 -36
- data/features/specified_pathnames.feature +0 -33
- data/features/specified_typing.feature +0 -33
- data/features/step_definitions/application_steps.rb +0 -52
- data/features/support/env.rb +0 -16
- data/lib/muffins/mapping_decorator.rb +0 -22
- data/spec/muffins/mapping_decorator_spec.rb +0 -46
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create 1.9.2@muffins
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
Muffins
|
2
2
|
========
|
3
3
|
|
4
|
+
[](http://travis-ci.org/rclosner/muffins)
|
5
|
+
|
4
6
|
A Nokogiri-based Object to XML/HTML mapping library.
|
5
7
|
|
6
8
|
Installation
|
@@ -38,44 +40,35 @@ Examples
|
|
38
40
|
|
39
41
|
Primary class:
|
40
42
|
|
43
|
+
```ruby
|
41
44
|
class Book
|
42
45
|
|
43
46
|
include Muffins
|
44
47
|
|
45
|
-
|
48
|
+
path 'book'
|
46
49
|
|
47
|
-
map :asin
|
50
|
+
map :asin, String
|
48
51
|
|
49
52
|
within :item_attributes do |attributes|
|
50
|
-
attributes.map :title
|
51
|
-
attributes.map :author
|
52
|
-
attributes.map :ean, :to => 'EAN'
|
53
|
-
attributes.map :release_date,
|
53
|
+
attributes.map :title, String
|
54
|
+
attributes.map :author, String, :collection => true
|
55
|
+
attributes.map :ean, String, :to => 'EAN'
|
56
|
+
attributes.map :release_date, Date
|
54
57
|
end
|
55
58
|
|
56
|
-
map :editorial_review, :within => :reviews
|
57
|
-
|
58
|
-
map :similar_products, :to => 'SimilarProducts', :type => Product
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
Associated class:
|
63
|
-
|
64
|
-
class Product
|
65
|
-
|
66
|
-
include Muffins
|
67
|
-
|
68
|
-
base_path 'similar_product'
|
69
|
-
|
70
|
-
map :title
|
71
|
-
map :ean, :to => 'EAN'
|
59
|
+
map :editorial_review, String, :within => :reviews
|
72
60
|
|
73
61
|
end
|
62
|
+
```
|
74
63
|
|
75
64
|
##Usage
|
76
65
|
|
66
|
+
```ruby
|
77
67
|
Book.parse(xml)
|
68
|
+
```
|
78
69
|
|
79
70
|
returns:
|
80
71
|
|
72
|
+
```ruby
|
81
73
|
[#<Book:0x233ebe8 @asin='1400079985', @title="War and Peace (Vintage Classics)", @author="Leo Tolstoy", @ean="9781400079988", @release_date=Tue, 02 Dec 2008, @editorial_review='ZOMG.', :similar_products=[#<Product:0x234ebe9 @title='Anna Karenina', @ean='9780143035008'>]>]
|
74
|
+
```
|
data/Rakefile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/muffins.rb
CHANGED
@@ -1,44 +1,22 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require "muffins/version"
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'virtus'
|
4
3
|
|
5
4
|
module Muffins
|
6
5
|
|
6
|
+
# @api private
|
7
7
|
def self.included(base)
|
8
|
+
base.send(:include, Virtus)
|
9
|
+
base.extend(Virtus::Options)
|
10
|
+
base.accept_options(:path)
|
8
11
|
base.extend(ClassMethods)
|
9
12
|
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
def base_path(bp = nil)
|
14
|
-
@base_path = bp if bp
|
15
|
-
@base_path
|
16
|
-
end
|
17
|
-
|
18
|
-
def parse(document, options = {})
|
19
|
-
document.css(base_path).collect do |node|
|
20
|
-
(new).tap do |object|
|
21
|
-
|
22
|
-
mappings.each do |mapping|
|
23
|
-
object.send("#{mapping.name}=", mapping.map(node))
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def within(path, &block)
|
31
|
-
yield MappingDecorator.new(path, self)
|
32
|
-
end
|
14
|
+
private_class_method :included
|
33
15
|
|
34
|
-
def map(symbol, options = {})
|
35
|
-
attr_accessor symbol unless new.respond_to?(symbol)
|
36
|
-
|
37
|
-
mappings << Mapping.new(symbol, options)
|
38
|
-
end
|
39
|
-
|
40
|
-
def mappings
|
41
|
-
@mappings ||= []
|
42
|
-
end
|
43
|
-
end
|
44
16
|
end
|
17
|
+
|
18
|
+
require "muffins/version"
|
19
|
+
require "muffins/class_methods"
|
20
|
+
require "muffins/mapping"
|
21
|
+
require "muffins/mapping_parent"
|
22
|
+
require "muffins/document"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Muffins
|
2
|
+
module ClassMethods
|
3
|
+
|
4
|
+
# @api public
|
5
|
+
def map(name, type, options = {})
|
6
|
+
options[:name] = name
|
7
|
+
options[:type] = type
|
8
|
+
|
9
|
+
mapping = Mapping.new(options)
|
10
|
+
|
11
|
+
add_mapping(mapping)
|
12
|
+
add_attribute(name, type)
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api public
|
18
|
+
def within(path, &block)
|
19
|
+
Muffins::MappingParent.new(:path => path, :klass => self.class).tap { |parent| yield parent }
|
20
|
+
end
|
21
|
+
|
22
|
+
# @api public
|
23
|
+
def parse(doc, options = {})
|
24
|
+
document = Muffins::Document.new(:body => doc)
|
25
|
+
|
26
|
+
if options[:single]
|
27
|
+
new_from_node(document.first(path))
|
28
|
+
else
|
29
|
+
document.map(path) { |node| new_from_node(node) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def mappings
|
37
|
+
@mappins ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
def add_mapping(mapping)
|
42
|
+
mappings << mapping
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
def add_attribute(name, type)
|
47
|
+
attribute(name, type)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def new_from_node(node)
|
52
|
+
new.tap do |obj|
|
53
|
+
mappings.each do |mapping|
|
54
|
+
obj.send("#{mapping.name}=", mapping.parse(node))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Muffins
|
2
|
+
class Document
|
3
|
+
|
4
|
+
include Virtus
|
5
|
+
|
6
|
+
attribute :body, Class, :reader => :protected
|
7
|
+
|
8
|
+
# @api public
|
9
|
+
def first(path)
|
10
|
+
node = body.at_css(path)
|
11
|
+
node.text if node
|
12
|
+
end
|
13
|
+
|
14
|
+
# @api public
|
15
|
+
def all(path)
|
16
|
+
nodes = body.css(path)
|
17
|
+
nodes.map(&:text)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api public
|
21
|
+
def map(path, &block)
|
22
|
+
all(path).map { |node| yield node }
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api public
|
26
|
+
def each(path, &block)
|
27
|
+
all(path).each { |node| yield node }
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def body=(body)
|
34
|
+
@body = Nokogiri::HTML(body)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/muffins/mapping.rb
CHANGED
@@ -1,79 +1,44 @@
|
|
1
|
-
class Boolean; end;
|
2
|
-
|
3
1
|
module Muffins
|
4
2
|
class Mapping
|
5
3
|
|
6
|
-
|
4
|
+
include Virtus
|
7
5
|
|
8
|
-
|
6
|
+
attribute :name, Symbol
|
7
|
+
attribute :type, Class
|
8
|
+
attribute :to, String
|
9
|
+
attribute :within, String
|
10
|
+
attribute :collection, Boolean
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
12
|
+
# @api public
|
13
|
+
def parse(doc)
|
14
|
+
document = Muffins::Document.new(:body => doc)
|
14
15
|
|
15
|
-
def map(document)
|
16
16
|
if collection?
|
17
|
-
(
|
18
|
-
document.css(path).each do |node|
|
19
|
-
items << typecast(node)
|
20
|
-
end
|
21
|
-
end
|
17
|
+
document.map(absolute_path) { |node| coerce(node) }
|
22
18
|
else
|
23
|
-
|
19
|
+
coerce(document.first(absolute_path))
|
24
20
|
end
|
25
21
|
end
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
unless options[:within].blank?
|
31
|
-
path = "#{options[:within]} > #{path}"
|
32
|
-
end
|
33
|
-
|
34
|
-
@path ||= path
|
23
|
+
# @api public
|
24
|
+
def coerce(value)
|
25
|
+
Virtus::Coercion[value.class].send(coercion_method, value)
|
35
26
|
end
|
36
27
|
|
37
|
-
|
38
|
-
|
28
|
+
# @api public
|
29
|
+
def coercion_method
|
30
|
+
Virtus::Attribute.determine_type(type).coercion_method
|
39
31
|
end
|
40
32
|
|
41
|
-
|
42
|
-
|
33
|
+
# @api public
|
34
|
+
def absolute_path
|
35
|
+
within ? [ within, relative_path ].compact.join(" > ") : relative_path
|
43
36
|
end
|
44
37
|
|
45
|
-
|
46
|
-
|
38
|
+
# @api public
|
39
|
+
def relative_path
|
40
|
+
to || name
|
47
41
|
end
|
48
42
|
|
49
|
-
def typecast(node)
|
50
|
-
value = node.try(:text)
|
51
|
-
|
52
|
-
return value if value.blank? || constant.blank?
|
53
|
-
|
54
|
-
if primitive?
|
55
|
-
case constant
|
56
|
-
when 'String'
|
57
|
-
value.to_s
|
58
|
-
when 'Float'
|
59
|
-
value[/[-+]?[0-9]*\.?[0-9]+/].to_f
|
60
|
-
when 'Integer'
|
61
|
-
value[/[\d]+/].to_i
|
62
|
-
when 'Time'
|
63
|
-
Time.parse(value) rescue Time.at(value.to_i)
|
64
|
-
when 'Date'
|
65
|
-
Date.parse(value)
|
66
|
-
when 'DateTime'
|
67
|
-
DateTime.parse(value)
|
68
|
-
when 'Boolean'
|
69
|
-
['true', 't', '1'].include?(value.to_s.downcase)
|
70
|
-
else
|
71
|
-
value
|
72
|
-
end
|
73
|
-
|
74
|
-
else
|
75
|
-
constant.constantize.parse(node)
|
76
|
-
end
|
77
|
-
end
|
78
43
|
end
|
79
|
-
end
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Muffins
|
2
|
+
class MappingParent
|
3
|
+
|
4
|
+
include Virtus
|
5
|
+
|
6
|
+
attribute :path, String
|
7
|
+
attribute :klass, Object
|
8
|
+
|
9
|
+
# @api public
|
10
|
+
def map(name, type, options = {})
|
11
|
+
set_options(options).tap do |options|
|
12
|
+
klass.map(name, type, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def set_options(options)
|
22
|
+
options[:within] = [ path, options[:within] ].compact.join(" > ")
|
23
|
+
options
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/muffins/version.rb
CHANGED
data/muffins.gemspec
CHANGED
@@ -6,31 +6,29 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "muffins"
|
7
7
|
s.version = Muffins::VERSION
|
8
8
|
s.authors = ["Ryan Closner"]
|
9
|
-
s.email = ["ryan
|
10
|
-
s.homepage = "
|
11
|
-
s.summary = %q{An Object to XML/HTML mapping library
|
12
|
-
s.description = %q{An Object to XML/HTML mapping library using Nokogiri}
|
9
|
+
s.email = ["ryan@ryanclosner.com"]
|
10
|
+
s.homepage = "http://rubygems.org/gems/muffins"
|
11
|
+
s.summary = %q{An Object to XML/HTML mapping library.}
|
12
|
+
s.description = %q{An Object to XML/HTML mapping library using Virtus and Nokogiri}
|
13
13
|
|
14
14
|
s.rubyforge_project = "muffins"
|
15
15
|
|
16
|
-
{
|
17
|
-
'
|
18
|
-
'
|
19
|
-
}
|
20
|
-
s.add_runtime_dependency lib, version
|
21
|
-
end
|
16
|
+
runtime_dependencies = {
|
17
|
+
'nokogiri' => '~> 1.5.0',
|
18
|
+
'virtus' => '~> 0.0.9'
|
19
|
+
}
|
22
20
|
|
21
|
+
runtime_dependencies.each {|lib, version| s.add_runtime_dependency(lib, version) }
|
23
22
|
|
24
|
-
{
|
25
|
-
'
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
s.add_development_dependency lib, version
|
30
|
-
end
|
23
|
+
development_dependencies = {
|
24
|
+
'rspec' => '~> 2.7.0'
|
25
|
+
}
|
26
|
+
|
27
|
+
development_dependencies.each {|lib, version| s.add_development_dependency(lib, version) }
|
31
28
|
|
32
29
|
s.files = `git ls-files`.split("\n")
|
33
30
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
34
31
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
35
32
|
s.require_paths = ["lib"]
|
33
|
+
|
36
34
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
2
|
+
"http://www.w3.org/TR/html4/strict.dtd">
|
3
|
+
|
4
|
+
<html lang="en">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<div id="books">
|
10
|
+
|
11
|
+
<div class="book">
|
12
|
+
<span class="isbn">1400079985</span>
|
13
|
+
<span class="title">War and Peace (Vintage Classics)</span>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div class="book">
|
17
|
+
<span class="isbn">1143035008</span>
|
18
|
+
<span class="title">Anna Karenina (Oprah's Book Club)</span>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
</div>
|
22
|
+
</body>
|
23
|
+
</html>
|