├── spec ├── templates │ ├── test.haml │ ├── test.rhtml │ └── test.html.erb ├── oembed_links_test.yml ├── formatters │ ├── example_xml │ │ ├── flickr.xml │ │ ├── hulu.xml │ │ ├── youtube.xml │ │ └── viddler.xml │ └── lib_xml_spec.rb ├── spec_helper.rb └── oembed_links_spec.rb ├── rails └── init.rb ├── CREDIT ├── lib ├── oembed_links │ ├── formatters │ │ ├── json.rb │ │ ├── ruby_xml.rb │ │ ├── hpricot_xml.rb │ │ └── lib_xml.rb │ ├── fetchers │ │ ├── net_http.rb │ │ ├── curb.rb │ │ └── ruby_tubesday.rb │ ├── response.rb │ └── template_resolver.rb └── oembed_links.rb ├── Rakefile ├── History.txt ├── Manifest.txt ├── oembed_links_example.yml ├── oembed_links.gemspec └── README.txt /spec/templates/test.haml: -------------------------------------------------------------------------------- 1 | = data['url'] + ' haml' -------------------------------------------------------------------------------- /spec/templates/test.rhtml: -------------------------------------------------------------------------------- 1 | <%= data['url'] %> rhtml -------------------------------------------------------------------------------- /spec/templates/test.html.erb: -------------------------------------------------------------------------------- 1 | <%= data['url'] %> erb 2 | -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | require 'oembed_links' 2 | 3 | yaml_file = File.join(RAILS_ROOT, "config", "oembed_links.yml") 4 | if File.exists?(yaml_file) 5 | OEmbed::register_yaml_file(yaml_file) 6 | end 7 | 8 | -------------------------------------------------------------------------------- /CREDIT: -------------------------------------------------------------------------------- 1 | Thanks to the Indianapolis Star I&D department for open sourcing this; most notably Chris Vannoy for giving the okay. 2 | Thanks to Kyle Slattery for adding Viddler to the example oembed_links.yml 3 | 4 | Chris.Zelenak!indystar!com -------------------------------------------------------------------------------- /lib/oembed_links/formatters/json.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | class OEmbed 4 | module Formatters 5 | class JSON 6 | 7 | def name 8 | "json" 9 | end 10 | 11 | def format(txt) 12 | ::JSON.parse(txt) 13 | end 14 | 15 | end 16 | end 17 | end 18 | OEmbed.register_formatter(OEmbed::Formatters::JSON) 19 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'hoe' 3 | 4 | Hoe.new('oembed_links', "0.1.7") do |p| 5 | p.summary = "" 6 | p.url = 'http://indystar.com/' 7 | p.description = "Easy OEmbed integration for Ruby (and Rails)." 8 | p.rubyforge_name = 'oembed_links' 9 | p.extra_dev_deps = ['json'] 10 | p.developer('Indianapolis Star MD&D', 'bugs@indy.com') 11 | end 12 | 13 | -------------------------------------------------------------------------------- /lib/oembed_links/fetchers/net_http.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | 3 | class OEmbed 4 | module Fetchers 5 | class NetHTTP 6 | 7 | def name 8 | "NetHTTP" 9 | end 10 | 11 | def fetch(url) 12 | Net::HTTP.get(URI.parse(url)) 13 | end 14 | 15 | end 16 | end 17 | end 18 | 19 | OEmbed.register_fetcher(OEmbed::Fetchers::NetHTTP) 20 | -------------------------------------------------------------------------------- /lib/oembed_links/fetchers/curb.rb: -------------------------------------------------------------------------------- 1 | require 'curb' 2 | 3 | class OEmbed 4 | module Fetchers 5 | class Curb 6 | 7 | def initialize 8 | end 9 | 10 | def name 11 | "Curb" 12 | end 13 | 14 | def fetch(url) 15 | Curl::Easy.perform(url).body_str 16 | end 17 | 18 | end 19 | end 20 | end 21 | 22 | OEmbed.register_fetcher(OEmbed::Fetchers::Curb) 23 | -------------------------------------------------------------------------------- /lib/oembed_links/fetchers/ruby_tubesday.rb: -------------------------------------------------------------------------------- 1 | require 'ruby_tubesday' 2 | 3 | class OEmbed 4 | module Fetchers 5 | class RubyTubesday 6 | 7 | def initialize 8 | @client = ::RubyTubesday.new(:verify_ssl => false) 9 | end 10 | 11 | def name 12 | "RubyTubesday" 13 | end 14 | 15 | def fetch(url) 16 | @client.get(url) 17 | end 18 | 19 | end 20 | end 21 | end 22 | 23 | OEmbed.register_fetcher(OEmbed::Fetchers::RubyTubesday) 24 | -------------------------------------------------------------------------------- /spec/oembed_links_test.yml: -------------------------------------------------------------------------------- 1 | :config: 2 | :method: "NetHTTP" 3 | 4 | :providers: 5 | :test1: "http://test1/dude/{format}/" 6 | :test2: "http://test2.{format}" 7 | :test3: "http://test3" 8 | 9 | 10 | :test1: 11 | :format: "json" 12 | :schemes: 13 | - "http://test1.*/*" 14 | - "http://test1/*/test/*" 15 | 16 | :test2: 17 | :format: "xml" 18 | :schemes: 19 | - "http://test2.*/*/stuff/*" 20 | - "http://test2/foo/bar/*" 21 | 22 | :test3: 23 | :format: "json" 24 | :schemes: 25 | - "http://test3/foo/*" 26 | 27 | -------------------------------------------------------------------------------- /spec/formatters/example_xml/flickr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0 4 | photo 5 | Bacon Lollys 6 | ‮‭‬bees‬ 7 | http://www.flickr.com/photos/bees/ 8 | 9 | 3600 10 | Flickr 11 | http://www.flickr.com/ 12 | 500 13 | 375 14 | http://farm4.static.flickr.com/3040/2362225867_4a87ab8baf.jpg 15 | 16 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 0.1.6 / 2009-02-01 2 | 3 | * 1 minor enhancement 4 | 5 | * template resolver strips text on output 6 | 7 | === 0.1.3 / 2008-10-16 8 | 9 | * 1 minor enhancement 10 | 11 | * refactored no matching scheme into .none? matcher 12 | 13 | === 0.1.2 / 2008-10-16 14 | 15 | * 1 minor enhancement 16 | 17 | * add saimonmoore's nil response addition 18 | 19 | === 0.1.1 / 2008-10-16 20 | 21 | * 1 minor fix 22 | 23 | * fix stupid namespacing bug, doh 24 | 25 | === 0.1.0 / 2008-10-16 26 | 27 | * 2 minor enhancements 28 | 29 | * added Ruby Tubesday fetcher (http://github.com/DanaDanger/ruby_tubesday) 30 | * redid gem layout using Hoe 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | CREDIT 2 | History.txt 3 | Manifest.txt 4 | README.txt 5 | Rakefile 6 | lib 7 | lib/oembed_links 8 | lib/oembed_links.rb 9 | lib/oembed_links/fetchers 10 | lib/oembed_links/fetchers/net_http.rb 11 | lib/oembed_links/fetchers/ruby_tubesday.rb 12 | lib/oembed_links/formatters 13 | lib/oembed_links/formatters/hpricot_xml.rb 14 | lib/oembed_links/formatters/json.rb 15 | lib/oembed_links/formatters/lib_xml.rb 16 | lib/oembed_links/formatters/ruby_xml.rb 17 | lib/oembed_links/response.rb 18 | lib/oembed_links/template_resolver.rb 19 | oembed_links.gemspec 20 | oembed_links_example.yml 21 | rails 22 | rails/init.rb 23 | spec 24 | spec/oembed_links_spec.rb 25 | spec/oembed_links_test.yml 26 | spec/spec_helper.rb 27 | spec/templates 28 | spec/templates/test.haml 29 | spec/templates/test.html.erb 30 | spec/templates/test.rhtml 31 | -------------------------------------------------------------------------------- /spec/formatters/example_xml/hulu.xml: -------------------------------------------------------------------------------- 1 | 2 | http://www.hulu.com/video296NBC3600http://thumbnails.hulu.com/8/500/23678_145x80_manicured__SIXr9mO9sUO3dYt+S-XTIw.jpg<object width="512" height="296"><param name="movie" value="http://www.hulu.com/embed/0-njKp22bl4GivFXH0lh5w"></param><embed src="http://www.hulu.com/embed/0-njKp22bl4GivFXH0lh5w" type="application/x-shockwave-flash" width="512" height="296"></embed></object>5121451.080HuluWed, May 21, 2008 (Late Night with Conan O'Brien) 3 | -------------------------------------------------------------------------------- /lib/oembed_links/formatters/ruby_xml.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | 3 | 4 | class OEmbed 5 | module Formatters 6 | class RubyXML 7 | 8 | def name 9 | "xml" 10 | end 11 | 12 | # This is an extremely naive XML doc to hash 13 | # formatter. Cases like arrays represented in 14 | # XML will not work; only strings, ints and 15 | # floats will be converted. 16 | def format(txt) 17 | doc = ::REXML::Document.new(txt) 18 | h = { } 19 | doc.elements.each("/oembed/*") do |elem| 20 | c = elem.text 21 | if c =~ /^[0-9]+$/ 22 | c = c.to_i 23 | elsif c=~ /^[0-9]+\.[0-9]+$/ 24 | c = c.to_f 25 | end 26 | h[elem.name.strip] = c 27 | end 28 | return h 29 | end 30 | 31 | end 32 | end 33 | end 34 | 35 | -------------------------------------------------------------------------------- /spec/formatters/example_xml/youtube.xml: -------------------------------------------------------------------------------- 1 | 2 | http://www.youtube.com/Auto-Tune the News #8: dragons. geese. Michael Vick. (ft. T-Pain)<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/bDOYN-6gdRE&fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/bDOYN-6gdRE&fs=1" type="application/x-shockwave-flash" width="425" height="344" allowscriptaccess="always" allowfullscreen="true"></embed></object>schmoyoho3444251.0http://www.youtube.com/user/schmoyohoYouTubevideo 3 | -------------------------------------------------------------------------------- /lib/oembed_links/formatters/hpricot_xml.rb: -------------------------------------------------------------------------------- 1 | require 'hpricot' 2 | 3 | class OEmbed 4 | module Formatters 5 | class HpricotXML 6 | 7 | def name 8 | "xml" 9 | end 10 | 11 | 12 | 13 | # This is an extremely naive XML doc to hash 14 | # formatter. Cases like arrays represented in 15 | # XML will not work; only strings, ints and 16 | # floats will be converted. 17 | def format(txt) 18 | doc = ::Hpricot.XML(txt) 19 | h = { } 20 | (doc/"/oembed/*").each do |elem| 21 | if elem.is_a? Hpricot::Elem 22 | c = elem.innerHTML 23 | if c =~ /^[0-9]+$/ 24 | c = c.to_i 25 | elsif c=~ /^[0-9]+\.[0-9]+$/ 26 | c = c.to_f 27 | end 28 | h[elem.name.strip] = c 29 | end 30 | end 31 | return h 32 | end 33 | 34 | end 35 | end 36 | end 37 | 38 | -------------------------------------------------------------------------------- /lib/oembed_links/formatters/lib_xml.rb: -------------------------------------------------------------------------------- 1 | require 'libxml' 2 | 3 | class OEmbed 4 | module Formatters 5 | class LibXML 6 | 7 | def name 8 | "xml" 9 | end 10 | 11 | # This is an extremely naive XML doc to hash 12 | # formatter. Cases like arrays represented in 13 | # XML will not work; only strings, ints and 14 | # floats will be converted. 15 | def format(txt) 16 | parser = ::LibXML::XML::Parser.string(txt) 17 | doc = parser.parse 18 | h = { } 19 | doc.root.children.each do |node| 20 | unless node.name.strip.empty? 21 | c = node.inner_xml.gsub("<", "<").gsub(">",">") 22 | if c =~ /^[0-9]+$/ 23 | c = c.to_i 24 | elsif c=~ /^[0-9]+\.[0-9]+$/ 25 | c = c.to_f 26 | end 27 | h[node.name.strip] = c 28 | end 29 | end 30 | return h 31 | end 32 | 33 | end 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "..", "lib") 2 | require 'oembed_links' 3 | 4 | module SpecHelper 5 | 6 | def url_provides(content) 7 | Net::HTTP.fake_content(content) 8 | end 9 | 10 | def clear_urls 11 | Net::HTTP.clear_fakes 12 | end 13 | 14 | end 15 | 16 | require 'net/http' 17 | module Net 18 | class HTTP 19 | 20 | def self.fake_content(content) 21 | @content = content 22 | end 23 | 24 | 25 | def self.clear_fakes 26 | @content = nil 27 | end 28 | 29 | def self.get(uri) 30 | return @content 31 | end 32 | 33 | end 34 | end 35 | 36 | 37 | require 'json' 38 | class FakeFetcher 39 | def name 40 | "fake_fetcher" 41 | end 42 | 43 | def fetch(url) 44 | { 45 | "url" => "fakecontent" 46 | }.to_json 47 | end 48 | end 49 | 50 | class FakeFormatter 51 | def name 52 | "fake_formatter" 53 | end 54 | 55 | def format(txt) 56 | { 57 | "url" => "http://fakesville" 58 | } 59 | end 60 | end 61 | 62 | 63 | -------------------------------------------------------------------------------- /spec/formatters/example_xml/viddler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0 4 | video 5 | 437 6 | 288 7 | iPhone macro lens demonstration 8 | 9 | http://www.viddler.com/explore/cdevroe/videos/424/ 10 | cdevroe 11 | http://www.viddler.com/explore/cdevroe/ 12 | Viddler 13 | http://www.viddler.com/ 14 | http://cdn-thumbs.viddler.com/thumbnail_2_1646c55_v1.jpg 15 | 16 | 437 17 | 288 18 | 20 | 21 | -------------------------------------------------------------------------------- /oembed_links_example.yml: -------------------------------------------------------------------------------- 1 | :config: 2 | :method: "NetHTTP" 3 | 4 | :providers: 5 | :oohembed: "http://oohembed.com/oohembed/" 6 | :vimeo: "http://www.vimeo.com/api/oembed.{format}" 7 | :hulu: "http://www.hulu.com/api/oembed.{format}" 8 | :flickr: "http://www.flickr.com/services/oembed" 9 | :viddler: "http://lab.viddler.com/services/oembed/" 10 | :youtube: "http://www.youtube.com/oembed" 11 | 12 | :youtube: 13 | :format: "json" 14 | :schemes: 15 | - "http://www.youtube.com/watch?v=*" 16 | 17 | :oohembed: 18 | :format: "json" 19 | :schemes: 20 | - "http://*.amazon.(com|co.uk|de|ca|jp)/*/(gp/product|o/ASIN|obidos/ASIN|dp)/*" 21 | - "http://*.amazon.(com|co.uk|de|ca|jp)/(gp/product|o/ASIN|obidos/ASIN|dp)/*" 22 | - "http://*.collegehumor.com/video:*" 23 | - "http://*.funnyordie.com/videos/*" 24 | - "http://video.google.com/videoplay?*" 25 | - "http://*.imdb.com/title/tt*/" 26 | - "http://*.metacafe.com/watch/*" 27 | - "http://*.slideshare.net/*" 28 | - "http://twitter.com/*/statuses/*" 29 | - "http://*.wikipedia.org/wiki/*" 30 | - "http://*.wordpress.com/[0-9]{4}/[0-9]{2}/[0-9]{2}/*" 31 | 32 | :vimeo: 33 | :format: "json" 34 | :schemes: 35 | - "http://vimeo.com/*" 36 | - "http://vimeo.com/groups/*/videos/*" 37 | 38 | :hulu: 39 | :format: "xml" 40 | :schemes: 41 | - "http://www.hulu.com/watch/*" 42 | 43 | :flickr: 44 | :format: "xml" 45 | :schemes: 46 | - "http://*.flickr.com/*" 47 | 48 | :viddler: 49 | :format: "xml" 50 | :schemes: 51 | - "http://*.viddler.com/*" 52 | -------------------------------------------------------------------------------- /spec/formatters/lib_xml_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "../spec_helper") 2 | require File.join(File.dirname(__FILE__), "../../lib/oembed_links/formatters/lib_xml.rb") 3 | 4 | describe OEmbed::Formatters::LibXML do 5 | 6 | describe "raw xml from youtube" do 7 | before(:each) do 8 | @youtube_xml = File.open( 9 | File.join(File.dirname(__FILE__), "example_xml/youtube.xml"), 10 | "rb" 11 | ).read 12 | @result = OEmbed::Formatters::LibXML.new.format(@youtube_xml) 13 | end 14 | 15 | it "parsed raw xml should contain some html" do 16 | @result["html"].empty?.should_not be_true 17 | end 18 | end 19 | 20 | describe "raw xml from flickr" do 21 | before(:each) do 22 | @flickr_xml = File.open( 23 | File.join(File.dirname(__FILE__), "example_xml/flickr.xml"), 24 | "rb" 25 | ).read 26 | @result = OEmbed::Formatters::LibXML.new.format(@flickr_xml) 27 | end 28 | 29 | it "parsed raw xml should contain a url" do 30 | @result["url"].should == "http://farm4.static.flickr.com/3040/2362225867_4a87ab8baf.jpg" 31 | end 32 | end 33 | 34 | describe "raw xml from viddler" do 35 | before(:each) do 36 | @viddler_xml = File.open( 37 | File.join(File.dirname(__FILE__), "example_xml/viddler.xml"), 38 | "rb" 39 | ).read 40 | @result = OEmbed::Formatters::LibXML.new.format(@viddler_xml) 41 | end 42 | 43 | it "parsed raw xml should contain some html" do 44 | @result["html"].empty?.should_not be_true 45 | end 46 | end 47 | 48 | describe "raw xml from hulu" do 49 | before(:each) do 50 | @hulu_xml = File.open( 51 | File.join(File.dirname(__FILE__), "example_xml/hulu.xml"), 52 | "rb" 53 | ).read 54 | @result = OEmbed::Formatters::LibXML.new.format(@hulu_xml) 55 | end 56 | 57 | it "parsed raw xml should contain some html" do 58 | @result["html"].empty?.should_not be_true 59 | end 60 | end 61 | 62 | end # Oembed 63 | -------------------------------------------------------------------------------- /oembed_links.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = %q{oembed_links} 3 | s.version = "0.1.9" 4 | 5 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 6 | s.authors = ["Indianapolis Star MD&D"] 7 | s.date = %q{2008-10-16} 8 | s.description = %q{Easy OEmbed integration for Ruby (and Rails).} 9 | s.email = ["bugs@indy.com"] 10 | s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"] 11 | s.files = ["CREDIT", "History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib", "lib/oembed_links", "lib/oembed_links.rb", "lib/oembed_links/fetchers", "lib/oembed_links/fetchers/net_http.rb", "lib/oembed_links/fetchers/ruby_tubesday.rb", "lib/oembed_links/fetchers/curb.rb", "lib/oembed_links/formatters", "lib/oembed_links/formatters/hpricot_xml.rb", "lib/oembed_links/formatters/json.rb", "lib/oembed_links/formatters/lib_xml.rb", "lib/oembed_links/formatters/ruby_xml.rb", "lib/oembed_links/response.rb", "lib/oembed_links/template_resolver.rb", "oembed_links.gemspec", "oembed_links_example.yml", "rails", "rails/init.rb", "spec", "spec/oembed_links_spec.rb", "spec/oembed_links_test.yml", "spec/spec_helper.rb", "spec/templates", "spec/templates/test.haml", "spec/templates/test.html.erb", "spec/templates/test.rhtml"] 12 | s.has_rdoc = true 13 | s.homepage = %q{http://indystar.com/} 14 | s.rdoc_options = ["--main", "README.txt"] 15 | s.require_paths = ["lib"] 16 | s.rubyforge_project = %q{oembed_links} 17 | s.rubygems_version = %q{1.3.0} 18 | s.summary = %q{} 19 | 20 | if s.respond_to? :specification_version then 21 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 22 | s.specification_version = 2 23 | 24 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 25 | s.add_development_dependency(%q) 26 | s.add_development_dependency(%q, [">= 1.8.0"]) 27 | s.add_dependency(%q) 28 | else 29 | s.add_dependency(%q) 30 | s.add_dependency(%q, [">= 1.8.0"]) 31 | end 32 | else 33 | s.add_dependency(%q) 34 | s.add_dependency(%q, [">= 1.8.0"]) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/oembed_links/response.rb: -------------------------------------------------------------------------------- 1 | # The class used to represent data returned by the server. 2 | # 3 | 4 | class OEmbed 5 | class Response 6 | 7 | def initialize(provider, url, response_object) 8 | @provider = provider 9 | @url = url 10 | @response = response_object || {} 11 | @rendered_via_provider = @rendered_via_regex = @rendered_via_type = false 12 | @rendered = nil 13 | end 14 | 15 | def to_s 16 | @response["html"] || @response["url"] 17 | end 18 | 19 | # If no content has been explicitly rendered for this Response, 20 | # the default representation of the data will be returned. 21 | def rendered_content 22 | @rendered || self.to_s 23 | end 24 | 25 | 26 | # Case where url has not matched at all 27 | def none?(*args, &block) 28 | if @response.keys.empty? && !has_rendered? 29 | return render_content(*args, &block) 30 | end 31 | end 32 | 33 | # Test if this response has been returned from 34 | # the given provider_name. 35 | def from?(provider_name, *args, &block) 36 | if @provider.to_s === provider_name.to_s 37 | if can_render_type?(:provider) 38 | @rendered_via_provider = true 39 | return render_content(*args, &block) 40 | end 41 | end 42 | end 43 | 44 | # Test if this response came from a URL 45 | # that matches the given regex. 46 | def matches?(regex, *args, &block) 47 | if @url =~ regex 48 | if can_render_type?(:regex) 49 | @rendered_via_regex = true 50 | render_content(*args, &block) 51 | end 52 | end 53 | end 54 | 55 | # Lowest priority renderer, which will execute 56 | # a block regardless of conditions so long as 57 | # no content has yet been rendered for this response. 58 | def any?(*args, &block) 59 | if can_render_type? 60 | return render_content(*args, &block) 61 | end 62 | end 63 | 64 | # Provides the mechanism to allow .audio?, .video? 65 | # and other .type? checking methods. The value of the 66 | # method name will be compared against the "type" field 67 | # from the returned server data. 68 | def method_missing(msym, *args, &block) 69 | mname = msym.to_s 70 | if mname[mname.size - 1, mname.size] == "?" 71 | ts = mname[0..mname.size - 2] 72 | if @response["type"] == ts 73 | if can_render_type?(:type) 74 | @rendered_via_type = true 75 | return render_content(*args, &block) 76 | end 77 | end 78 | else 79 | raise NoMethodError.new("No such method #{msym.to_s}", msym, *args) 80 | end 81 | end 82 | 83 | def has_rendered? 84 | !@rendered.nil? 85 | end 86 | 87 | private 88 | 89 | # Needlessly stupid priority for rendering. 90 | def can_render_type?(type = nil) 91 | if type == :provider 92 | !@rendered_via_provider 93 | elsif type == :regex 94 | !@rendered_via_provider && !@rendered_via_regex 95 | elsif type == :type 96 | !@rendered_via_provider && !@rendered_via_regex && !@rendered_via_type 97 | else 98 | !has_rendered? 99 | end 100 | end 101 | 102 | 103 | def render_content(*args, &block) 104 | options = (args.last.is_a?(Hash)) ? args.last : { } 105 | if options[:template] 106 | @rendered = TemplateResolver.eval_template_for_path(options[:template], @url, @response, self).strip 107 | elsif block_given? 108 | @rendered = yield(@response).strip 109 | else 110 | @rendered = self.to_s.strip 111 | end 112 | end 113 | 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/oembed_links/template_resolver.rb: -------------------------------------------------------------------------------- 1 | class OEmbed 2 | # The TemplateResolver class acts as a filesystem path resolver for finding 3 | # templates, as well as a template renderer. Currently support is enabled for 4 | # Haml, Erubis, and ERB templates; if you wish to force a particular type of processor 5 | # for any of the templates used, you may set it by specifing it with template_processor= 6 | # method. You can specify a base path for resolving template names with the template_root= 7 | # method. 8 | class TemplateResolver 9 | 10 | # Specify the base filesystem path for resolving templates. 11 | def self.template_root=(r) 12 | @template_root = r 13 | end 14 | 15 | # Get the current base filesystem path, or nil 16 | def self.template_root 17 | @template_root 18 | end 19 | 20 | # Specify the template processor to use for rendering templates; 21 | # this will be used regardless of file extension 22 | def self.template_processor=(p) 23 | p = p.to_s if p.is_a? Symbol 24 | raise "Unsupported processor type" unless ["erb", "haml", "erubis", nil].include?(p) 25 | @template_processor = p 26 | end 27 | 28 | # Return the current forced template processor, or nil 29 | def self.template_processor 30 | @template_processor 31 | end 32 | 33 | # Resolves the template path for the given (potentially relative) path 34 | # If the given path is found to exist immediately, whether by itself 35 | # or when combined with the optionally present template_root (OEmbed::TemplateResolver.template_root), 36 | # it is returned immediately. 37 | # 38 | # If no path is found for the supplied template path, an exception 39 | # is raised. 40 | def self.resolve_template_path(path) 41 | tmp_path = (@template_root) ? File.join(@template_root, path) : path 42 | found_path = nil 43 | if File.exists?(tmp_path) 44 | found_path = tmp_path 45 | end 46 | unless found_path 47 | raise StandardError.new("File not found: #{path}") 48 | else 49 | return found_path 50 | end 51 | end 52 | 53 | # Evaluate the template at the given (possibly relative) path, 54 | # assigning the template the local variables url, data and response. 55 | # If ApplicationController and ActionController have been defined, then 56 | # it is assumed that you are specifying a Rails template path, and an instance 57 | # ApplicationController will be used to render the results. You may use 58 | # Rails-style helpers / models inside your template, as the ActionView rendering 59 | # pipeline will be used. NOTE that to accomplish this, the Rails TestRequest 60 | # and TestResponse classes will be loaded and used, if they have not been loaded already. 61 | # 62 | # If no Rails style template was found or you are not using rails, the following actions take place: 63 | # If you specify a template processor (via OEmbed::TemplateResolver.template_processor=) 64 | # then it will be used to process the template at the given path (taking any configured template_root 65 | # into account. Otherwise a processor will be selected on the following criterion: 66 | # 67 | # If the file extension is haml, then the Haml library will be required and used 68 | # If the Erubis library has been loaded or the file extension is erubis, the Erubis library will be used 69 | # In all other cases, the ERB library will be used 70 | # 71 | 72 | # 73 | # The evaluated result of the template will be returned. 74 | def self.eval_template_for_path(path, url, data, response) 75 | rendered_response = nil 76 | if defined?(ApplicationController) && defined?(ActionController) 77 | if !defined?(ActionController::TestRequest) || 78 | !defined?(ActionController::TestResponse) 79 | require 'action_controller/test_process' 80 | end 81 | @app_c ||= ApplicationController.new 82 | rendered_response = @app_c.process(ActionController::TestRequest.new, 83 | ActionController::TestResponse.new, 84 | :render_for_file, 85 | path, 86 | 200, 87 | nil, 88 | { :data => data, 89 | :url => url, 90 | :response => response }).body 91 | end 92 | if rendered_response.nil? && actual_path = resolve_template_path(path) 93 | contents = File.read(actual_path) 94 | processor = (@template_processor || File.extname(actual_path)[1..4]).to_s 95 | has_erubis = defined?(Erubis) 96 | if processor == "haml" 97 | require 'haml' unless defined?(Haml) 98 | rendered_response = Haml::Engine.new(contents).render({ }, :data => data, :url => url, :response => response) 99 | elsif processor == "erubis" || has_erubis 100 | require 'erubis' unless has_erubis 101 | rendered_response = Erubis::Eruby.new(contents).result(:data => data, :url => url, :response => response) 102 | else 103 | require 'erb' unless defined?(ERB) 104 | rendered_response = ERBTemplate.new(url, data, response).evaluate(contents) 105 | end 106 | end 107 | return rendered_response.chomp 108 | end 109 | 110 | private 111 | 112 | class ERBTemplate 113 | attr_reader :url, :data, :response 114 | 115 | def initialize(u, d, r) 116 | @url = u 117 | @data = d 118 | @response = r 119 | end 120 | 121 | 122 | def evaluate(contents) 123 | ERB.new(contents).result(binding) 124 | end 125 | end 126 | 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | = oembed_links 2 | 3 | * http://indystar.com/ 4 | 5 | == DESCRIPTION: 6 | 7 | This is the oembed_links gem. It allows you to easily parse text and 8 | query configured providers for embedding information on the links 9 | inside the text. A sample configuration file for configuring the 10 | library has been included (oembed_links_example.yml), though you 11 | may also configure the library programmatically (see rdocs). 12 | 13 | == REQUIREMENTS: 14 | 15 | You must have the JSON gem installed to use oembed_links. 16 | If you have the libxml-ruby gem installed, oembed_links will use that; 17 | it will fall back to hpricot if that is installed, and finally REXML 18 | if you have nothing else. 19 | 20 | == SYNOPSIS: 21 | 22 | To get started quickly (in irb): 23 | 24 | require 'oembed_links' 25 | OEmbed.register({:method => "NetHTTP"}, 26 | {:flickr => "http://www.flickr.com/services/oembed/", 27 | :vimeo => "http://www.vimeo.com/api/oembed.{format}"}, 28 | {:flickr => { :format => "xml", :schemes => ["http://www.flickr.com/photos/*"]}, 29 | :vimeo => { :format => "json", :schemes => ["http://www.vimeo.com/*"]}}) 30 | 31 | # Simple transformation 32 | OEmbed.transform("This is my flickr URL http://www.flickr.com/photos/bees/2341623661/ and all I did was show the URL straight to the picture") 33 | 34 | # More complex transformation 35 | OEmbed.transform("This is my flickr URL http://www.flickr.com/photos/bees/2341623661/ and this is a vimeo URL http://www.vimeo.com/757219 wow neat") do |r, url| 36 | r.audio? { |a| "It's unlikely flickr or vimeo will give me audio" } 37 | r.photo? { |p| "Sweet, a photo named #{p["title"]}" } 38 | r.from?(:vimeo) { |v| "
#{v['html']}
" } 39 | end 40 | 41 | # Transformation to drive Amazon links to our department affiliate code and help us buy some laptops (hint) 42 | OEmbed.register_provider(:oohembed, 43 | "http://oohembed.com/oohembed/", 44 | "json", 45 | "http://*.amazon.(com|co.uk|de|ca|jp)/*/(gp/product|o/ASIN|obidos/ASIN|dp)/*", 46 | "http://*.amazon.(com|co.uk|de|ca|jp)/(gp/product|o/ASIN|obidos/ASIN|dp)/*") 47 | OEmbed.transform("Here is a link to amazon http://www.amazon.com/Complete-Aubrey-Maturin-Novels/dp/039306011X/ref=pd_bbs_sr_2 wow") do |res, url| 48 | res.matches?(/amazon/) { |d| 49 | unless url =~ /(&|\?)tag=[^&]+/i 50 | url += ((url.index("?")) ? "&" : "?") 51 | url += "tag=wwwindystarco-20" 52 | end 53 | <<-EOHTML 54 | 60 | EOHTML 61 | } 62 | end 63 | 64 | 65 | To get started quickly in Rails: 66 | 67 | Copy the included oembed_links_example.yml file to RAILS_ROOT/config/oembed_links.yml, 68 | add a dependency to the gem in your environment.rb ( config.gem "oembed_links" ) 69 | and start your server. That's it. If you'd like to transform the oembedded content via 70 | templates, you can do so using the following syntax: 71 | 72 | OEmbed.transform(text_to_transform) do |res, url| 73 | res.video?(:template => "oembed/video") 74 | res.from?(:a_provider, :template => "a_provider/oembed") 75 | res.matches?(/some_regex/, :template => "shared/oembed_link") 76 | res.any?(:template => "shared/oembed_link") 77 | end 78 | 79 | This presumes you have a directory in your Rails views directory called "oembed", and a file 80 | of the form "video.html.erb" or "video.rhtml". If you are not using Rails, you may still use the 81 | template functionality, but you must specify the full path to the template. When you are integrating 82 | with Rails, you may use any template library that is installed for your Rails app; when you are using 83 | the absolute filename method, you only have access to ERB, Erubis or HAML. 84 | 85 | As of version 0.0.9, your Rails oembed templates have access to all the traditional Rails template helper methods 86 | (such as Rails' link_to, image_tag, etc.); the templates are processed using the Rails template rendering 87 | pipeline, and as such may even do evil things like access your Models. 88 | 89 | See the RDocs for OEmbed::TemplateResolver for more information regarding templates and oembed_links. 90 | 91 | See the rdocs for much more complete examples. The specs directory has some examples of programmatic 92 | use, but the test to code ratio is slim atm. 93 | 94 | == INSTALL: 95 | 96 | sudo gem install oembed_links 97 | (from github) 98 | gem sources -a http://gems.github.com 99 | sudo gem install netshade-oembed_links 100 | 101 | == LICENSE: 102 | 103 | (The MIT License) 104 | 105 | Copyright (c) 2008 Indianapolis Star 106 | 107 | Permission is hereby granted, free of charge, to any person obtaining 108 | a copy of this software and associated documentation files (the 109 | 'Software'), to deal in the Software without restriction, including 110 | without limitation the rights to use, copy, modify, merge, publish, 111 | distribute, sublicense, and/or sell copies of the Software, and to 112 | permit persons to whom the Software is furnished to do so, subject to 113 | the following conditions: 114 | 115 | The above copyright notice and this permission notice shall be 116 | included in all copies or substantial portions of the Software. 117 | 118 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 119 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 120 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 121 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 122 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 123 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 124 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 125 | -------------------------------------------------------------------------------- /spec/oembed_links_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "spec_helper") 2 | 3 | describe OEmbed, "registration tasks" do 4 | include SpecHelper 5 | 6 | before(:each) do 7 | OEmbed.clear_registrations 8 | OEmbed.load_default_libs 9 | clear_urls 10 | end 11 | 12 | it "should default to NetHTTP if no method is specified in configuration" do 13 | OEmbed.register() 14 | OEmbed.instance_variable_get("@fetch_method").should == "NetHTTP" 15 | end 16 | 17 | it "should throw an error if you provide provider URLs but no scheme or format information" do 18 | lambda { OEmbed.register({ }, { :fake => "http://fake" })}.should raise_error 19 | end 20 | 21 | it "should throw an error if you don't provide any scheme urls for registration" do 22 | lambda { OEmbed.register({ }, { :fake => "http://fake" }, { :fake => { :format => "json", :schemes => []}})}.should raise_error 23 | end 24 | 25 | it "should default to json format if none is specified" do 26 | OEmbed.register({ }, { :fake => "http://fake" }, { :fake => { :schemes => ["http://fake/*"]}}) 27 | OEmbed.instance_variable_get("@formats")[:fake].should == "json" 28 | end 29 | 30 | it "should support loading a configuration via YAML" do 31 | OEmbed.register_yaml_file(File.join(File.dirname(__FILE__), "oembed_links_test.yml")) 32 | OEmbed.instance_variable_get("@urls").size.should == 3 33 | end 34 | 35 | it "should support ad hoc addition of providers" do 36 | OEmbed.register_yaml_file(File.join(File.dirname(__FILE__), "oembed_links_test.yml")) 37 | OEmbed.instance_variable_get("@urls").size.should == 3 38 | OEmbed.register_provider(:test4, "http://test4/oembed.{format}", "xml", "http://test4.*/*", "http://test4.*/foo/*") 39 | OEmbed.instance_variable_get("@urls").size.should == 4 40 | end 41 | 42 | it "should support adding new fetchers" do 43 | 44 | OEmbed.register_fetcher(FakeFetcher) 45 | OEmbed.register({ :method => "fake_fetcher"}, 46 | { :fake => "http://fake" }, 47 | { :fake => { 48 | :format => "json", 49 | :schemes => "http://fake/*" 50 | }}) 51 | OEmbed.transform("http://fake/bar/baz").should == "fakecontent" 52 | end 53 | 54 | it "should support adding new formatters" do 55 | OEmbed.register_formatter(FakeFormatter) 56 | OEmbed.register({ :method => "fake_fetcher"}, 57 | { :fake => "http://fake" }, 58 | { :fake => { 59 | :format => "fake_formatter", 60 | :schemes => "http://fake/*" 61 | }}) 62 | url_provides("") 63 | OEmbed.transform("http://fake/bar/baz").should == "http://fakesville" 64 | end 65 | 66 | it "should allow selective loading of formatters through the load_default_libs function" do 67 | OEmbed.clear_registrations 68 | OEmbed.load_default_libs 69 | OEmbed.instance_variable_get("@formatters")["xml"].class.should == OEmbed::Formatters::LibXML 70 | OEmbed.clear_registrations 71 | OEmbed.load_default_libs("libxml") 72 | OEmbed.instance_variable_get("@formatters")["xml"].class.should == OEmbed::Formatters::HpricotXML 73 | OEmbed.clear_registrations 74 | OEmbed.load_default_libs("libxml", "hpricot") 75 | OEmbed.instance_variable_get("@formatters")["xml"].class.should == OEmbed::Formatters::RubyXML 76 | end 77 | 78 | it "should support the hpricot formatter" do 79 | OEmbed.clear_registrations 80 | OEmbed.load_default_libs("libxml") 81 | url_provides(<<-EOXML) 82 | 83 | 84 | bar 85 | 86 | EOXML 87 | OEmbed.register_provider(:test, "http://test4/oembed.{format}", "xml", "http://test.*/*") 88 | OEmbed.transform("http://test.com/bar/baz").should == "bar" 89 | end 90 | 91 | it "should support the rexml formatter" do 92 | OEmbed.clear_registrations 93 | OEmbed.load_default_libs("libxml", "hpricot") 94 | url_provides(<<-EOXML) 95 | 96 | 97 | barxml 98 | 99 | EOXML 100 | OEmbed.register_provider(:test, "http://test4/oembed.{format}", "xml", "http://test.*/*") 101 | OEmbed.transform("http://test.com/bar/baz").should == "barxml" 102 | end 103 | 104 | 105 | 106 | end 107 | 108 | describe OEmbed, "transforming functions" do 109 | include SpecHelper 110 | before(:each) do 111 | OEmbed.clear_registrations 112 | OEmbed.load_default_libs 113 | clear_urls 114 | url_provides({ 115 | "html" => "foo", 116 | "type" => "video" 117 | }.to_json) 118 | OEmbed.register_yaml_file(File.join(File.dirname(__FILE__), "oembed_links_test.yml")) 119 | @current_path = File.dirname(__FILE__) 120 | @template_path = File.join(@current_path, "templates") 121 | end 122 | 123 | it "should always give priority to provider conditional blocks" do 124 | OEmbed.transform("http://test1.net/foo") do |r, url| 125 | r.none? { "none" } 126 | r.any? { |a| "any" } 127 | r.video? { |v| "video" } 128 | r.from?(:test1) { |t| "test1" } 129 | r.matches?(/./) { |m| "regex" } 130 | end.should == "test1" 131 | end 132 | 133 | it "should always give priority regex conditional blocks over all others except provider" do 134 | OEmbed.transform("http://test1.net/foo") do |r, url| 135 | r.none? { "none" } 136 | r.any? { |a| "any" } 137 | r.video? { |v| "video" } 138 | r.matches?(/./) { |m| "regex" } 139 | end.should == "regex" 140 | OEmbed.transform("http://test1.net/foo") do |r, url| 141 | r.matches?(/./) { |m| "regex" } 142 | r.any? { |a| "any" } 143 | r.video? { |v| "video" } 144 | end.should == "regex" 145 | end 146 | 147 | it "should recognize the type of content and handle the conditional block appropriately" do 148 | OEmbed.transform("http://test1.net/foo") do |r, url| 149 | r.none? { "none" } 150 | r.any? { |a| "any" } 151 | r.video? { |v| "video" } 152 | end.should == "video" 153 | url_provides({ 154 | "html" => "bar", 155 | "type" => "hedgehog" 156 | }.to_json) 157 | OEmbed.transform("http://test1.net/foo") do |r, url| 158 | r.video? { |v| "video" } 159 | r.hedgehog? { |v| "hedgey"} 160 | end.should == "hedgey" 161 | end 162 | 163 | it "should still output the content of a url if no transforming blocks match it" do 164 | OEmbed.transform("http://test1.net/foo") do |r, url| 165 | r.audio? { |a| "audio" } 166 | r.hedgehog? { |v| "hedgey"} 167 | r.from?(:test2) { |t| "test2" } 168 | r.matches?(/baz/) { |m| "regex" } 169 | end.should == "http://test1.net/foo" 170 | end 171 | 172 | it "should transform only urls which have registered providers" do 173 | OEmbed.transform("http://test1.net/foo and http://not.a.valid.url.host/fake are urls") do |r, url| 174 | r.video? { |v| "video" } 175 | end.should == "video and http://not.a.valid.url.host/fake are urls" 176 | end 177 | 178 | it "should pass control to the .none? block if no scheme matched" do 179 | OEmbed.transform("http://not.a.valid.url.host/fake") do |r, url| 180 | r.none? { "nomatch" } 181 | r.audio? { |a| "audio" } 182 | r.hedgehog? { |v| "hedgey"} 183 | r.from?(:test2) { |t| "test2" } 184 | r.matches?(/baz/) { |m| "regex" } 185 | end.should == "nomatch" 186 | end 187 | 188 | it "should allow templates to be used to process the output" do 189 | url_provides({ 190 | "url" => "template!" 191 | }.to_json) 192 | 193 | OEmbed.transform("http://test1.net/foo") do |r, url| 194 | r.any?(:template => File.join(@template_path, "test.rhtml")) 195 | end.should == "template! rhtml" 196 | end 197 | 198 | 199 | it "should allow a template root to be set programmatically to process the output" do 200 | url_provides({ 201 | "url" => "template!" 202 | }.to_json) 203 | OEmbed::TemplateResolver.template_root = @template_path 204 | OEmbed.transform("http://test1.net/foo") do |r, url| 205 | r.any?(:template => "test.html.erb") 206 | end.should == "template! erb" 207 | end 208 | 209 | it "should allow for selection of different template renderers" do 210 | url_provides({ 211 | "url" => "template!" 212 | }.to_json) 213 | defined?(Erubis).should_not == true 214 | defined?(Haml).should_not == true 215 | OEmbed::TemplateResolver.template_root = @template_path 216 | OEmbed::TemplateResolver.template_processor = :erubis 217 | OEmbed.transform("http://test1.net/foo") do |r, url| 218 | r.any?(:template => "test.html.erb") 219 | end.should == "template! erb" 220 | defined?(Erubis).should == "constant" 221 | OEmbed::TemplateResolver.template_processor = :haml 222 | OEmbed.transform("http://test1.net/foo") do |r, url| 223 | r.any?(:template => "test.haml") 224 | end.should == "template! haml" 225 | defined?(Haml).should == "constant" 226 | end 227 | 228 | it "should throw an exception if a path to a template specified does not exist" do 229 | url_provides({ 230 | "url" => "template!" 231 | }.to_json) 232 | lambda { OEmbed.transform("http://test1.net/foo") do |r, url| 233 | r.any?(:template => "does.not.exist") 234 | end }.should raise_error 235 | end 236 | 237 | 238 | end 239 | 240 | 241 | describe OEmbed, "Rails template resolving functions" do 242 | include SpecHelper 243 | before(:each) do 244 | OEmbed.clear_registrations 245 | OEmbed.load_default_libs 246 | clear_urls 247 | url_provides({ 248 | "html" => "foo", 249 | "type" => "video", 250 | "url" => "rails" 251 | }.to_json) 252 | OEmbed.register_yaml_file(File.join(File.dirname(__FILE__), "oembed_links_test.yml")) 253 | OEmbed::TemplateResolver.template_processor = nil 254 | @current_path = File.dirname(__FILE__) 255 | @template_path = File.join(@current_path, "templates") 256 | 257 | require 'actionpack' 258 | require 'action_controller' 259 | require 'action_controller/test_process' 260 | 261 | class ::ApplicationController < ActionController::Base 262 | 263 | end 264 | ApplicationController.view_paths += [File.dirname(__FILE__)] 265 | ActiveSupport::Dependencies.mark_for_unload(ApplicationController) 266 | end 267 | 268 | it "should support Rails-like template paths for template selection" do 269 | 270 | OEmbed.transform("http://test1.net/foo") do |r, url| 271 | r.any?(:template => ("templates/test")) 272 | end.should == "rails erb" 273 | end 274 | 275 | it "should support Rails-like template paths with extension specified" do 276 | OEmbed.transform("http://test1.net/foo") do |r, url| 277 | r.any?(:template => "templates/test.rhtml") 278 | end.should == "rails rhtml" 279 | end 280 | 281 | end 282 | 283 | 284 | 285 | 286 | 287 | -------------------------------------------------------------------------------- /lib/oembed_links.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | require 'uri' 3 | require 'yaml' 4 | require 'oembed_links/template_resolver' 5 | require 'oembed_links/response' 6 | 7 | # The OEmbed class is the interface to class registration functions 8 | # such as register_provider, register_formatter, etc., as well as 9 | # the seat of the main .transform() function. If you are using OEmbed 10 | # inside a Rails application, the process of initialization will be 11 | # handled for you by the init.rb file. Create a file called 12 | # oembed_links.yml inside your RAILS_ROOT/config directory, giving it 13 | # a format like: 14 | # 15 | # :config: 16 | # :method: "NetHTTP" 17 | # 18 | # :providers: 19 | # :provider_1: "http://provider1.com/oembed.{format}" 20 | # :provider_2: "http://provider2.com/oembed.{format}" 21 | # 22 | # :provider_1: 23 | # :format: "json" 24 | # :schemes: 25 | # - "http://provider1.com/links/*/user/*" 26 | # - "http://provider1.com/photos/*/user/*" 27 | # 28 | # :provider_2: 29 | # :format: "xml" 30 | # :schemes: 31 | # - "http://provider2.com/videos/*" 32 | # 33 | # 34 | # If you are not using the library in a Rails app, you can still create 35 | # a YAML file like above and register it using OEmbed.register_yaml_file("/path/to/file.yml") 36 | # 37 | # You may also programmatically register information into the app using the 38 | # register function, which takes a hash of configuration options, a 39 | # provider hash, and the provider scheme hash. 40 | # 41 | # You may also register providers ad hoc using the OEmbed.register_provider 42 | # function. 43 | # 44 | # To transform text, use the OEmbed.transform function, like: 45 | # 46 | # OEmbed.transform("This is my text and here's a picture http://www.flickr.com/path/to/a/photo") 47 | # 48 | # OEmbed.transform("Same text http://youtube.com/videos/somevideo") do |r, url| 49 | # r.from?(:youtube) { |vid| vid["html"] } 50 | # end 51 | # 52 | # OEmbed.transform("Some more text from http://en.wikipedia.com/wiki/Dinosaurs!") do |r, url| 53 | # r.from?(:wikipedia, :template => "links/wiki_links") 54 | # end 55 | # 56 | # See the OEmbed.transform function for more details. 57 | # 58 | # 59 | # The OEmbed library by default uses net/http, libxml and json libraries for 60 | # fetching network data, parsing xml and parsing json, respectively. These 61 | # libraries are loaded by default. 62 | # 63 | # If you want to write your own mechanism for fetching HTTP data, parsing XML 64 | # data, JSON data, or some other format, see the OEmbed::Fetchers::NetHTTP and 65 | # OEmbed::Formatters::JSON for simple examples in terms of API. Once your new 66 | # class mirrors these classes, you can register it with OEmbed by using 67 | # OEmbed.register_formatter(class) or OEmbed.register_fetcher(class). 68 | # 69 | # NOTE that the default formatters and fetcher are EXTREMELY naive. There is no 70 | # attempt at error handling, connection timeouts, or the like. If you need richer 71 | # functionality you should subclass the existing formatters / fetchers and register them. 72 | # 73 | class OEmbed 74 | 75 | # Configure OEmbed with all necessary information - library configuration, 76 | # oembed provider urls, and the supported schemes and formats of said providers. 77 | # 78 | # The configuration hash should follow the form: 79 | # { :method => "NameOfFetcher" } 80 | # Note that the name of the fetcher is NOT the classname, but the arbitrarily 81 | # chosen name provided by that class' .name() method. By default, it will 82 | # be NetHTTP. 83 | # 84 | # The provider hash will be a hash where the keys are the symbolic names of the 85 | # providers, eg. :vimeo, and the values are the URL strings used to query those providers. 86 | # You may use the substring {format} inside these URLs to indicate that the 87 | # given provider URL requires the format desired to be inserted at that point. 88 | # Whatever format you have configured that provider for in the provider_scheme_hash 89 | # will be inserted when they are queried. 90 | # 91 | # The provider_scheme_hash is a hash with two keys - the first key is the format 92 | # key, which will either be the string "json" or the string "xml". The other 93 | # key will be the schemes key, which contains an array of supported URL schemes 94 | # by the provider. 95 | # 96 | # It is assumed that all hashes passed in use symbols for keys. Do not use string 97 | # keys. This decision is totally arbitrary and without any technical merit. 98 | # 99 | # It is assumed that all provider names are symbols. Same rules as above. 100 | # 101 | def self.register(config_hash = { }, provider_hash = { }, provider_scheme_hash = { }) 102 | @fetch_method = (config_hash[:method] || "NetHTTP") 103 | @config = config_hash 104 | provider_hash.each do |provider, url| 105 | config = provider_scheme_hash[provider] 106 | raise "No Schemes were provided for #{provider.to_s}" if config.nil? || 107 | config[:schemes].nil? || 108 | config[:schemes].empty? 109 | self.register_provider(provider, url, config[:format] || "json", *config[:schemes]) 110 | end 111 | end 112 | 113 | # The configuration hash passed into register() or parsed from the YAML file 114 | def self.config 115 | @config 116 | end 117 | 118 | # Register a provider with OEmbed. The provider name should be a symbol, 119 | # like :flickr. The URL should be a string representing the endpoint 120 | # for that provider, and may include the {format} substring to indicate 121 | # how that provider should be notified of the desired format. 122 | # format is either the string "json", or the string "xml". 123 | # The list of schemes is an array of strings representing the different 124 | # URL schemes supported by the provider. These strings follow the form: 125 | # 126 | # http://*.somedomain.*/*/* 127 | # 128 | # All schemes should use * to indicate variable text matched until the 129 | # next non-* character or end of line. 130 | def self.register_provider(provider, url, format = "json", *schemes) 131 | @schemes ||= [ ] 132 | @urls ||= { } 133 | @formats ||= { } 134 | 135 | @formats[provider] = format 136 | @urls[provider] = url.gsub(/\{format\}/i, format) 137 | schemes.each do |scheme| 138 | sanitized_scheme = scheme.gsub(/([\.\?])/) { |str| "\\#{$1}" }.gsub(/\*/, '.+?') 139 | @schemes << [Regexp.new("^" + sanitized_scheme + "$"), provider] 140 | end 141 | end 142 | 143 | # Loads the YAML file at the specified path and registers 144 | # its information with OEmbed. 145 | def self.register_yaml_file(file) 146 | y = YAML.load(File.read(file)) 147 | self.register(y.delete(:config), 148 | y.delete(:providers), 149 | y) 150 | end 151 | 152 | # Clear all registration information; really only valuable in testing 153 | def self.clear_registrations() 154 | @schemes = [] 155 | @urls = { } 156 | @formats = { } 157 | @formatters = { } 158 | @fetchers = { } 159 | end 160 | 161 | # Load the default JSON and XML formatters, autodetecting 162 | # formatters when possible; load the default fetcher as well 163 | def self.load_default_libs(*ignore_formats) 164 | self.autodetect_xml_formatters(*ignore_formats) 165 | require 'oembed_links/formatters/json' 166 | self.register_formatter(OEmbed::Formatters::JSON) 167 | require 'oembed_links/fetchers/net_http' 168 | self.register_fetcher(OEmbed::Fetchers::NetHTTP) 169 | end 170 | 171 | # Register a new formatter. klass is the class object of the desired formatter. 172 | # A new instance of klass will be created and stored. This instance MUST 173 | # respond to the methods "name" and "format". 174 | def self.register_formatter(klass) 175 | @formatters ||= { } 176 | inst = klass.new 177 | @formatters[inst.name] = inst 178 | end 179 | 180 | # Register a new fetcher. klass is the class object of the desired fetcher. 181 | # A new instance of klass will be created and stored. This instance MUST 182 | # respond to the methods "name" and "fetch". 183 | def self.register_fetcher(klass) 184 | @fetchers ||= { } 185 | inst = klass.new 186 | @fetchers[inst.name] = inst 187 | end 188 | 189 | # Get the OEmbed::Response object for a given URL and provider. If you wish 190 | # to pass extra attributes to the provider, provide a hash as the last attribute 191 | # with keys and values representing the keys and values of the added querystring 192 | # parameters. 193 | def self.get_url_for_provider(url, provider, *attribs) 194 | purl = @urls[provider] 195 | eurl = CGI.escape(url) 196 | purl += (purl.index("?")) ? "&" : "?" 197 | purl += "url=#{eurl}" 198 | attrib_hash = (attribs.last.is_a?(Hash)) ? attribs.last : { } 199 | attrib_hash.each do |k, v| 200 | purl += "&#{CGI.escape(k)}=#{CGI.escape(v)}" 201 | end 202 | fetcher = @fetchers[@fetch_method] || @fetchers[@fetchers.keys.first] 203 | formatter = @formatters[@formats[provider]] 204 | response = fetcher.fetch(purl) 205 | formatter.format(response) 206 | end 207 | 208 | # Transform all URLs supported by configured providers by the passed-in 209 | # block or by outputting their "html" attributes ( or "url" attributes 210 | # if no "html" attribute exists ). You may pass in a hash to specify 211 | # extra parameters that should be appended to the querystrings to any 212 | # matching providers (see OEmbed.get_url_for_provider). If you pass a 213 | # block to this method, that block will be executed for each URL 214 | # found in txt that has a matching provider. This block will be passed 215 | # the OEmbed::Response object representing the embedding information for that 216 | # url. 217 | # 218 | # OEmbed.transform supports two additional parameters: 219 | # 220 | # use_strict: Optionally use Ruby's stricter URI detection regex. While 221 | # this will be technically correct regex, not all URLs 222 | # use correct syntax. If this is false, URLs will be detected 223 | # by the incredibly naive approach of finding any instance of 224 | # http:// or https://, and finding anything that is not whitespace 225 | # after that. 226 | # 227 | # Example: 228 | # OEmbed.transform("all my urls are correctly formed", true) 229 | # 230 | # (options hash): This hash is used to append extra querystring parameters 231 | # to the oembed provider. For example: 232 | # OEmbed.transform("blah", false, :max_width => 320, :max_height => 200) 233 | # 234 | # You may fine tune the appearance of the embedding information by using the 235 | # following forms: 236 | # 237 | # OEmbed.transform(some_string) do |r, url| 238 | # r.from?(:provider_name) { |content| content["html"] } 239 | # r.matches?(/some_regex_against_the_url/) { |content| content["title"] } 240 | # r.video?(:template => "videos/oembed_link") 241 | # r.audio? { |audio| content["html"] } 242 | # r.hedgehog?(:template => File.join(File.dirname(__FILE__), "templates", "hedgehogs.haml")) 243 | # r.photo? { |photo| "" } 244 | # r.any? { |anythingelse| content["title"] } 245 | # end 246 | # 247 | # The priority of these conditions is as follows: 248 | # The first matching block for provider (.from?(:provider_name)) takes precendence OVER 249 | # The first matching block for a URL regex (.matches?(/some_regex_against_the_url/)) takes precedence OVER 250 | # The first matching block for a type equivalent (.video?, .audio?, .hedgehog?, .photo?) takes precendence OVER 251 | # The match anything block (.any?) 252 | # 253 | # 254 | # 255 | # 256 | # You do not need to specify an .any? block if you do not intend to perform any special 257 | # transformations upon its data; the OEmbed::Response object will output either its html attribute 258 | # (if it exists) or its url attribute. 259 | # 260 | # The value passed to these conditional blocks is a hash representing the data returned 261 | # by the server. The keys of all the attributes will be strings. 262 | # 263 | # If you specify the :template option, a template will be found for you based on your current engironment. 264 | # Currently there is support for Haml, Erubis and ERB templates. Each template will have the following 265 | # local variables available to it: 266 | # 267 | # url : The URL for which OEmbed data exists 268 | # data : A hash of the actual OEmbed data for that URL 269 | # response : The OEmbed::Response object for the URL 270 | # 271 | # 272 | # If you are using Rails, you may specify your template relative to your application's 273 | # view root (eg "photos/flickr_oembed"), and your template will be found based on your application settings. 274 | # For more options regarding template support, see the documentation for OEmbed::TemplateResolver. 275 | # 276 | # NOTE: The type equivalent blocks (.video?, .audio?, .hedgehog?, .photo?, etc.) perform 277 | # an equality test between the method name and the type returned by the OEmbed provider. 278 | # You may specify any type name you wish as the method name, and its type will be checked 279 | # appropriately (as shown by the obviously trivial .hedgehog? method name). 280 | # 281 | def self.transform(txt, use_strict = false, *attribs, &block) 282 | ret = txt.dup 283 | 284 | if use_strict 285 | URI.extract(txt, "http") do |u| 286 | transform_url_for_text!(u, ret, *attribs, &block) 287 | end 288 | else 289 | simple_extract(txt) do |u| 290 | transform_url_for_text!(u, ret, *attribs, &block) 291 | end 292 | end 293 | 294 | return ret 295 | end 296 | 297 | # Determine the XML formatter that can be loaded for 298 | # this system based on what libraries are present 299 | def self.autodetect_xml_formatters(*ignore) 300 | loaded_lib = false 301 | unless ignore.include? "libxml" 302 | begin 303 | require 'libxml' 304 | require 'oembed_links/formatters/lib_xml' 305 | self.register_formatter(OEmbed::Formatters::LibXML) 306 | loaded_lib = true 307 | rescue LoadError 308 | # Silently fail: LibXML XML formatter not found 309 | end 310 | end 311 | unless loaded_lib || ignore.include?("hpricot") 312 | begin 313 | require 'hpricot' 314 | require 'oembed_links/formatters/hpricot_xml' 315 | self.register_formatter(OEmbed::Formatters::HpricotXML) 316 | loaded_lib = true 317 | rescue LoadError 318 | # Silently fail: Hpricot XML formatter not found 319 | end 320 | end 321 | unless loaded_lib || ignore.include?("rexml") 322 | require 'oembed_links/formatters/ruby_xml' 323 | self.register_formatter(OEmbed::Formatters::RubyXML) 324 | loaded_lib = true 325 | end 326 | raise StandardError.new("No XML formatter could be autodetected") unless loaded_lib 327 | end 328 | 329 | private 330 | 331 | # stupid simple copy of URI.extract to allow for looser URI detection 332 | def self.simple_extract(str, &block) 333 | reg = /(https?:\/\/[^\s]+)/i 334 | if block_given? 335 | str.scan(reg) { yield $& } 336 | nil 337 | else 338 | result = [] 339 | str.scan(reg) { result.push $& } 340 | result 341 | end 342 | end 343 | 344 | # extraction of inner loop of .transform(), to allow for easier 345 | # parameterization of OEmbed 346 | def self.transform_url_for_text!(u, txt, *attribs, &block) 347 | unless (vschemes = @schemes.select { |a| u =~ a[0] }).empty? 348 | regex, provider = vschemes.first 349 | data = get_url_for_provider(u, provider, *attribs) 350 | response = OEmbed::Response.new(provider, u, data) 351 | if block.nil? 352 | txt.gsub!(u, response.to_s) 353 | else 354 | yield(response, u) 355 | (response.has_rendered?) ? txt.gsub!(u, response.rendered_content) : txt 356 | end 357 | else 358 | if block.nil? 359 | txt 360 | else 361 | response = OEmbed::Response.new("", u, {}) 362 | yield(response, u) 363 | (response.has_rendered?) ? txt.gsub!(u, response.rendered_content) : txt 364 | end 365 | end 366 | end 367 | end 368 | 369 | 370 | OEmbed.load_default_libs 371 | 372 | --------------------------------------------------------------------------------