robmitch-happymapper 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/History ADDED
@@ -0,0 +1,59 @@
1
+ == 0.2.5
2
+ * 1 minor tweak
3
+ * Classes can now be strings instead of constants so you don't have to worry about class definition order (this was all for technicalpickles, enjoy!)
4
+
5
+ == 0.2.4
6
+ * 1 minor tweak
7
+ * Added a patch that allows even crazy namespaces to work
8
+
9
+ == 0.2.3
10
+ * 1 minor tweak
11
+ * bumped the version of libxml-ruby to 1.1.3
12
+
13
+ == 0.2.2
14
+ * 2 minor tweaks
15
+ * removed GC.start (libxml recommended this) as setting nodes to nil should be enough, specs run 3-4x faster (Brandon Keepers)
16
+ * renamed get_tag_name to tag_name (Brandon Keepers)
17
+ * removed libxml helpers as they are no longer needed
18
+
19
+ == 0.2.1
20
+ * 1 minor fix, 3 major enhancements
21
+ * fixed warnings about using XML::Parser (mojodna)
22
+ * Improved namespace support, now handles multiple namespaces and allows namespaces to be set item wide or on a per element basis (mojodna)
23
+ * Auto detect root nodes (mojodna)
24
+ * Type coercion (mojodna)
25
+
26
+ == 0.2.0
27
+ * 1 major enhancement, 2 minor ehancements
28
+ * Automatic handling of namespaces (part by Robert Lowrey and rest by John Nunemaker)
29
+ * Added :root option to tag method. This allows setting an object as the root element, which sets xpath to use / and sets single to true
30
+ * Now defaulting tag names for classes in modules to last constant downcased
31
+
32
+ == 0.1.7 2009-01-29
33
+ * 1 minor enhancement
34
+ * Support dashes in elements (Josh Nichols)
35
+
36
+ == 0.1.6 2009-01-17
37
+ * 1 minor enhancement:
38
+ * added support for nested collection elements (Justin Marney)
39
+
40
+ == 0.1.5 2009-01-05
41
+ * 1 major enhancement:
42
+ * Updated to latest version of libxml-ruby (lightningdb)
43
+
44
+ == 0.1.4 2009-01-05
45
+ * 1 major enhancement:
46
+ * Fixed parsing when the object is the root node. (Garret Alfert)
47
+
48
+ == 0.1.3 2008-12-31
49
+ * 1 major enhancement:
50
+ * Added parsing of attributes of elements that are also mapped, see current_weather.rb for example (jeremyf)
51
+
52
+ == 0.1.2 2008-12-12
53
+ * 1 major enhancement:
54
+ * Fixed that :deep only worked for first item (dvrensk)
55
+
56
+ == 0.1.0 2008-11-16
57
+
58
+ * 1 major enhancement:
59
+ * Initial release
data/License ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,38 @@
1
+ examples/amazon.rb
2
+ examples/current_weather.rb
3
+ examples/dashed_elements.rb
4
+ examples/post.rb
5
+ examples/twitter.rb
6
+ happymapper.gemspec
7
+ History
8
+ lib/happymapper/attribute.rb
9
+ lib/happymapper/element.rb
10
+ lib/happymapper/item.rb
11
+ lib/happymapper/version.rb
12
+ lib/happymapper.rb
13
+ License
14
+ Manifest
15
+ Rakefile
16
+ README
17
+ spec/fixtures/address.xml
18
+ spec/fixtures/analytics.xml
19
+ spec/fixtures/commit.xml
20
+ spec/fixtures/current_weather.xml
21
+ spec/fixtures/family_tree.xml
22
+ spec/fixtures/multiple_namespaces.xml
23
+ spec/fixtures/pita.xml
24
+ spec/fixtures/posts.xml
25
+ spec/fixtures/product_default_namespace.xml
26
+ spec/fixtures/product_no_namespace.xml
27
+ spec/fixtures/product_single_namespace.xml
28
+ spec/fixtures/radar.xml
29
+ spec/fixtures/statuses.xml
30
+ spec/happymapper_attribute_spec.rb
31
+ spec/happymapper_element_spec.rb
32
+ spec/happymapper_item_spec.rb
33
+ spec/happymapper_spec.rb
34
+ spec/spec.opts
35
+ spec/spec_helper.rb
36
+ TODO
37
+ website/css/common.css
38
+ website/index.html
data/README ADDED
@@ -0,0 +1,61 @@
1
+ = happymapper
2
+
3
+ == DESCRIPTION:
4
+
5
+ Object to xml mapping library. I have included examples to help get you going. The specs
6
+ should also point you in the right direction.
7
+
8
+ == FEATURES:
9
+
10
+ * Easy to define xml attributes and elements for an object
11
+ * Fast because it uses libxml-ruby under the hood
12
+ * Automatic conversion of xml to defined objects
13
+
14
+ == EXAMPLES:
15
+
16
+ Here is a simple example that maps Twitter statuses and users.
17
+
18
+ class User
19
+ include HappyMapper
20
+
21
+ element :id, Integer
22
+ element :name, String
23
+ element :screen_name, String
24
+ element :location, String
25
+ element :description, String
26
+ element :profile_image_url, String
27
+ element :url, String
28
+ element :protected, Boolean
29
+ element :followers_count, Integer
30
+ end
31
+
32
+ class Status
33
+ include HappyMapper
34
+
35
+ element :id, Integer
36
+ element :text, String
37
+ element :created_at, Time
38
+ element :source, String
39
+ element :truncated, Boolean
40
+ element :in_reply_to_status_id, Integer
41
+ element :in_reply_to_user_id, Integer
42
+ element :favorited, Boolean
43
+ has_one :user, User
44
+ end
45
+
46
+ See examples directory in the gem for more examples.
47
+
48
+ http://github.com/jnunemaker/happymapper/tree/master/examples/
49
+
50
+ == INSTALL:
51
+
52
+ * sudo gem install jnunemaker-happymapper -s http://gems.github.com
53
+ * sudo gem install happymapper (when rubyforge approves and i release there)
54
+
55
+ == TICKETS:
56
+
57
+ http://github.com/jnunemaker/happymapper/issues/
58
+
59
+ == DOCS:
60
+
61
+ http://rdoc.info/projects/jnunemaker/happymapper
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ ProjectName = 'happymapper'
2
+ WebsitePath = "jnunemaker@rubyforge.org:/var/www/gforge-projects/#{ProjectName}"
3
+
4
+ require 'rubygems'
5
+ require 'rake'
6
+ require 'echoe'
7
+ require 'spec/rake/spectask'
8
+ require "lib/#{ProjectName}/version"
9
+
10
+ Echoe.new(ProjectName, HappyMapper::Version) do |p|
11
+ p.description = "object to xml mapping library"
12
+ p.install_message = "May you have many happy mappings!"
13
+ p.url = "http://#{ProjectName}.rubyforge.org"
14
+ p.author = "John Nunemaker"
15
+ p.email = "nunemaker@gmail.com"
16
+ p.extra_deps = [['libxml-ruby', '= 1.1.3']]
17
+ p.need_tar_gz = false
18
+ p.docs_host = WebsitePath
19
+ end
20
+
21
+ desc 'Upload website files to rubyforge'
22
+ task :website do
23
+ sh %{rsync -av website/ #{WebsitePath}}
24
+ Rake::Task['website_docs'].invoke
25
+ end
26
+
27
+ task :website_docs do
28
+ Rake::Task['redocs'].invoke
29
+ sh %{rsync -av doc/ #{WebsitePath}/docs}
30
+ end
31
+
32
+ desc 'Preps the gem for a new release'
33
+ task :prepare do
34
+ %w[manifest build_gemspec].each do |task|
35
+ Rake::Task[task].invoke
36
+ end
37
+ end
38
+
39
+ Rake::Task[:default].prerequisites.clear
40
+ task :default => :spec
41
+ Spec::Rake::SpecTask.new do |t|
42
+ t.spec_files = FileList["spec/**/*_spec.rb"]
43
+ end
data/TODO ADDED
File without changes
@@ -0,0 +1,34 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/pita.xml')
5
+
6
+ # The document `pita.xml` contains both a default namespace and the 'georss'
7
+ # namespace (for the 'point' element).
8
+ module PITA
9
+ class Item
10
+ include HappyMapper
11
+
12
+ tag 'Item' # if you put class in module you need tag
13
+ element :asin, String, :tag => 'ASIN'
14
+ element :detail_page_url, String, :tag => 'DetailPageURL'
15
+ element :manufacturer, String, :tag => 'Manufacturer', :deep => true
16
+ # this is the only element that exists in a different namespace, so it
17
+ # must be explicitly specified
18
+ element :point, String, :tag => 'point', :namespace => 'georss'
19
+ end
20
+
21
+ class Items
22
+ include HappyMapper
23
+
24
+ tag 'Items' # if you put class in module you need tag
25
+ element :total_results, Integer, :tag => 'TotalResults'
26
+ element :total_pages, Integer, :tag => 'TotalPages'
27
+ has_many :items, Item
28
+ end
29
+ end
30
+
31
+ item = PITA::Items.parse(file_contents, :single => true)
32
+ item.items.each do |i|
33
+ puts i.asin, i.detail_page_url, i.manufacturer, ''
34
+ end
@@ -0,0 +1,21 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/current_weather.xml')
5
+
6
+ class CurrentWeather
7
+ include HappyMapper
8
+
9
+ tag 'ob'
10
+ namespace 'aws'
11
+ element :temperature, Integer, :tag => 'temp'
12
+ element :feels_like, Integer, :tag => 'feels-like'
13
+ element :current_condition, String, :tag => 'current-condition', :attributes => {:icon => String}
14
+ end
15
+
16
+ CurrentWeather.parse(file_contents).each do |current_weather|
17
+ puts "temperature: #{current_weather.temperature}"
18
+ puts "feels_like: #{current_weather.feels_like}"
19
+ puts "current_condition: #{current_weather.current_condition}"
20
+ puts "current_condition.icon: #{current_weather.current_condition.icon}"
21
+ end
@@ -0,0 +1,20 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/commit.xml')
5
+
6
+ module GitHub
7
+ class Commit
8
+ include HappyMapper
9
+
10
+ tag "commit"
11
+ element :url, String
12
+ element :tree, String
13
+ element :message, String
14
+ element :id, String
15
+ element :'committed-date', Date
16
+ end
17
+ end
18
+
19
+ commit = GitHub::Commit.parse(file_contents)
20
+ puts commit.committed_date, commit.url, commit.id
data/examples/post.rb ADDED
@@ -0,0 +1,19 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/posts.xml')
5
+
6
+ class Post
7
+ include HappyMapper
8
+
9
+ attribute :href, String
10
+ attribute :hash, String
11
+ attribute :description, String
12
+ attribute :tag, String
13
+ attribute :time, DateTime
14
+ attribute :others, Integer
15
+ attribute :extended, String
16
+ end
17
+
18
+ posts = Post.parse(file_contents)
19
+ posts.each { |post| puts post.description, post.href, post.extended, '' }
@@ -0,0 +1,37 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'happymapper')
3
+
4
+ file_contents = File.read(dir + '/../spec/fixtures/statuses.xml')
5
+
6
+ class User
7
+ include HappyMapper
8
+
9
+ element :id, Integer
10
+ element :name, String
11
+ element :screen_name, String
12
+ element :location, String
13
+ element :description, String
14
+ element :profile_image_url, String
15
+ element :url, String
16
+ element :protected, Boolean
17
+ element :followers_count, Integer
18
+ end
19
+
20
+ class Status
21
+ include HappyMapper
22
+
23
+ element :id, Integer
24
+ element :text, String
25
+ element :created_at, Time
26
+ element :source, String
27
+ element :truncated, Boolean
28
+ element :in_reply_to_status_id, Integer
29
+ element :in_reply_to_user_id, Integer
30
+ element :favorited, Boolean
31
+ has_one :user, User
32
+ end
33
+
34
+ statuses = Status.parse(file_contents)
35
+ statuses.each do |status|
36
+ puts status.user.name, status.user.screen_name, status.text, status.source, ''
37
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{happymapper}
5
+ s.version = "0.2.5"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["John Nunemaker"]
9
+ s.date = %q{2009-05-27}
10
+ s.description = %q{object to xml mapping library}
11
+ s.email = %q{nunemaker@gmail.com}
12
+ s.extra_rdoc_files = ["lib/happymapper/attribute.rb", "lib/happymapper/element.rb", "lib/happymapper/item.rb", "lib/happymapper/version.rb", "lib/happymapper.rb", "README", "TODO"]
13
+ s.files = ["examples/amazon.rb", "examples/current_weather.rb", "examples/dashed_elements.rb", "examples/post.rb", "examples/twitter.rb", "happymapper.gemspec", "History", "lib/happymapper/attribute.rb", "lib/happymapper/element.rb", "lib/happymapper/item.rb", "lib/happymapper/version.rb", "lib/happymapper.rb", "License", "Manifest", "Rakefile", "README", "spec/fixtures/address.xml", "spec/fixtures/analytics.xml", "spec/fixtures/commit.xml", "spec/fixtures/current_weather.xml", "spec/fixtures/family_tree.xml", "spec/fixtures/multiple_namespaces.xml", "spec/fixtures/pita.xml", "spec/fixtures/posts.xml", "spec/fixtures/product_default_namespace.xml", "spec/fixtures/product_no_namespace.xml", "spec/fixtures/product_single_namespace.xml", "spec/fixtures/radar.xml", "spec/fixtures/statuses.xml", "spec/happymapper_attribute_spec.rb", "spec/happymapper_element_spec.rb", "spec/happymapper_item_spec.rb", "spec/happymapper_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "TODO", "website/css/common.css", "website/index.html"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://happymapper.rubyforge.org}
16
+ s.post_install_message = %q{May you have many happy mappings!}
17
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Happymapper", "--main", "README"]
18
+ s.require_paths = ["lib"]
19
+ s.rubyforge_project = %q{happymapper}
20
+ s.rubygems_version = %q{1.3.1}
21
+ s.summary = %q{object to xml mapping library}
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ s.add_development_dependency(%q<echoe>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<echoe>, [">= 0"])
31
+ end
32
+ else
33
+ s.add_dependency(%q<echoe>, [">= 0"])
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module HappyMapper
2
+ class Attribute < Item; end
3
+ end
@@ -0,0 +1,3 @@
1
+ module HappyMapper
2
+ class Element < Item; end
3
+ end
@@ -0,0 +1,166 @@
1
+ module HappyMapper
2
+ class Item
3
+ attr_accessor :name, :type, :tag, :options, :namespace
4
+
5
+ Types = [String, Float, Time, Date, DateTime, Integer, Boolean]
6
+
7
+ # options:
8
+ # :deep => Boolean False to only parse element's children, True to include
9
+ # grandchildren and all others down the chain (// in expath)
10
+ # :namespace => String Element's namespace if it's not the global or inherited
11
+ # default
12
+ # :parser => Symbol Class method to use for type coercion.
13
+ # :raw => Boolean Use raw node value (inc. tags) when parsing.
14
+ # :single => Boolean False if object should be collection, True for single object
15
+ # :tag => String Element name if it doesn't match the specified name.
16
+ def initialize(name, type, o={})
17
+ self.name = name.to_s
18
+ self.type = type
19
+ self.tag = o.delete(:tag) || name.to_s
20
+ self.options = o
21
+
22
+ @xml_type = self.class.to_s.split('::').last.downcase
23
+ end
24
+
25
+ def constant
26
+ @constant ||= constantize(type)
27
+ end
28
+
29
+ def from_xml_node(node, namespace)
30
+ if primitive?
31
+ find(node, namespace) do |n|
32
+ if n.respond_to?(:text)
33
+ typecast(n.text)
34
+ else
35
+ typecast(n.to_s)
36
+ end
37
+ end
38
+ else
39
+ if options[:parser]
40
+ find(node, namespace) do |n|
41
+ if n.respond_to?(:text) && !options[:raw]
42
+ value = n.text
43
+ else
44
+ value = n.to_s
45
+ end
46
+
47
+ begin
48
+ constant.send(options[:parser].to_sym, value)
49
+ rescue
50
+ nil
51
+ end
52
+ end
53
+ else
54
+ constant.parse(node, options)
55
+ end
56
+ end
57
+ end
58
+
59
+ def xpath(namespace = self.namespace)
60
+ xpath = ''
61
+ xpath += './/' if options[:deep]
62
+ xpath += "#{namespace}:" if namespace
63
+ xpath += tag
64
+ # puts "xpath: #{xpath}"
65
+ xpath
66
+ end
67
+
68
+ def primitive?
69
+ Types.include?(constant)
70
+ end
71
+
72
+ def element?
73
+ @xml_type == 'element'
74
+ end
75
+
76
+ def attribute?
77
+ !element?
78
+ end
79
+
80
+ def method_name
81
+ @method_name ||= name.tr('-', '_')
82
+ end
83
+
84
+ def typecast(value)
85
+ return value if value.kind_of?(constant) || value.nil?
86
+ begin
87
+ if constant == String then value.to_s
88
+ elsif constant == Float then value.to_f
89
+ elsif constant == Time then Time.parse(value.to_s)
90
+ elsif constant == Date then Date.parse(value.to_s)
91
+ elsif constant == DateTime then DateTime.parse(value.to_s)
92
+ elsif constant == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
93
+ elsif constant == Integer
94
+ # ganked from datamapper
95
+ value_to_i = value.to_i
96
+ if value_to_i == 0 && value != '0'
97
+ value_to_s = value.to_s
98
+ begin
99
+ Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
100
+ rescue ArgumentError
101
+ nil
102
+ end
103
+ else
104
+ value_to_i
105
+ end
106
+ else
107
+ value
108
+ end
109
+ rescue
110
+ value
111
+ end
112
+ end
113
+
114
+ private
115
+ def constantize(type)
116
+ if type.is_a?(String)
117
+ names = type.split('::')
118
+ constant = Object
119
+ names.each do |name|
120
+ constant = constant.const_defined?(name) ?
121
+ constant.const_get(name) :
122
+ constant.const_missing(name)
123
+ end
124
+ constant
125
+ else
126
+ type
127
+ end
128
+ end
129
+
130
+ def find(node, namespace, &block)
131
+ # this node has a custom namespace (that is present in the doc)
132
+ if self.namespace && node.prefixes.detect {|prefix| prefix == self.namespace}
133
+ # from the class definition
134
+ namespace = self.namespace
135
+ elsif options[:namespace] && node.prefixes.detect {|prefix| prefix == options[:namespace]}
136
+ # from an element definition
137
+ namespace = options[:namespace]
138
+ end
139
+
140
+ if element?
141
+ result = REXML::XPath.first(node, xpath(namespace))
142
+ # puts "vfxn: #{xpath} #{result.inspect}"
143
+ if result
144
+ value = yield(result)
145
+ if options[:attributes].is_a?(Hash)
146
+ result.attributes.each_attribute do |xml_attribute|
147
+ if attribute_options = options[:attributes][xml_attribute.name.to_sym]
148
+ attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options).from_xml_node(result, namespace)
149
+ result.instance_eval <<-EOV
150
+ def value.#{xml_attribute.name}
151
+ #{attribute_value.inspect}
152
+ end
153
+ EOV
154
+ end
155
+ end
156
+ end
157
+ value
158
+ else
159
+ nil
160
+ end
161
+ else
162
+ yield(node.attribute(tag))
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,3 @@
1
+ module HappyMapper
2
+ Version = '0.2.5'
3
+ end