smg 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.rdoc +6 -62
  2. data/examples/discogs/label.rb +62 -0
  3. data/examples/discogs/search.rb +60 -0
  4. data/examples/plant.rb +1 -2
  5. data/examples/twitter.rb +8 -5
  6. data/examples/weather.rb +146 -0
  7. data/lib/smg.rb +4 -0
  8. data/lib/smg/document.rb +25 -8
  9. data/lib/smg/http.rb +66 -0
  10. data/lib/smg/http/exceptions.rb +37 -0
  11. data/lib/smg/http/request.rb +126 -0
  12. data/lib/smg/mapping.rb +11 -1
  13. data/lib/smg/mapping/element.rb +29 -13
  14. data/lib/smg/mapping/typecasts.rb +2 -1
  15. data/lib/smg/model.rb +9 -3
  16. data/lib/smg/resource.rb +5 -1
  17. data/spec/collect_spec.rb +254 -0
  18. data/spec/context_spec.rb +189 -0
  19. data/spec/extract_spec.rb +200 -0
  20. data/spec/filtering_spec.rb +164 -0
  21. data/spec/fixtures/discogs/948224.xml +1 -0
  22. data/spec/fixtures/discogs/Enzyme+Records.xml +9 -0
  23. data/spec/fixtures/discogs/Genosha+Recordings.xml +13 -0
  24. data/spec/fixtures/discogs/Ophidian.xml +6 -0
  25. data/spec/fixtures/fake/malus.xml +18 -0
  26. data/spec/fixtures/fake/valve.xml +8 -0
  27. data/spec/fixtures/twitter/pipopolam.xml +46 -0
  28. data/spec/fixtures/yahoo.weather.com.xml +50 -0
  29. data/spec/http/request_spec.rb +186 -0
  30. data/spec/http/shared/automatic.rb +43 -0
  31. data/spec/http/shared/non_automatic.rb +36 -0
  32. data/spec/http/shared/redirectable.rb +30 -0
  33. data/spec/http_spec.rb +76 -0
  34. data/spec/lib/helpers/http_helpers.rb +27 -0
  35. data/spec/lib/matchers/instance_methods.rb +38 -0
  36. data/spec/mapping/element_spec.rb +241 -0
  37. data/spec/mapping/typecasts_spec.rb +52 -0
  38. data/spec/resource_spec.rb +30 -0
  39. data/spec/root_spec.rb +26 -0
  40. data/spec/spec_helper.rb +23 -0
  41. metadata +53 -10
  42. data/examples/discogs.rb +0 -39
@@ -6,72 +6,16 @@ Backed by Nokogiri's SAX Parser.
6
6
  == FEATURES:
7
7
 
8
8
  * Typecasting
9
- * Nested resources (aka has_one)
10
- * Collections (aka has_many)
9
+ * Nested resources
10
+ * Collections
11
+ * Conditions
11
12
  * Contextual parsing
13
+ * HTTP interaction
12
14
 
13
15
  == EXAMPLES:
14
16
 
15
- === Twitter
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
@@ -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
@@ -5,8 +5,8 @@ class Status
5
5
 
6
6
  root 'status'
7
7
 
8
- extract :id , :class => :integer, :as => :status_id
9
- extract :created_at , :class => :datetime
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 "#{user.screen_name} (#{user.name}), since #{user.created_at.strftime('%Y.%m.%d')}"
32
- puts user.location
33
- puts user.status.text
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
@@ -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
@@ -11,4 +11,8 @@ require 'smg/model'
11
11
  require 'smg/resource'
12
12
  require 'smg/document'
13
13
 
14
+ module SMG
15
+ autoload :HTTP, 'smg/http'
16
+ end
17
+
14
18
  # EOF
@@ -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]) && thing.in_context_of?(@context)
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
- attrh = Hash[*attrs]
29
- maps.values_at(*attrh.keys).compact.each do |m|
30
- @object.__send__(m.accessor, m.cast(attrh[m.at])) if m.in_context_of?(@context)
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]) && e.in_context_of?(@context)
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__(doc.thing.accessor, doc.object)
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