pummel 0.3.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.
@@ -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