bbc-programmes 0.1.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.
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