muffins 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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>