smg 0.1.0 → 0.2.0
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/README.rdoc +6 -62
- data/examples/discogs/label.rb +62 -0
- data/examples/discogs/search.rb +60 -0
- data/examples/plant.rb +1 -2
- data/examples/twitter.rb +8 -5
- data/examples/weather.rb +146 -0
- data/lib/smg.rb +4 -0
- data/lib/smg/document.rb +25 -8
- data/lib/smg/http.rb +66 -0
- data/lib/smg/http/exceptions.rb +37 -0
- data/lib/smg/http/request.rb +126 -0
- data/lib/smg/mapping.rb +11 -1
- data/lib/smg/mapping/element.rb +29 -13
- data/lib/smg/mapping/typecasts.rb +2 -1
- data/lib/smg/model.rb +9 -3
- data/lib/smg/resource.rb +5 -1
- data/spec/collect_spec.rb +254 -0
- data/spec/context_spec.rb +189 -0
- data/spec/extract_spec.rb +200 -0
- data/spec/filtering_spec.rb +164 -0
- data/spec/fixtures/discogs/948224.xml +1 -0
- data/spec/fixtures/discogs/Enzyme+Records.xml +9 -0
- data/spec/fixtures/discogs/Genosha+Recordings.xml +13 -0
- data/spec/fixtures/discogs/Ophidian.xml +6 -0
- data/spec/fixtures/fake/malus.xml +18 -0
- data/spec/fixtures/fake/valve.xml +8 -0
- data/spec/fixtures/twitter/pipopolam.xml +46 -0
- data/spec/fixtures/yahoo.weather.com.xml +50 -0
- data/spec/http/request_spec.rb +186 -0
- data/spec/http/shared/automatic.rb +43 -0
- data/spec/http/shared/non_automatic.rb +36 -0
- data/spec/http/shared/redirectable.rb +30 -0
- data/spec/http_spec.rb +76 -0
- data/spec/lib/helpers/http_helpers.rb +27 -0
- data/spec/lib/matchers/instance_methods.rb +38 -0
- data/spec/mapping/element_spec.rb +241 -0
- data/spec/mapping/typecasts_spec.rb +52 -0
- data/spec/resource_spec.rb +30 -0
- data/spec/root_spec.rb +26 -0
- data/spec/spec_helper.rb +23 -0
- metadata +53 -10
- data/examples/discogs.rb +0 -39
data/README.rdoc
CHANGED
@@ -6,72 +6,16 @@ Backed by Nokogiri's SAX Parser.
|
|
6
6
|
== FEATURES:
|
7
7
|
|
8
8
|
* Typecasting
|
9
|
-
* Nested resources
|
10
|
-
* Collections
|
9
|
+
* Nested resources
|
10
|
+
* Collections
|
11
|
+
* Conditions
|
11
12
|
* Contextual parsing
|
13
|
+
* HTTP interaction
|
12
14
|
|
13
15
|
== EXAMPLES:
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
class Status
|
18
|
-
include SMG::Resource
|
19
|
-
|
20
|
-
root 'status'
|
21
|
-
|
22
|
-
extract :id , :class => :integer, :as => :status_id
|
23
|
-
extract :created_at , :class => :datetime
|
24
|
-
extract :text
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
class User
|
29
|
-
include SMG::Resource
|
30
|
-
|
31
|
-
root 'user'
|
32
|
-
|
33
|
-
extract :id , :class => :integer, :as => :twitter_id
|
34
|
-
extract :location , :class => :string
|
35
|
-
extract :status , :class => Status
|
36
|
-
extract :created_at , :class => :datetime
|
37
|
-
extract :name
|
38
|
-
extract :screen_name
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
=== discogs.com (with context)
|
43
|
-
|
44
|
-
class Label
|
45
|
-
|
46
|
-
class Release
|
47
|
-
|
48
|
-
include SMG::Resource
|
49
|
-
|
50
|
-
extract :release , :at => :id, :as => :discogs_id, :class => :integer
|
51
|
-
extract :release , :at => :status
|
52
|
-
|
53
|
-
extract 'release/title'
|
54
|
-
extract 'release/catno'
|
55
|
-
extract 'release/artist'
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
include SMG::Resource
|
60
|
-
|
61
|
-
root 'resp/label'
|
62
|
-
extract :name
|
63
|
-
extract :profile
|
64
|
-
collect 'releases/release' , :as => :releases, :class => Release, :context => [:with_releases]
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
label = Label.parse(xml)
|
69
|
-
label.name #=> "name of the label"
|
70
|
-
label.releases #=> []
|
71
|
-
|
72
|
-
label = Label.parse(xml, :with_releases)
|
73
|
-
label.name #=> "name of the label"
|
74
|
-
label.releases #=> [#<Label::Release...>, ... #<Label::Release...>]
|
17
|
+
See examples directory:
|
18
|
+
http://github.com/SSDany/smg/tree/master/examples
|
75
19
|
|
76
20
|
== REQUIREMENTS:
|
77
21
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
|
2
|
+
|
3
|
+
module Discogs
|
4
|
+
class Label
|
5
|
+
|
6
|
+
class Release
|
7
|
+
|
8
|
+
include SMG::Resource
|
9
|
+
|
10
|
+
extract 'release' , :at => :id, :as => :discogs_id, :class => :integer
|
11
|
+
extract 'release' , :at => :status
|
12
|
+
|
13
|
+
extract 'release/title'
|
14
|
+
extract 'release/catno'
|
15
|
+
extract 'release/artist'
|
16
|
+
extract 'release/format'
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
include SMG::Resource
|
21
|
+
|
22
|
+
root 'resp/label'
|
23
|
+
|
24
|
+
extract 'name'
|
25
|
+
extract 'contactinfo' , :as => :contacts
|
26
|
+
extract 'profile'
|
27
|
+
extract 'parentLabel' , :as => :parent
|
28
|
+
collect 'urls/url' , :as => :links
|
29
|
+
collect 'sublabels/label' , :as => :sublabels
|
30
|
+
collect 'releases/release' , :as => :releases, :class => Release , :context => [:releases], :with => {"status" => "Accepted"}
|
31
|
+
|
32
|
+
def name_with_parent
|
33
|
+
"#{name}#{" @ #{parent}" if parent}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
data = File.read(ROOT.join('spec/fixtures/discogs/Enzyme+Records.xml'))
|
40
|
+
|
41
|
+
template = <<-TEMPLATE
|
42
|
+
<%= label.name_with_parent %>
|
43
|
+
|
44
|
+
%label.sublabels.each do |sublabel|
|
45
|
+
* <%= sublabel %>
|
46
|
+
%end
|
47
|
+
|
48
|
+
%label.releases.each do |release|
|
49
|
+
* [<%= release.catno %>] <%= release.artist %> - <%= release.title %>
|
50
|
+
%end
|
51
|
+
|
52
|
+
TEMPLATE
|
53
|
+
|
54
|
+
require 'erb'
|
55
|
+
|
56
|
+
label = Discogs::Label.parse(data,:releases)
|
57
|
+
$stdout << ERB.new(template, nil, "%").result(binding)
|
58
|
+
|
59
|
+
label = Discogs::Label.parse(data)
|
60
|
+
$stdout << ERB.new(template, nil, "%").result(binding)
|
61
|
+
|
62
|
+
# EOF
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
module Discogs
|
6
|
+
|
7
|
+
def self.search(t,q)
|
8
|
+
Discogs::Search.get("search",
|
9
|
+
:query => {"type" => t, "q" => q},
|
10
|
+
:headers => {"Accept-Encoding" => "gzip,*;q=0"}) do |response|
|
11
|
+
Zlib::GzipReader.new(StringIO.new(response.body)).read
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Search
|
16
|
+
|
17
|
+
include SMG::Resource
|
18
|
+
include SMG::HTTP
|
19
|
+
|
20
|
+
class ExactResult
|
21
|
+
include SMG::Resource
|
22
|
+
extract 'result', :at => 'type', :as => :resource_type
|
23
|
+
extract 'result/title'
|
24
|
+
extract 'result/uri'
|
25
|
+
end
|
26
|
+
|
27
|
+
class Result
|
28
|
+
include SMG::Resource
|
29
|
+
extract 'result', :at => 'type', :as => :resource_type
|
30
|
+
extract 'result/title'
|
31
|
+
extract 'result/uri'
|
32
|
+
extract 'result/summary'
|
33
|
+
end
|
34
|
+
|
35
|
+
site "http://www.discogs.com"
|
36
|
+
params "f" => "xml", "api_key" => "API_KEY"
|
37
|
+
|
38
|
+
root 'resp'
|
39
|
+
collect 'exactresults/result', :as => :exactresults, :class => ExactResult
|
40
|
+
collect 'searchresults/result', :as => :results, :class => Result
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
search = Discogs.search("all", "Genosha Recordings")
|
47
|
+
|
48
|
+
puts <<-RESULT
|
49
|
+
type: #{search.exactresults.first.resource_type}
|
50
|
+
title: #{search.exactresults.first.title}
|
51
|
+
URI: #{search.exactresults.first.uri}
|
52
|
+
total: #{search.results.size}
|
53
|
+
RESULT
|
54
|
+
|
55
|
+
# type: label
|
56
|
+
# title: Genosha Recordings
|
57
|
+
# URI: http://www.discogs.com/label/Genosha+Recordings
|
58
|
+
# total: 20
|
59
|
+
|
60
|
+
# EOF
|
data/examples/plant.rb
CHANGED
@@ -20,7 +20,7 @@ class Plant
|
|
20
20
|
extract 'genus' , :context => [:classification]
|
21
21
|
extract 'binomial'
|
22
22
|
extract 'conservation' , :context => [:conservation, :info], :class => Conservation
|
23
|
-
collect 'synonims/binomial' , :context => [:synonims], :as => :synonims
|
23
|
+
collect 'synonims/binomial' , :context => [:synonims], :as => :synonims
|
24
24
|
|
25
25
|
end
|
26
26
|
|
@@ -85,5 +85,4 @@ puts plant.conservation.code #=> "NE"
|
|
85
85
|
puts plant.conservation.status #=> nil
|
86
86
|
puts plant.synonims #=> []
|
87
87
|
|
88
|
-
|
89
88
|
# EOF
|
data/examples/twitter.rb
CHANGED
@@ -5,8 +5,8 @@ class Status
|
|
5
5
|
|
6
6
|
root 'status'
|
7
7
|
|
8
|
-
extract :id
|
9
|
-
extract :created_at
|
8
|
+
extract :id , :class => :integer, :as => :status_id
|
9
|
+
extract :created_at , :class => :datetime
|
10
10
|
extract :text
|
11
11
|
|
12
12
|
end
|
@@ -28,8 +28,11 @@ end
|
|
28
28
|
data = File.read(ROOT.join('spec/fixtures/twitter/pipopolam.xml'))
|
29
29
|
user = User.parse(data)
|
30
30
|
|
31
|
-
puts
|
32
|
-
|
33
|
-
|
31
|
+
puts <<-EOS
|
32
|
+
ID: #{user.twitter_id}
|
33
|
+
#{user.screen_name} (#{user.name}), since #{user.created_at.strftime('%Y.%m.%d')}
|
34
|
+
location: #{user.location}
|
35
|
+
status: #{user.status.text}
|
36
|
+
EOS
|
34
37
|
|
35
38
|
# EOF
|
data/examples/weather.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class YWeather
|
4
|
+
|
5
|
+
CONDITION_CODES = {
|
6
|
+
1 => "tornado",
|
7
|
+
2 => "tropical storm",
|
8
|
+
3 => "hurricane",
|
9
|
+
4 => "severe thunderstorms",
|
10
|
+
4 => "thunderstorms",
|
11
|
+
5 => "mixed rain and snow",
|
12
|
+
6 => "mixed rain and sleet",
|
13
|
+
7 => "mixed snow and sleet",
|
14
|
+
8 => "freezing drizzle",
|
15
|
+
9 => "drizzle",
|
16
|
+
10 => "freezing rain",
|
17
|
+
11 => "showers",
|
18
|
+
12 => "showers",
|
19
|
+
13 => "snow flurries",
|
20
|
+
14 => "light snow showers",
|
21
|
+
15 => "blowing snow",
|
22
|
+
16 => "snow",
|
23
|
+
17 => "hail",
|
24
|
+
18 => "sleet",
|
25
|
+
19 => "dust",
|
26
|
+
20 => "foggy",
|
27
|
+
21 => "haze",
|
28
|
+
22 => "smoky",
|
29
|
+
23 => "blustery",
|
30
|
+
24 => "windy",
|
31
|
+
25 => "cold",
|
32
|
+
26 => "cloudy",
|
33
|
+
27 => "mostly cloudy (night)",
|
34
|
+
28 => "mostly cloudy (day)",
|
35
|
+
29 => "partly cloudy (night)",
|
36
|
+
30 => "partly cloudy (day)",
|
37
|
+
31 => "clear (night)",
|
38
|
+
32 => "sunny",
|
39
|
+
33 => "fair (night)",
|
40
|
+
34 => "fair (day)",
|
41
|
+
35 => "mixed rain and hail",
|
42
|
+
36 => "hot",
|
43
|
+
37 => "isolated thunderstorms",
|
44
|
+
38 => "scattered thunderstorms",
|
45
|
+
39 => "scattered thunderstorms",
|
46
|
+
40 => "scattered showers",
|
47
|
+
41 => "heavy snow",
|
48
|
+
42 => "scattered snow showers",
|
49
|
+
43 => "heavy snow",
|
50
|
+
44 => "partly cloudy",
|
51
|
+
45 => "thundershowers",
|
52
|
+
46 => "snow showers",
|
53
|
+
47 => "isolated thundershowers",
|
54
|
+
3200 => "not available"
|
55
|
+
}
|
56
|
+
|
57
|
+
class Forecast
|
58
|
+
include SMG::Resource
|
59
|
+
|
60
|
+
extract "yweather:forecast" , :at => :low
|
61
|
+
extract "yweather:forecast" , :at => :high
|
62
|
+
extract "yweather:forecast" , :at => :day
|
63
|
+
extract "yweather:forecast" , :at => :date , :class => :datetime
|
64
|
+
extract "yweather:forecast" , :at => :code , :class => :integer
|
65
|
+
extract "yweather:forecast" , :at => :text
|
66
|
+
|
67
|
+
def condition
|
68
|
+
CONDITION_CODES[code]
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class Condition
|
74
|
+
include SMG::Resource
|
75
|
+
|
76
|
+
extract "yweather:condition" , :at => :temp
|
77
|
+
extract "yweather:condition" , :at => :date
|
78
|
+
extract "yweather:condition" , :at => :text
|
79
|
+
extract "yweather:condition" , :at => :code , :class => :integer
|
80
|
+
|
81
|
+
def condition
|
82
|
+
CONDITION_CODES[code]
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
class Units
|
88
|
+
include SMG::Resource
|
89
|
+
|
90
|
+
extract "yweather:units" , :at => :temperature
|
91
|
+
extract "yweather:units" , :at => :distance
|
92
|
+
extract "yweather:units" , :at => :pressure
|
93
|
+
extract "yweather:units" , :at => :speed
|
94
|
+
end
|
95
|
+
|
96
|
+
class Atmosphere
|
97
|
+
include SMG::Resource
|
98
|
+
|
99
|
+
extract "yweather:atmosphere" , :at => :humidity
|
100
|
+
extract "yweather:atmosphere" , :at => :visibility
|
101
|
+
extract "yweather:atmosphere" , :at => :pressure
|
102
|
+
extract "yweather:atmosphere" , :at => :rising
|
103
|
+
end
|
104
|
+
|
105
|
+
class Location
|
106
|
+
include SMG::Resource
|
107
|
+
|
108
|
+
extract "yweather:location" , :at => :city
|
109
|
+
extract "yweather:location" , :at => :region
|
110
|
+
extract "yweather:location" , :at => :country
|
111
|
+
end
|
112
|
+
|
113
|
+
class Wind
|
114
|
+
include SMG::Resource
|
115
|
+
|
116
|
+
extract "yweather:wind" , :at => :chill
|
117
|
+
extract "yweather:wind" , :at => :direction
|
118
|
+
extract "yweather:wind" , :at => :speed
|
119
|
+
end
|
120
|
+
|
121
|
+
include SMG::Resource
|
122
|
+
|
123
|
+
root 'rss/channel'
|
124
|
+
|
125
|
+
extract "lastBuildDate" , :as => :built_at
|
126
|
+
extract "yweather:astronomy" , :at => :sunrise
|
127
|
+
extract "yweather:astronomy" , :at => :sunset
|
128
|
+
extract "yweather:location" , :as => :wind , :class => Location
|
129
|
+
extract "yweather:units" , :as => :units , :class => Units
|
130
|
+
extract "yweather:atmosphere" , :as => :atmosphere , :class => Atmosphere
|
131
|
+
extract "yweather:wind" , :as => :wind , :class => Wind
|
132
|
+
|
133
|
+
root 'rss/channel/item'
|
134
|
+
|
135
|
+
extract "geo:lat" , :as => :latitude
|
136
|
+
extract "geo:long" , :as => :longitude
|
137
|
+
extract "title" , :as => :title
|
138
|
+
extract "yweather:condition" , :as => :current , :class => Condition
|
139
|
+
collect "yweather:forecast" , :as => :forecasts , :class => Forecast
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
data = File.read(ROOT.join('spec/fixtures/yahoo.weather.com.xml'))
|
144
|
+
yweather = YWeather.parse(data)
|
145
|
+
|
146
|
+
# EOF
|
data/lib/smg.rb
CHANGED
data/lib/smg/document.rb
CHANGED
@@ -11,27 +11,42 @@ module SMG #:nodoc:
|
|
11
11
|
@thing = thing
|
12
12
|
@context = context
|
13
13
|
@chars = ""
|
14
|
+
|
15
|
+
@mapping.refresh!
|
14
16
|
end
|
15
17
|
|
16
|
-
def start_element(name, attrs
|
18
|
+
def start_element(name, attrs)
|
17
19
|
|
18
20
|
@stack << name
|
21
|
+
ahash = nil
|
19
22
|
|
20
23
|
if doc = @docs.last
|
21
24
|
doc.start_element(name, attrs)
|
22
|
-
elsif (thing = @mapping.nested[@stack]) &&
|
25
|
+
elsif (thing = @mapping.nested[@stack]) &&
|
26
|
+
!@mapping.parsed.include?(thing.object_id) &&
|
27
|
+
thing.in_context_of?(@context) &&
|
28
|
+
thing.with?(ahash ||= Hash[*attrs])
|
29
|
+
|
23
30
|
@docs << doc = Document.new(thing.data_class.new,@context,thing)
|
24
31
|
doc.start_element(name, attrs)
|
25
32
|
end
|
26
33
|
|
27
34
|
if !attrs.empty? && maps = @mapping.attributes[@stack]
|
28
|
-
|
29
|
-
|
30
|
-
|
35
|
+
maps.values_at(*(ahash ||= Hash[*attrs]).keys).compact.each do |m|
|
36
|
+
if !@mapping.parsed.include?(m.object_id) &&
|
37
|
+
m.in_context_of?(@context) &&
|
38
|
+
m.with?(ahash)
|
39
|
+
|
40
|
+
@object.__send__(m.accessor, m.cast(ahash[m.at]))
|
41
|
+
@mapping.parsed << m.object_id unless m.collection?
|
42
|
+
end
|
31
43
|
end
|
32
44
|
end
|
33
45
|
|
34
|
-
if (e = @mapping.elements[@stack]) &&
|
46
|
+
if (e = @mapping.elements[@stack]) &&
|
47
|
+
!@mapping.parsed.include?(e.object_id) &&
|
48
|
+
e.in_context_of?(@context) &&
|
49
|
+
e.with?(ahash ||= Hash[*attrs])
|
35
50
|
@element = e
|
36
51
|
@chars = ""
|
37
52
|
end
|
@@ -42,15 +57,17 @@ module SMG #:nodoc:
|
|
42
57
|
|
43
58
|
if @element
|
44
59
|
@object.__send__(@element.accessor, @element.cast(@chars))
|
60
|
+
@mapping.parsed << @element.object_id unless @element.collection?
|
45
61
|
@chars = ""
|
46
62
|
@element = nil
|
47
63
|
end
|
48
64
|
|
49
65
|
if doc = @docs.last
|
50
66
|
doc.end_element(name)
|
51
|
-
if doc.thing.path == @stack
|
52
|
-
@object.__send__(
|
67
|
+
if (t = doc.thing).path == @stack
|
68
|
+
@object.__send__(t.accessor, doc.object)
|
53
69
|
@docs.pop
|
70
|
+
@mapping.parsed << t.object_id unless t.collection?
|
54
71
|
end
|
55
72
|
end
|
56
73
|
|