pummel 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ module OEmbed
2
+ class Formatters
3
+ FORMATS = Hash.new { |_, format| raise OEmbed::FormatNotSupported, format }
4
+
5
+ # Load XML
6
+ begin
7
+ require 'xmlsimple'
8
+ FORMATS[:xml] = proc { |r| XmlSimple.xml_in(r, 'ForceArray' => false)}
9
+ rescue LoadError
10
+ end
11
+
12
+ # Load JSON
13
+ begin
14
+ require 'json'
15
+ FORMATS[:json] = proc { |r| ::JSON.load(r) }
16
+ rescue LoadError
17
+ end
18
+
19
+ DEFAULT = FORMATS.keys.first
20
+
21
+ def self.verify?(type)
22
+ FORMATS[type] && type
23
+ end
24
+
25
+ def self.convert(type, value)
26
+ FORMATS[type].call(value)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,80 @@
1
+ module OEmbed
2
+ class Provider
3
+ attr_accessor :format, :name, :url, :urls, :endpoint
4
+
5
+ def initialize(endpoint, format = OEmbed::Formatters::DEFAULT)
6
+ @endpoint = endpoint
7
+ @urls = []
8
+ # Try to use the best available format
9
+ @format = OEmbed::Formatters.verify?(format)
10
+ end
11
+
12
+ def <<(url)
13
+ if url.is_a? Regexp
14
+ @urls << url
15
+ OEmbed::Providers.urls[url] = self
16
+ return
17
+ end
18
+ full, scheme, domain, path = *url.match(%r{([^:]*)://?([^/?]*)(.*)})
19
+ domain = Regexp.escape(domain).gsub("\\*", "(.*?)").gsub("(.*?)\\.", "([^\\.]+\\.)?")
20
+ path = Regexp.escape(path).gsub("\\*", "(.*?)")
21
+ url_regex = Regexp.new("^#{Regexp.escape(scheme)}://#{domain}#{path}")
22
+ @urls << url_regex
23
+ OEmbed::Providers.urls[url_regex] = self
24
+ end
25
+
26
+ def build(url, options = {})
27
+ raise OEmbed::NotFound, url unless include?(url)
28
+ query = options.merge({:url => url})
29
+ endpoint = @endpoint.clone
30
+
31
+ if format_in_url?
32
+ format = endpoint["{format}"] = (query[:format] || @format).to_s
33
+ query.delete(:format)
34
+ else
35
+ format = query[:format] ||= @format
36
+ end
37
+
38
+ query_string = "?" + query.inject("") do |memo, (key, value)|
39
+ "#{key}=#{value}&#{memo}"
40
+ end.chop
41
+
42
+ URI.parse(endpoint + query_string).instance_eval do
43
+ @format = format; def format; @format; end
44
+ self
45
+ end
46
+ end
47
+
48
+ def raw(url, options = {})
49
+ uri = build(url, options)
50
+
51
+ res = Net::HTTP.start(uri.host, uri.port) do |http|
52
+ http.get(uri.request_uri)
53
+ end
54
+
55
+ case res
56
+ when Net::HTTPNotImplemented
57
+ raise OEmbed::UnknownFormat, uri.format
58
+ when Net::HTTPNotFound
59
+ raise OEmbed::NotFound, url
60
+ when Net::HTTPOK
61
+ res.body
62
+ else
63
+ raise OEmbed::UnknownResponse, res.code
64
+ end
65
+ end
66
+
67
+ def get(url, options = {})
68
+ options[:format] ||= @format if @format
69
+ OEmbed::Response.create_for(raw(url, options), self, url, options[:format])
70
+ end
71
+
72
+ def format_in_url?
73
+ @endpoint.include?("{format}")
74
+ end
75
+
76
+ def include?(url)
77
+ @urls.empty? || !!@urls.detect{ |u| u =~ url }
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,55 @@
1
+ module OEmbed
2
+ class ProviderDiscovery
3
+ class << self
4
+ def raw(url, options = {})
5
+ provider = discover_provider(url, options)
6
+ provider.raw(url, options)
7
+ end
8
+
9
+ def get(url, options = {})
10
+ provider = discover_provider(url, options)
11
+ provider.get(url, options)
12
+ end
13
+
14
+ def discover_provider(url, options = {})
15
+ uri = URI.parse(url)
16
+
17
+ res = Net::HTTP.start(uri.host, uri.port) do |http|
18
+ http.get(uri.request_uri)
19
+ end
20
+
21
+ case res
22
+ when Net::HTTPNotFound
23
+ raise OEmbed::NotFound, url
24
+ when Net::HTTPOK
25
+ format = options[:format]
26
+
27
+ if format.nil? || format == :json
28
+ provider_endpoint ||= /<link.*href=['"]*([^\s'"]+)['"]*.*application\/json\+oembed.*>/.match(res.body)
29
+ provider_endpoint ||= /<link.*application\/json\+oembed.*href=['"]*([^\s'"]+)['"]*.*>/.match(res.body)
30
+ format ||= :json if provider_endpoint
31
+ end
32
+ if format.nil? || format == :xml
33
+ provider_endpoint ||= /<link.*href=['"]*([^\s'"]+)['"]*.*application\/xml\+oembed.*>/.match(res.body)
34
+ provider_endpoint ||= /<link.*application\/xml\+oembed.*href=['"]*([^\s'"]+)['"]*.*>/.match(res.body)
35
+ format ||= :xml if provider_endpoint
36
+ end
37
+
38
+ begin
39
+ provider_endpoint = URI.parse(provider_endpoint && provider_endpoint[1])
40
+ provider_endpoint.query = nil
41
+ provider_endpoint = provider_endpoint.to_s
42
+ rescue URI::Error
43
+ raise OEmbed::NotFound, url
44
+ end
45
+
46
+
47
+ Provider.new(provider_endpoint, format || OEmbed::Formatters::DEFAULT)
48
+ else
49
+ raise OEmbed::UnknownResponse, res.code
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,146 @@
1
+ module OEmbed
2
+ class Providers
3
+ class << self
4
+ @@urls = {}
5
+ @@fallback = []
6
+
7
+ def urls
8
+ @@urls
9
+ end
10
+
11
+ def register(*providers)
12
+ providers.each do |provider|
13
+ provider.urls.each do |url|
14
+ @@urls[url] = provider
15
+ end
16
+ end
17
+ end
18
+
19
+ def unregister(*providers)
20
+ providers.each do |provider|
21
+ provider.urls.each do |url|
22
+ @@urls.delete(url)
23
+ end
24
+ end
25
+ end
26
+
27
+ def register_all
28
+ register(Flickr, Viddler, Qik, Pownce, Revision3, Hulu, Vimeo)
29
+ end
30
+
31
+ # Takes an array of OEmbed::Provider instances or OEmbed::ProviderDiscovery
32
+ # Use this method to register fallback providers.
33
+ # When the raw or get methods are called, if the URL doesn't match
34
+ # any of the registerd url patters the fallback providers
35
+ # will be called (in order) with the URL.
36
+ #
37
+ # A common example:
38
+ # OEmbed::Providers.register_fallback(OEmbed::ProviderDiscovery, OEmbed::Providers::OohEmbed)
39
+ def register_fallback(*providers)
40
+ @@fallback += providers
41
+ end
42
+
43
+ # Returns an array of all registerd fallback providers
44
+ def fallback
45
+ @@fallback
46
+ end
47
+
48
+ def find(url)
49
+ @@urls[@@urls.keys.detect { |u| u =~ url }] || false
50
+ end
51
+
52
+ def raw(url, options = {})
53
+ provider = find(url)
54
+ if provider
55
+ provider.raw(url, options)
56
+ else
57
+ fallback.each do |p|
58
+ return p.raw(url, options) rescue OEmbed::Error
59
+ end
60
+ raise(OEmbed::NotFound)
61
+ end
62
+ end
63
+
64
+ def get(url, options = {})
65
+ provider = find(url)
66
+ if provider
67
+ provider.get(url, options)
68
+ else
69
+ fallback.each do |p|
70
+ return p.get(url, options) rescue OEmbed::Error
71
+ end
72
+ raise(OEmbed::NotFound)
73
+ end
74
+ end
75
+ end
76
+
77
+ # Custom providers:
78
+ Youtube = OEmbed::Provider.new("http://www.youtube.com/oembed/")
79
+ Youtube << "http://*.youtube.com/*"
80
+
81
+ Flickr = OEmbed::Provider.new("http://www.flickr.com/services/oembed/")
82
+ Flickr << "http://*.flickr.com/*"
83
+
84
+ Viddler = OEmbed::Provider.new("http://lab.viddler.com/services/oembed/")
85
+ Viddler << "http://*.viddler.com/*"
86
+
87
+ Qik = OEmbed::Provider.new("http://qik.com/api/oembed.{format}")
88
+ Qik << "http://qik.com/*"
89
+ Qik << "http://qik.com/video/*"
90
+
91
+ Revision3 = OEmbed::Provider.new("http://revision3.com/api/oembed/")
92
+ Revision3 << "http://*.revision3.com/*"
93
+
94
+ Hulu = OEmbed::Provider.new("http://www.hulu.com/api/oembed.{format}")
95
+ Hulu << "http://www.hulu.com/watch/*"
96
+
97
+ Vimeo = OEmbed::Provider.new("http://www.vimeo.com/api/oembed.{format}")
98
+ Vimeo << "http://*.vimeo.com/*"
99
+ Vimeo << "http://*.vimeo.com/groups/*/videos/*"
100
+
101
+ Pownce = OEmbed::Provider.new("http://api.pownce.com/2.1/oembed.{format}")
102
+ Pownce << "http://*.pownce.com/*"
103
+
104
+ # A general end point, which then calls other APIs and returns OEmbed info
105
+ OohEmbed = OEmbed::Provider.new("http://oohembed.com/oohembed/", :json)
106
+ OohEmbed << %r{http://yfrog.(com|ru|com.tr|it|fr|co.il|co.uk|com.pl|pl|eu|us)/(.*?)} # image & video hosting
107
+ OohEmbed << "http://*.xkcd.com/*" # A hilarious stick figure comic
108
+ OohEmbed << "http://*.wordpress.com/*/*/*/*" # Blogging Engine & community
109
+ OohEmbed << "http://*.wikipedia.org/wiki/*" # Online encyclopedia
110
+ OohEmbed << "http://*.twitpic.com/*" # Picture hosting for Twitter
111
+ OohEmbed << "http://twitter.com/*/statuses/*" # Mirco-blogging network
112
+ OohEmbed << "http://*.slideshare.net/*" # Share presentations online
113
+ OohEmbed << "http://*.phodroid.com/*/*/*" # Photo host
114
+ OohEmbed << "http://*.metacafe.com/watch/*" # Video host
115
+ OohEmbed << "http://video.google.com/videoplay?*" # Video hosting
116
+ OohEmbed << "http://*.funnyordie.com/videos/*" # Comedy video host
117
+ OohEmbed << "http://*.thedailyshow.com/video/*" # Syndicated show
118
+ OohEmbed << "http://*.collegehumor.com/video:*" # Comedic & original videos
119
+ OohEmbed << %r{http://(.*?).amazon.(com|co.uk|de|ca|jp)/(.*?)/(gp/product|o/ASIN|obidos/ASIN|dp)/(.*?)} # Online product shopping
120
+ OohEmbed << "http://*.5min.com/Video/*" # micro-video host
121
+
122
+ PollEverywhere = OEmbed::Provider.new("http://www.polleverywhere.com/services/oembed/")
123
+ PollEverywhere << "http://www.polleverywhere.com/polls/*"
124
+ PollEverywhere << "http://www.polleverywhere.com/multiple_choice_polls/*"
125
+ PollEverywhere << "http://www.polleverywhere.com/free_text_polls/*"
126
+
127
+ MyOpera = OEmbed::Provider.new("http://my.opera.com/service/oembed", :json)
128
+ MyOpera << "http://my.opera.com/*"
129
+
130
+ ClearspringWidgets = OEmbed::Provider.new("http://widgets.clearspring.com/widget/v1/oembed/")
131
+ ClearspringWidgets << "http://www.clearspring.com/widgets/*"
132
+
133
+ NFBCanada = OEmbed::Provider.new("http://www.nfb.ca/remote/services/oembed/")
134
+ NFBCanada << "http://*.nfb.ca/film/*"
135
+
136
+ Scribd = OEmbed::Provider.new("http://www.scribd.com/services/oembed")
137
+ Scribd << "http://*.scribd.com/*"
138
+
139
+ MovieClips = OEmbed::Provider.new("http://movieclips.com/services/oembed/")
140
+ MovieClips << "http://movieclips.com/watch/*/*/"
141
+
142
+ TwentyThree = OEmbed::Provider.new("http://www.23hq.com/23/oembed")
143
+ TwentyThree << "http://www.23hq.com/*"
144
+
145
+ end
146
+ end
@@ -0,0 +1,59 @@
1
+ module OEmbed
2
+ class Response
3
+ METHODS = [:define_methods!, :provider, :field, :fields]
4
+ attr_reader :fields, :provider, :format, :url
5
+
6
+ def self.create_for(raw, provider, url, format = :json)
7
+ fields = OEmbed::Formatters.convert(format, raw)
8
+
9
+ resp_type = case fields['type']
10
+ when 'photo' then OEmbed::Response::Photo
11
+ when 'video' then OEmbed::Response::Video
12
+ when 'link' then OEmbed::Response::Link
13
+ when 'rich' then OEmbed::Response::Rich
14
+ else self
15
+ end
16
+
17
+ resp_type.new(fields, provider, url)
18
+ end
19
+
20
+ def initialize(fields, provider, url)
21
+ @fields = fields
22
+ @provider = provider
23
+ define_methods!
24
+ end
25
+
26
+ def field(m)
27
+ @fields[m.to_s]
28
+ end
29
+
30
+ def video?
31
+ is_a?(OEmbed::Response::Video)
32
+ end
33
+
34
+ def photo?
35
+ is_a?(OEmbed::Response::Photo)
36
+ end
37
+
38
+ def link?
39
+ is_a?(OEmbed::Response::Link)
40
+ end
41
+
42
+ def rich?
43
+ is_a?(OEmbed::Response::Rich)
44
+ end
45
+
46
+ private
47
+
48
+ def define_methods!
49
+ @fields.keys.each do |key|
50
+ next if METHODS.include?(key.to_sym) || key[0,2]=="__" || key[-1]==??
51
+ class << self
52
+ self
53
+ end.send(:define_method, key) do
54
+ @fields[key]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module OEmbed
2
+ class Response
3
+ class Link < self
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module OEmbed
2
+ class Response
3
+ class Photo < self
4
+ def html
5
+ "<img src='" + self.url + "' />"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module OEmbed
2
+ class Response
3
+ class Rich < self
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module OEmbed
2
+ class Response
3
+ class Video < self
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,175 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe OEmbed::Provider do
4
+ include OEmbedSpecHelper
5
+
6
+ before(:all) do
7
+ @default = OEmbed::Formatters::DEFAULT
8
+ @flickr = OEmbed::Provider.new("http://www.flickr.com/services/oembed/")
9
+ @qik = OEmbed::Provider.new("http://qik.com/api/oembed.{format}", :xml)
10
+ @viddler = OEmbed::Provider.new("http://lab.viddler.com/services/oembed/", :json)
11
+
12
+ @flickr << "http://*.flickr.com/*"
13
+ @qik << "http://qik.com/video/*"
14
+ @qik << "http://qik.com/*"
15
+ @viddler << "http://*.viddler.com/*"
16
+ end
17
+
18
+ it "should by default use OEmbed::Formatters::DEFAULT" do
19
+ @flickr.format.should == @default
20
+ end
21
+
22
+ it "should allow xml" do
23
+ @qik.format.should == :xml
24
+ end
25
+
26
+ it "should allow json" do
27
+ @viddler.format.should == :json
28
+ end
29
+
30
+ it "should not allow random formats" do
31
+ proc { OEmbed::Provider.new("http://www.hulu.com/api/oembed.{format}", :yml) }.
32
+ should raise_error(OEmbed::FormatNotSupported)
33
+ end
34
+
35
+ it "should add URL schemes" do
36
+ @flickr.urls.should == [%r{^http://([^\.]+\.)?flickr\.com/(.*?)}]
37
+ @qik.urls.should == [%r{^http://qik\.com/video/(.*?)},
38
+ %r{^http://qik\.com/(.*?)}]
39
+ end
40
+
41
+ it "should match URLs" do
42
+ @flickr.include?(example_url(:flickr)).should be_true
43
+ @qik.include?(example_url(:qik)).should be_true
44
+ end
45
+
46
+ it "should detect if the format is in the URL" do
47
+ @flickr.format_in_url?.should be_false
48
+ @qik.format_in_url?.should be_true
49
+ end
50
+
51
+ it "should raise error if the URL is invalid" do
52
+ proc{ @flickr.build(example_url(:fake)) }.should raise_error(OEmbed::NotFound)
53
+ proc{ @qik.build(example_url(:fake)) }.should raise_error(OEmbed::NotFound)
54
+ end
55
+
56
+ describe "#build" do
57
+ it "should return a proper URL" do
58
+ uri = @flickr.build(example_url(:flickr))
59
+ uri.host.should == "www.flickr.com"
60
+ uri.path.should == "/services/oembed/"
61
+ uri.query.include?("format=#{@flickr.format}").should be_true
62
+ uri.query.include?("url=http://flickr.com/photos/bees/2362225867/").should be_true
63
+
64
+ uri = @qik.build(example_url(:qik))
65
+ uri.host.should == "qik.com"
66
+ uri.path.should == "/api/oembed.xml"
67
+ uri.query.should == "url=http://qik.com/video/49565"
68
+ end
69
+
70
+ it "should accept parameters" do
71
+ uri = @flickr.build(example_url(:flickr),
72
+ :maxwidth => 600,
73
+ :maxheight => 200,
74
+ :format => :xml,
75
+ :another => "test")
76
+
77
+ uri.query.include?("maxwidth=600").should be_true
78
+ uri.query.include?("maxheight=200").should be_true
79
+ uri.query.include?("format=xml").should be_true
80
+ uri.query.include?("another=test").should be_true
81
+ end
82
+
83
+ it "should build correctly when format is in the endpoint URL" do
84
+ uri = @qik.build(example_url(:qik), :format => :json)
85
+ uri.path.should == "/api/oembed.json"
86
+ end
87
+ end
88
+
89
+ describe "#raw" do
90
+ it "should return the body on 200" do
91
+ res = Net::HTTPOK.new("1.1", 200, "OK").instance_eval do
92
+ @body = "raw content"
93
+ @read = true
94
+ self
95
+ end
96
+ Net::HTTP.stub!(:start).and_return(res)
97
+
98
+ @flickr.raw(example_url(:flickr)).should == "raw content"
99
+ end
100
+
101
+ it "should raise error on 501" do
102
+ res = Net::HTTPNotImplemented.new("1.1", 501, "Not Implemented")
103
+ Net::HTTP.stub!(:start).and_return(res)
104
+
105
+ proc do
106
+ @flickr.raw(example_url(:flickr))
107
+ end.should raise_error(OEmbed::UnknownFormat)
108
+ end
109
+
110
+ it "should raise error on 404" do
111
+ res = Net::HTTPNotFound.new("1.1", 404, "Not Found")
112
+ Net::HTTP.stub!(:start).and_return(res)
113
+
114
+ proc do
115
+ @flickr.raw(example_url(:flickr))
116
+ end.should raise_error(OEmbed::NotFound)
117
+ end
118
+
119
+ it "should raise error on all other responses" do
120
+ Net::HTTPResponse::CODE_TO_OBJ.delete_if do |code, res|
121
+ ["200", "404", "501"].include?(code)
122
+ end.each do |code, res|
123
+ r = res.new("1.1", code, "Message")
124
+ Net::HTTP.stub!(:start).and_return(r)
125
+
126
+ proc do
127
+ @flickr.raw(example_url(:flickr))
128
+ end.should raise_error(OEmbed::UnknownResponse)
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "#get" do
134
+ it "should send the specified format" do
135
+ @flickr.should_receive(:raw).
136
+ with(example_url(:flickr), {:format=>:json}).
137
+ and_return(valid_response(:json))
138
+ @flickr.get(example_url(:flickr), :format=>:json)
139
+
140
+ @flickr.should_receive(:raw).
141
+ with(example_url(:flickr), {:format=>:xml}).
142
+ and_return(valid_response(:xml))
143
+ @flickr.get(example_url(:flickr), :format=>:xml)
144
+
145
+ lambda do
146
+ @flickr.should_receive(:raw).
147
+ with(example_url(:flickr), {:format=>:yml}).
148
+ and_return(valid_response(:json))
149
+ @flickr.get(example_url(:flickr), :format=>:yml)
150
+ end.should raise_error(OEmbed::FormatNotSupported)
151
+ end
152
+
153
+ it "should send the provider's format if none is specified" do
154
+ @flickr.should_receive(:raw).
155
+ with(example_url(:flickr), :format => @default).
156
+ and_return(valid_response(@default))
157
+ @flickr.get(example_url(:flickr))
158
+
159
+ @qik.should_receive(:raw).
160
+ with(example_url(:qik), :format=>:xml).
161
+ and_return(valid_response(:xml))
162
+ @qik.get(example_url(:qik))
163
+
164
+ @viddler.should_receive(:raw).
165
+ with(example_url(:viddler), :format=>:json).
166
+ and_return(valid_response(:json))
167
+ @viddler.get(example_url(:viddler))
168
+ end
169
+
170
+ it "should return OEmbed::Response" do
171
+ @flickr.stub!(:raw).and_return(valid_response(@default))
172
+ @flickr.get(example_url(:flickr)).is_a?(OEmbed::Response).should be_true
173
+ end
174
+ end
175
+ end