grumpymapper 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in grumpy_mapper.gemspec
4
+ gemspec
data/LICENSE ADDED
File without changes
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ Introduction to GrumpyMapper [![Build Status](https://secure.travis-ci.org/marcinwyszynski/grumpymapper.png?branch=master)](http://travis-ci.org/marcinwyszynski/grumpymapper)
2
+ ------------------------
3
+
4
+ GrumpyMapper is a simple alternative to the excellent [HappyMapper](https://github.com/jnunemaker/happymapper/) gem. Unlike HappyMapper it only works one way - parsing XML into a Ruby object. It is also more difficult to use as it assumes some knowledge of XPath. I did create the gem for myself but as a reference for my future self and perhaps a lost visitor to this repository below is some documentation.
5
+
6
+ Using GrumpyMapper
7
+ ------------------
8
+
9
+ GrumpyMapper is a module which you include in your classes. Once included, it gives access to three class macros ("tag", "has_one" and "has_many") as well as one class method ("parse"). Here is how you'd use it:
10
+
11
+ ```ruby
12
+ class Venue
13
+ include GrumpyMapper
14
+ tag "//venue"
15
+ has_one :name, ".//name/text()", String
16
+ has_one :latitude, ".//lat/text()", Float
17
+ has_one :longitude, ".//long/text()", Float
18
+ end
19
+
20
+
21
+ class Event
22
+ include GrumpyMapper
23
+ tag "//event"
24
+ has_one :name, ".//title/text()", String
25
+ has_one :cancelled, ".//cancelled/text()", Boolean
26
+ has_one :venue, ".//venue", Venue
27
+ has_one :start_date, ".//startDate/text()", DateTime
28
+ has_many :artists, ".//artists/artist/text()", String
29
+ end
30
+
31
+ events = Event::parse(some_xml_content) # ... events is an Array of Event objects
32
+ ````
33
+
34
+ For a piece of XML this is supposed to parse, please see fixtures/lastfm.xml or the unit test for this library. In any case, the "tag" class macro defines what is the parent tag for your mapped object. Both attributes and children are then defined using XPath expressions in "has_one" and "has_many" class macros. Each of these macros takes at least three arguments - the attribute to map to the value to, the XPath expression (can be an attribute as well as an element) to search by and the expected class of the element found. A number of simple classes is supported - that is String, Integer, Float, Date and DateTime. A Boolean class is defined inside the GrumpyMapper as Ruby does not have one built-in. If you specify any other class, it should be one that responds to the "parse" class method - preferably one that also includes the GrumpyMapper module. One slight difference between "has_one" and "has_many" class macros is that the first allows you to specify a default value as the fourth argument. It will be used when the XPath expression finds no matches. Some expected classes like String, Integer and Float have a default default - "", 0 and 0.0 respectively. For others it is a Ruby native nil value. The "has_many" class macro always has an empty Array as it's default.
35
+
36
+ Parting words
37
+ -------------
38
+
39
+ For more inspiration on how to use the library use the unit test, source code and make sure to have your favorite XPath tutorial close at hand. Enjoy!
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake"
3
+ require "rake/testtask"
4
+
5
+ task :default => [ :unittests ]
6
+
7
+ Rake::TestTask.new("unittests") do |t|
8
+ t.pattern = "test/*_test.rb"
9
+ t.verbose = true
10
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "grumpymapper/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "grumpymapper"
7
+ s.version = GrumpyMapper::VERSION
8
+ s.authors = ["Marcin Wyszynski"]
9
+ s.email = ["marcin.pixie@gmail.com"]
10
+ s.homepage = "http://github.com/marcinwyszynski/grumpymapper"
11
+ s.summary = %q{More versatile, XPath-based one-way version of HappyMapper}
12
+ s.description = %q{More versatile, XPath-based one-way version of HappyMapper}
13
+
14
+ s.rubyforge_project = "grumpymapper"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency("nokogiri")
22
+
23
+ end
@@ -0,0 +1,3 @@
1
+ module GrumpyMapper
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,90 @@
1
+ require "date"
2
+ require "nokogiri"
3
+ require "grumpymapper/version"
4
+
5
+ module GrumpyMapper
6
+
7
+ class Boolean; end
8
+
9
+ Property = Struct::new(:xpath, :klass, :multi, :default)
10
+
11
+ def self.included(base)
12
+ base.instance_variable_set("@tag", nil)
13
+ base.instance_variable_set("@attributes", {})
14
+ base.extend ClassMethods
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def tag(xpath)
20
+ @tag = xpath
21
+ end
22
+
23
+ def has_one(name, xpath, klass, default=nil)
24
+ attr_accessor name
25
+ if default.nil?
26
+ default = if klass.eql?(String)
27
+ ""
28
+ elsif klass.eql?(Integer)
29
+ 0
30
+ elsif klass.eql?(Float)
31
+ 0.0
32
+ else
33
+ nil
34
+ end
35
+ end
36
+ @attributes[name] = Property::new(xpath, klass, false, default)
37
+ end
38
+
39
+ def has_many(name, xpath, klass)
40
+ attr_accessor name
41
+ @attributes[name] = Property::new(xpath, klass, true, [])
42
+ end
43
+
44
+ def parse(xml)
45
+ result = []
46
+ Nokogiri::XML(xml).xpath(@tag).each do |tag|
47
+ entity = new
48
+ @attributes.each do |key,property|
49
+ matches = tag.xpath(property.xpath)
50
+ if matches.size > 0
51
+ matches = if property.klass.eql?(Integer)
52
+ matches.map(&:text).map(&:to_i)
53
+ elsif property.klass.eql?(Float)
54
+ matches.map(&:text).map(&:to_f)
55
+ elsif property.klass.eql?(Boolean)
56
+ matches.map(&:text).map do |m|
57
+ %w(true t 1).include?(m) ? true : false
58
+ end
59
+ elsif property.klass.eql?(Date)
60
+ matches.map(&:text).map do |m|
61
+ Date::parse(m)
62
+ end
63
+ elsif property.klass.eql?(DateTime)
64
+ matches.map(&:text).map do |m|
65
+ DateTime::parse(m)
66
+ end
67
+ elsif property.klass.eql?(String)
68
+ matches.map(&:text)
69
+ else
70
+ matches.map do |m|
71
+ property.klass.parse(m.to_s).first
72
+ end
73
+ end
74
+ if property.multi
75
+ entity.send(:"#{key}=", matches)
76
+ else
77
+ entity.send(:"#{key}=", matches.first)
78
+ end
79
+ else
80
+ entity.send(:"#{key}=", property.default)
81
+ end # if matches.size > 0
82
+ end # @attributes.each
83
+ result << entity
84
+ end # each tag
85
+ return result
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,89 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <lfm status="ok">
3
+ <events artist="Radiohead" festivalsonly="0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" page="1" perPage="50" totalPages="1" total="24">
4
+ <event xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" >
5
+ <id>3109094</id>
6
+ <title>Fuji Rock Festival 2012</title>
7
+ <artists>
8
+ <artist>Radiohead</artist>
9
+ <artist>ケンイシイ</artist>
10
+ <headliner>Radiohead</headliner>
11
+ </artists>
12
+ <venue>
13
+ <id>8802452</id>
14
+ <name>Naeba Ski Resort</name>
15
+ <location>
16
+ <city>Yuzawa</city>
17
+ <country>Japan</country>
18
+ <street></street>
19
+ <postalcode></postalcode>
20
+ <geo:point>
21
+ <geo:lat>36.9333333</geo:lat>
22
+ <geo:long>138.8166667</geo:long>
23
+ </geo:point>
24
+ </location>
25
+ <url>http://www.last.fm/venue/8802452+Naeba+Ski+Resort</url>
26
+ <website></website>
27
+ <phonenumber></phonenumber>
28
+ </venue>
29
+ <startDate>Fri, 27 Jul 2012 18:28:01</startDate>
30
+ <endDate>Sun, 29 Jul 2012 18:28:01</endDate>
31
+ <description>A detailed description in Japanese.</description>
32
+ <image size="small">http://userserve-ak.last.fm/serve/34/76106242.jpg</image>
33
+ <image size="medium">http://userserve-ak.last.fm/serve/64/76106242.jpg</image>
34
+ <image size="large">http://userserve-ak.last.fm/serve/126/76106242.jpg</image>
35
+ <image size="extralarge">http://userserve-ak.last.fm/serve/252/76106242.jpg</image>
36
+ <attendance>557</attendance>
37
+ <reviews>0</reviews>
38
+ <tag>lastfm:event=3109094</tag>
39
+ <url>http://www.last.fm/festival/3109094+Fuji+Rock+Festival+2012</url>
40
+ <website>http://www.fujirockfestival.com/</website>
41
+ <tickets> </tickets>
42
+ <cancelled>0</cancelled>
43
+ </event>
44
+ <event xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" >
45
+ <id>3191725</id>
46
+ <title>Jisan Valley Rock Festival</title>
47
+ <artists>
48
+ <artist>Radiohead</artist>
49
+ <artist>Owl City</artist>
50
+ <headliner>Radiohead</headliner>
51
+ </artists>
52
+ <venue>
53
+ <id>10240273</id>
54
+ <name>Jisan Valley Ski Resort</name>
55
+ <location>
56
+ <city>Icheon</city>
57
+ <country>Korea, Republic of</country>
58
+ <street></street>
59
+ <postalcode></postalcode>
60
+ <geo:point>
61
+ <geo:lat></geo:lat>
62
+ <geo:long></geo:long>
63
+ </geo:point>
64
+ </location>
65
+ <url>http://www.last.fm/venue/10240273+Jisan+Valley+Ski+Resort</url>
66
+ <website></website>
67
+ <phonenumber></phonenumber>
68
+ <image size="small"></image>
69
+ </venue>
70
+ <startDate>Fri, 27 Jul 2012 19:21:01</startDate>
71
+ <endDate>Sun, 29 Jul 2012 19:21:01</endDate>
72
+ <description>Something in Korean.</description>
73
+ <image size="small">http://userserve-ak.last.fm/serve/34/79922351.jpg</image>
74
+ <image size="medium">http://userserve-ak.last.fm/serve/64/79922351.jpg</image>
75
+ <image size="large">http://userserve-ak.last.fm/serve/126/79922351.jpg</image>
76
+ <image size="extralarge">http://userserve-ak.last.fm/serve/252/79922351.jpg</image>
77
+ <attendance>103</attendance>
78
+ <reviews>0</reviews>
79
+ <tag>lastfm:event=3191725</tag>
80
+ <url>http://www.last.fm/festival/3191725+Jisan+Valley+Rock+Festival</url>
81
+ <website>http://valleyrockfestival.mnet.com/2012/index.asp</website>
82
+ <tickets></tickets>
83
+ <cancelled>1</cancelled>
84
+ <tags>
85
+ <tag>rock</tag>
86
+ </tags>
87
+ </event>
88
+ </events>
89
+ </lfm>
@@ -0,0 +1,94 @@
1
+ require "test/unit"
2
+ require "grumpymapper"
3
+ require "pry"
4
+
5
+
6
+ class Venue
7
+ include GrumpyMapper
8
+ tag "//venue"
9
+ has_one :name, ".//name/text()", String
10
+ has_one :lastfm_id, ".//id/text()", Integer
11
+ has_one :latitude, ".//lat/text()", Float
12
+ has_one :longitude, ".//long/text()", Float
13
+ has_one :city, ".//city/text()", String
14
+ end
15
+
16
+
17
+ class Event
18
+ include GrumpyMapper
19
+ tag "//event"
20
+ has_one :name, ".//title/text()", String
21
+ has_one :lastfm_id, ".//id/text()", Integer
22
+ has_one :cancelled, ".//cancelled/text()", Boolean
23
+ has_one :description, ".//description/text()", String
24
+ has_one :venue, ".//venue", Venue
25
+ has_one :start_date, ".//startDate/text()", DateTime
26
+ has_many :artists, ".//artists/artist/text()", String
27
+ end
28
+
29
+
30
+ class LastFMTest < Test::Unit::TestCase
31
+
32
+ def setup
33
+ @content = File::open(File::join(File::dirname(__FILE__),
34
+ "fixtures", "lastfm.xml")).read()
35
+ end
36
+
37
+ def test_parsing_correctness
38
+ # Check for parsing correctness
39
+ events = nil
40
+ assert_nothing_raised do
41
+ events = Event::parse(@content)
42
+ end
43
+ assert_equal 2, events.size, "Should have parsed two events"
44
+ end
45
+
46
+ def test_boolean_parsing
47
+ events = Event::parse(@content)
48
+ assert !events.first.cancelled, "First event should not be cancelled"
49
+ assert events.last.cancelled, "Second event should be cancelled"
50
+ end
51
+
52
+ def test_integer_parsing
53
+ events = Event::parse(@content)
54
+ assert events.first.lastfm_id.is_a?(Integer), "LastFM ID is not an integer"
55
+ assert_equal 3109094, events.first.lastfm_id, "Wrong LastFM ID"
56
+ end
57
+
58
+ def test_datetime_parsing
59
+ events = Event::parse(@content)
60
+ assert events.first.start_date.is_a?(DateTime), "Start date not a DateTime object"
61
+ assert_equal 2012, events.first.start_date.year, "Wrong year parsed"
62
+ assert_equal 7, events.first.start_date.month, "Wrong month parsed"
63
+ assert_equal 27, events.first.start_date.day, "Wrong day parsed"
64
+ assert events.first.start_date < events.last.start_date
65
+ end
66
+
67
+ def test_single_string_parsing
68
+ events = Event::parse(@content)
69
+ assert events.first.name.is_a?(String), "Event name not a String"
70
+ assert_equal "Fuji Rock Festival 2012", events.first.name, "Wrong name parsed"
71
+ end
72
+
73
+ def test_multiple_string_parsing
74
+ events = Event::parse(@content)
75
+ assert events.first.artists.is_a?(Array), "Artist list not an Array"
76
+ assert events.first.artists.first.is_a?(String), "First artist name not a String"
77
+ assert_equal "Radiohead", events.first.artists.first, "Wrong name parsed"
78
+ end
79
+
80
+ def test_single_object_parsing
81
+ events = Event::parse(@content)
82
+ venue = events.first.venue
83
+ assert venue.is_a?(Venue), "Not a Venue object"
84
+ assert_equal "Naeba Ski Resort", venue.name, "Wrong Venue name"
85
+ end
86
+
87
+ def test_float_parsing
88
+ venue = Event::parse(@content).first.venue
89
+ assert venue.latitude.is_a?(Float), "Venue latitude not a Float"
90
+ assert venue.longitude.is_a?(Float), "Venue longitude not a Float"
91
+ assert_equal 36.9333333, venue.latitude, "Wrong latitude parsed"
92
+ end
93
+
94
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grumpymapper
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.2
6
+ platform: ruby
7
+ authors:
8
+ - Marcin Wyszynski
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-07-28 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: nokogiri
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ description: More versatile, XPath-based one-way version of HappyMapper
27
+ email:
28
+ - marcin.pixie@gmail.com
29
+ executables: []
30
+
31
+ extensions: []
32
+
33
+ extra_rdoc_files: []
34
+
35
+ files:
36
+ - .gitignore
37
+ - .travis.yml
38
+ - Gemfile
39
+ - LICENSE
40
+ - README.md
41
+ - Rakefile
42
+ - grumpymapper.gemspec
43
+ - lib/grumpymapper.rb
44
+ - lib/grumpymapper/version.rb
45
+ - test/fixtures/lastfm.xml
46
+ - test/lastfm_test.rb
47
+ homepage: http://github.com/marcinwyszynski/grumpymapper
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project: grumpymapper
70
+ rubygems_version: 1.8.15
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: More versatile, XPath-based one-way version of HappyMapper
74
+ test_files:
75
+ - test/fixtures/lastfm.xml
76
+ - test/lastfm_test.rb