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 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
+ [![travis](https://secure.travis-ci.org/rclosner/muffins.png)](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
- base_path 'book'
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, :type => 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 'bundler/gem_tasks'
1
+ require "bundler/gem_tasks"
data/lib/muffins.rb CHANGED
@@ -1,44 +1,22 @@
1
- require "muffins/mapping"
2
- require "muffins/mapping_decorator"
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
- module ClassMethods
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
@@ -1,79 +1,44 @@
1
- class Boolean; end;
2
-
3
1
  module Muffins
4
2
  class Mapping
5
3
 
6
- PRIMITIVE_TYPES = %w(String Float Time Date DateTime Integer Boolean)
4
+ include Virtus
7
5
 
8
- attr_reader :name, :options
6
+ attribute :name, Symbol
7
+ attribute :type, Class
8
+ attribute :to, String
9
+ attribute :within, String
10
+ attribute :collection, Boolean
9
11
 
10
- def initialize(name, options = {})
11
- @name = name
12
- @options = options
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
- ([]).tap do |items|
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
- typecast document.at_css(path)
19
+ coerce(document.first(absolute_path))
24
20
  end
25
21
  end
26
22
 
27
- def path
28
- path = options[:to].present? ? options[:to] : name
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
- def constant
38
- @constant ||= "#{options[:type]}"
28
+ # @api public
29
+ def coercion_method
30
+ Virtus::Attribute.determine_type(type).coercion_method
39
31
  end
40
32
 
41
- def collection?
42
- !!options[:collection]
33
+ # @api public
34
+ def absolute_path
35
+ within ? [ within, relative_path ].compact.join(" > ") : relative_path
43
36
  end
44
37
 
45
- def primitive?
46
- PRIMITIVE_TYPES.include?(constant)
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
@@ -1,3 +1,3 @@
1
1
  module Muffins
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
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.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}
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
- 'activesupport' => '>= 2.3.10',
18
- 'nokogiri' => '~> 1.4'
19
- }.each do |lib, version|
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
- '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
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>
@@ -0,0 +1,11 @@
1
+ <?xml version=\"1.0\" encoding=\"UTF-8\"?>
2
+ <books>
3
+ <book>
4
+ <isbn>1400079985</isbn>
5
+ <title>War and Peace (Vintage Classics)</title>
6
+ </book>
7
+ <book>
8
+ <isbn>1143035008</isbn>
9
+ <title>Anna Karenina (Oprah's Book Club)</title>
10
+ </book>
11
+ </books>