bbc-programmes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ pkg
3
+ coverage
4
+ .yardoc
5
+ doc
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Ruby client library for the BBC Programmes website
2
+
3
+ require 'rubygems'
4
+ require 'bbc/programmes'
5
+
6
+ episode = BBC::Programmes::Episode.fetch('b00jnwnv')
7
+ puts "PID: #{episode.pid}"
8
+ puts "Title: #{episode.title}"
9
+ puts "Short Synopsis: #{episode.short_synopsis}"
10
+ puts "Medium Synopsis: #{episode.medium_synopsis}"
11
+ puts "Long Synopsis: #{episode.long_synopsis}"
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'yard'
3
+
4
+ begin
5
+ gem 'jeweler'
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "bbc-programmes"
9
+ gemspec.summary = "Ruby client library for the BBC Programmes website."
10
+ gemspec.email = "njh@aelius.com.com"
11
+ gemspec.homepage = "http://github.com/njh/bbc-programmes-ruby"
12
+ gemspec.authors = ["Nicholas J Humfrey"]
13
+ gemspec.add_dependency('rdf', '>= 0.2.2')
14
+ gemspec.add_dependency('nokogiri', '>= 1.3.3')
15
+ gemspec.add_development_dependency('rspec')
16
+ gemspec.add_development_dependency('yard')
17
+ gemspec.extra_rdoc_files = %w(README.md)
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.pattern = 'spec/*.spec'
28
+ spec.spec_opts = ["-c"]
29
+ end
30
+
31
+ desc "Run specs through RCov"
32
+ Spec::Rake::SpecTask.new("spec:rcov") do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/*.spec'
35
+ spec.rcov = true
36
+ spec.rcov_opts = ['-x', '/Library', '-x', '/System/Library', '-x', 'spec']
37
+ end
38
+
39
+ desc "Generate HTML report specs"
40
+ Spec::Rake::SpecTask.new("doc:spec") do |spec|
41
+ spec.libs << 'lib' << 'spec'
42
+ spec.spec_files = FileList['spec/*.spec']
43
+ spec.spec_opts = ["--format", "html:doc/spec.html"]
44
+ end
45
+
46
+ YARD::Rake::YardocTask.new do |t|
47
+ t.files = %w(lib/**/*.rb)
48
+ end
49
+
50
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/examples/basic.rb ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Basic example of getting the title and synopsis for an episode
4
+ #
5
+
6
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
7
+
8
+ require 'rubygems'
9
+ require 'bbc/programmes'
10
+
11
+ Spira.add_repository(:default, RDF::Repository)
12
+
13
+
14
+ episode = BBC::Programmes::Episode.fetch('b00jnwnv')
15
+ puts "PID: #{episode.pid}"
16
+ puts "Title: #{episode.title}"
17
+ puts "Short Synopsis: #{episode.short_synopsis}"
18
+ puts "Medium Synopsis: #{episode.medium_synopsis}"
19
+ puts "Long Synopsis: #{episode.long_synopsis}"
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Example of listing all the broadcasts for episode
4
+ #
5
+
6
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
7
+
8
+ require 'rubygems'
9
+ require 'bbc/programmes'
10
+
11
+ Spira.add_repository(:default, RDF::Repository)
12
+
13
+ episode = BBC::Programmes::Episode.fetch('b00jnwnv')
14
+ puts "#{episode.pid}: #{episode.title}"
15
+
16
+ version = episode.versions.first.fetch!
17
+ version.broadcasts.each do |broadcast|
18
+ puts "#{broadcast.start} - #{broadcast.end} #{broadcast.service}"
19
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Example of searching for programmes by title
4
+ #
5
+
6
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
7
+
8
+ require 'rubygems'
9
+ require 'bbc/programmes'
10
+
11
+ Spira.add_repository(:default, RDF::Repository)
12
+
13
+ programmes = BBC::Programmes.search('The Wire').each do |programme|
14
+ puts "#{programme.pid}: #{programme.title}"
15
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Example of listing all the broadcasts for episode
4
+ #
5
+
6
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
7
+
8
+ require 'rubygems'
9
+ require 'bbc/programmes'
10
+
11
+ Spira.add_repository(:default, RDF::Repository)
12
+
13
+ episode = BBC::Programmes::Episode.fetch('b00sk7q2')
14
+ puts "Episode: #{episode.title}"
15
+ puts
16
+
17
+ version = episode.versions.first.fetch!
18
+ version.segments.each do |segment|
19
+ puts segment
20
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Base
6
+ include Spira::Resource
7
+
8
+ base_uri BBC::Programmes::BASE_URI
9
+ default_vocabulary RDF::PO
10
+
11
+ property :pid, :type => String
12
+
13
+ def self.fetch(identifier)
14
+ programme = self.for(identifier)
15
+ programme.fetch!
16
+ programme
17
+ end
18
+
19
+ def fetch!
20
+ BBC::Programmes.fetch(subject)
21
+ self.reload
22
+ self
23
+ end
24
+
25
+ def pid
26
+ pid = attribute_get(:pid)
27
+ if pid.nil?
28
+ subject.to_s =~ /(\w+)\#(\w+)$/
29
+ pid = $1
30
+ end
31
+ pid
32
+ end
33
+
34
+ def self.subclasses
35
+ subclasses = []
36
+ ObjectSpace.each_object(Class) do |klass|
37
+ if klass.ancestors.include?(self)
38
+ subclasses << klass
39
+ end
40
+ end
41
+ subclasses
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Brand < Programme
6
+ type RDF::PO.Brand
7
+
8
+ has_many :series, :predicate => RDF::PO.series, :type => 'BBC_Programmes_Series'
9
+ has_many :episodes, :predicate => RDF::PO.episode, :type => 'BBC_Programmes_Episode'
10
+ end
11
+
12
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Broadcast < Base
6
+ type RDF::PO.Broadcast
7
+
8
+ default_vocabulary RDF::PO
9
+ property :time, :predicate => RDF::URI('http://purl.org/NET/c4dm/event.owl#time'), :type => 'BBC_Programmes_TimeInterval'
10
+ property :schedule_date, :type => String # Date
11
+ property :broadcast_of, :type => 'BBC_Programmes_Version'
12
+ property :broadcast_on, :type => 'BBC_Programmes_Service'
13
+
14
+ def start
15
+ time.nil? ? nil : time.start
16
+ end
17
+
18
+ def end
19
+ time.nil? ? nil : time.end
20
+ end
21
+
22
+ def service
23
+ broadcast_on
24
+ end
25
+
26
+ def version
27
+ broadcast_of
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Episode < Programme
6
+ type RDF::PO.Episode
7
+
8
+ default_vocabulary RDF::PO
9
+ property :position, :type => Integer
10
+ has_many :versions, :predicate => RDF::PO.version, :type => 'BBC_Programmes_Version'
11
+ end
12
+
13
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Programme < Base
6
+ default_vocabulary RDF::PO
7
+ type RDF::PO.Programme
8
+
9
+ property :title, :predicate => DC11.title, :type => String
10
+ property :short_synopsis, :type => String
11
+ property :medium_synopsis, :type => String
12
+ property :long_synopsis, :type => String
13
+ property :image, :predicate => FOAF.depiction, :type => RDF::URI
14
+
15
+ # FIXME: this should be of type service
16
+ property :masterbrand, :type => RDF::URI
17
+
18
+ # FIXME: implement this
19
+ #has_many :genres
20
+ #has_many :formats
21
+
22
+ def self.id_for(identifier)
23
+ if identifier.is_a?(::String)
24
+ super(
25
+ RDF::URI("#{base_uri}/#{identifier}#programme")
26
+ )
27
+ else
28
+ super(identifier)
29
+ end
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Segment < Base
6
+ type RDF::PO.Segment
7
+
8
+ default_vocabulary RDF::PO
9
+ property :label, :predicate => RDF::RDFS.label, :type => String
10
+
11
+ def to_s
12
+ label
13
+ end
14
+
15
+ # FIXME: add support for fetching full details of a segment
16
+ end
17
+
18
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Series < Programme
6
+ type RDF::PO.Series
7
+
8
+ default_vocabulary RDF::PO
9
+ property :position, :type => Integer
10
+ has_many :series, :predicate => RDF::PO.series, :type => 'BBC_Programmes_Series'
11
+ has_many :episodes, :predicate => RDF::PO.episode, :type => 'BBC_Programmes_Episode'
12
+ end
13
+
14
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Service < Base
6
+ type RDF::PO.Service
7
+
8
+ default_vocabulary RDF::PO
9
+ property :label, :predicate => RDF::RDFS.label, :type => String
10
+ property :parent, :predicate => RDF::PO.parent_service, :type => 'BBC_Programmes_Service'
11
+
12
+ def to_s
13
+ label.to_s
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class TimeInterval
6
+ include Spira::Resource
7
+
8
+ type RDF::TimeLine.Interval
9
+ property :start, :predicate => RDF::TimeLine.start, :type => Any
10
+ property :end, :predicate => RDF::TimeLine.end, :type => Any
11
+ property :duration, :predicate => RDF::TimeLine.end, :type => String # Duration
12
+ property :position, :predicate => RDF::PO.position, :type => Integer
13
+ property :timeline, :predicate => RDF::TimeLine.timeline, :type => 'BBC_Programmes_TimeLine'
14
+ end
15
+
16
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class TimeLine
6
+ include Spira::Resource
7
+ type RDF::TimeLine.TimeLine
8
+ end
9
+
10
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module BBC::Programmes
4
+
5
+ class Version < Base
6
+ type RDF::PO.Version
7
+
8
+ default_vocabulary RDF::PO
9
+ property :aspect_ratio, :type => String
10
+ property :sound_format, :type => String
11
+ property :duration, :type => Integer
12
+ property :time, :predicate => RDF::PO.time, :type => 'BBC_Programmes_TimeInterval'
13
+
14
+ def self.id_for(identifier)
15
+ if identifier.is_a?(::String)
16
+ super(
17
+ RDF::URI("#{base_uri}/#{identifier}#programme")
18
+ )
19
+ else
20
+ super(identifier)
21
+ end
22
+ end
23
+
24
+ def broadcasts
25
+ # FIXME: can this be done using inverse properties?
26
+ Broadcast.repository.query([nil, RDF::PO.broadcast_of, subject]).map do |b|
27
+ b.subject.as(Broadcast)
28
+ end
29
+ end
30
+
31
+ def timeline
32
+ time.nil? ? nil : time.timeline
33
+ end
34
+
35
+ def intervals
36
+ TimeInterval.repository.query([nil, RDF::TimeLine.timeline, timeline.subject]).map do |b|
37
+ b.subject.as(TimeInterval)
38
+ end
39
+ end
40
+
41
+ def segments
42
+ intervals = self.intervals.select {|i| !i.position.nil?}
43
+ intervals.sort! {|a,b| (a.position.to_i||0) <=> (b.position.to_i||0)}
44
+ intervals.map do |interval|
45
+ segment = Segment.repository.first_subject([nil, RDF::PO.time, interval.subject])
46
+ segment.nil? ? nil : segment.as(Segment)
47
+ end
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rdf'
4
+ require 'rdf/raptor'
5
+ require 'rdf/po'
6
+ require 'rdf/time_line'
7
+ require 'net/http'
8
+ require 'addressable/uri'
9
+ require 'spira'
10
+
11
+
12
+ module BBC
13
+ module Programmes
14
+ BASE_URI = 'http://www.bbc.co.uk/programmes'
15
+ USER_AGENT = "bbc-programmes-ruby/0.1"
16
+
17
+ require 'bbc/programmes/base'
18
+ require 'bbc/programmes/programme'
19
+ require 'bbc/programmes/brand'
20
+ require 'bbc/programmes/episode'
21
+ require 'bbc/programmes/series'
22
+ require 'bbc/programmes/version'
23
+ require 'bbc/programmes/service'
24
+ require 'bbc/programmes/broadcast'
25
+ require 'bbc/programmes/segment'
26
+
27
+ require 'bbc/programmes/time_interval'
28
+ require 'bbc/programmes/time_line'
29
+
30
+ def self.search(keywords)
31
+ encoded = Addressable::URI.encode(keywords)
32
+ graph = http_get_graph("#{BASE_URI}/a-z/by/#{encoded}/all.rdf")
33
+
34
+ # FIXME: push graph into the repository?
35
+
36
+ programmes = []
37
+ graph.query([nil, RDF.type, RDF::PO.Programme]) do |s|
38
+ programme = Programme.for(s.subject)
39
+ programme.title = graph.first_literal([s.subject, RDF::DC11.title, nil])
40
+ programmes << programme
41
+ end
42
+ programmes
43
+ end
44
+
45
+ # Fetches and returns a Graph for an identifier
46
+ def self.http_get_graph(uri)
47
+ uri = RDF::URI(uri) unless uri.is_a?(RDF::URI)
48
+
49
+ # FIXME: support other HTTP libraries too
50
+ res = Net::HTTP.start(uri.host, uri.port) do |http|
51
+ http.get(uri.request_uri, {
52
+ 'User-Agent' => USER_AGENT,
53
+ 'Accept' => 'application/rdf+xml'
54
+ })
55
+ end
56
+
57
+ # Throw an exception if it failed
58
+ res.value
59
+
60
+ # Find a reader for the content type returned
61
+ reader_class = RDF::Reader.for(:content_type => res.content_type)
62
+ raise "No reader found for parsing: #{res.content_type}" if reader_class.nil?
63
+
64
+ # Parse the RDF
65
+ RDF::Graph.new(uri) do |graph|
66
+ reader_class.new(res.body, :base_uri => uri) do |reader|
67
+ reader.each_statement do |statement|
68
+ graph << statement
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ # Load a subject into the local repository
75
+ def self.fetch(identifier)
76
+ unless identifier.is_a?(RDF::URI)
77
+ identifier = RDF::URI("#{BASE_URI}/#{identifier}")
78
+ end
79
+
80
+ graph = self.http_get_graph(identifier)
81
+
82
+ type = graph.first_object([identifier, RDF.type, nil])
83
+ raise "No type found for #{identifier}" if type.nil?
84
+
85
+ klass = BBC::Programmes::Base.subclasses.find { |k| k.type == type }
86
+ raise "No class found for #{identifier} of type #{type}" if klass.nil?
87
+
88
+ # Add the graph to the repository of the type
89
+ graph.each_statement do |s|
90
+ klass.repository << s
91
+ end
92
+
93
+ klass.for(identifier)
94
+ end
95
+
96
+ end
97
+ end
98
+
99
+
100
+ # FIXME: hack for Spira not supporting namespaced resources
101
+ BBC_Programmes_Programme = BBC::Programmes::Programme
102
+ BBC_Programmes_Brand = BBC::Programmes::Brand
103
+ BBC_Programmes_Episode = BBC::Programmes::Episode
104
+ BBC_Programmes_Series = BBC::Programmes::Series
105
+ BBC_Programmes_Version = BBC::Programmes::Version
106
+ BBC_Programmes_Service = BBC::Programmes::Service
107
+ BBC_Programmes_Broadcast = BBC::Programmes::Broadcast
108
+ BBC_Programmes_Segment = BBC::Programmes::Segment
109
+ BBC_Programmes_TimeInterval = BBC::Programmes::TimeInterval
110
+ BBC_Programmes_TimeLine = BBC::Programmes::TimeLine
data/lib/rdf/po.rb ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module RDF
4
+
5
+ ##
6
+ # Programmes Ontology vocabulary.
7
+ #
8
+ # @see http://purl.org/ontology/po/
9
+ class PO < RDF::Vocabulary('http://purl.org/ontology/po/')
10
+ property :Brand
11
+ property :Series
12
+ property :Episode
13
+ property :Programme
14
+ property :Version
15
+ end
16
+
17
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module RDF
4
+
5
+ ##
6
+ # Timeline vocabulary.
7
+ #
8
+ # @see http://purl.org/NET/c4dm/timeline.owl
9
+ class TimeLine < RDF::Vocabulary('http://purl.org/NET/c4dm/timeline.owl#')
10
+ end
11
+
12
+ end
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + "/spec_helper.rb"
2
+
3
+ describe BBC::Programmes do
4
+ before :each do
5
+ @repo = RDF::Repository.new
6
+ Spira.add_repository(:default, @repo)
7
+ end
8
+
9
+ context "performing an HTTP get for a URI" do
10
+ before :each do
11
+ mock_http('www.bbc.co.uk', 'b00jnwlc.rdf')
12
+ @uri = RDF::URI('http://www.bbc.co.uk/programmes/b00jnwlc#programme')
13
+ @graph = BBC::Programmes.http_get_graph(@uri)
14
+ end
15
+
16
+ it "should return an object of type RDF::Graph" do
17
+ @graph.should be_a RDF::Graph
18
+ end
19
+
20
+ it "should return a graph with more than 10 statements" do
21
+ @graph.size.should > 10
22
+ end
23
+
24
+ it "should return multiple statements about the URI looked up" do
25
+ @graph.query([@uri, nil, nil]).count.should > 2
26
+ end
27
+
28
+ it "should return a triple stating that the programme is of type Brand" do
29
+ @graph.should have_triple([@uri, RDF.type, RDF::PO.Brand])
30
+ end
31
+ end
32
+
33
+ context "searching for a keyword" do
34
+ before :each do
35
+ mock_http('www.bbc.co.uk', 'the_wire.rdf')
36
+ @programmes = BBC::Programmes.search("The Wire")
37
+ end
38
+
39
+ it "should return an array" do
40
+ @programmes.should be_a Array
41
+ end
42
+
43
+ it "should have three items in the array" do
44
+ @programmes.size.should == 3
45
+ end
46
+
47
+ it "should have items of type Programme in the array" do
48
+ @programmes.first.should be_a BBC::Programmes::Programme
49
+ end
50
+ end
51
+
52
+ context "fetching an episode" do
53
+ before :each do
54
+ mock_http('www.bbc.co.uk', 'b00jnwnv.rdf')
55
+ @episode = BBC::Programmes.fetch('b00jnwnv#programme')
56
+ end
57
+
58
+ it "should have a type URI defined" do
59
+ @episode.type.should == RDF::URI('http://purl.org/ontology/po/Episode')
60
+ end
61
+
62
+ it "should have a correct URI" do
63
+ @episode.subject.should == RDF::URI('http://www.bbc.co.uk/programmes/b00jnwnv#programme')
64
+ end
65
+
66
+ it "should have the title 'The Target'" do
67
+ @episode.title.should == 'The Target'
68
+ end
69
+ end
70
+
71
+ end