odeon_uk 1.1.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +8 -1
  4. data/README.md +1 -0
  5. data/Rakefile +13 -9
  6. data/lib/odeon_uk/cinema.rb +14 -42
  7. data/lib/odeon_uk/film.rb +23 -5
  8. data/lib/odeon_uk/internal/film_with_screenings_parser.rb +133 -46
  9. data/lib/odeon_uk/internal/showtimes_page.rb +36 -0
  10. data/lib/odeon_uk/internal/title_sanitizer.rb +49 -0
  11. data/lib/odeon_uk/internal/website.rb +36 -0
  12. data/lib/odeon_uk/screening.rb +56 -22
  13. data/lib/odeon_uk/version.rb +2 -2
  14. data/lib/odeon_uk.rb +4 -2
  15. data/odeon_uk.gemspec +0 -1
  16. data/test/fixture_updater.rb +72 -0
  17. data/test/fixtures/{odeon-bfi-imax.html → cinema/bfi_imax.html} +988 -1130
  18. data/test/fixtures/{odeon-brighton.html → cinema/brighton.html} +1035 -761
  19. data/test/fixtures/{odeon-london-leicester-square.html → cinema/leicester_square.html} +470 -766
  20. data/test/fixtures/showtimes/brighton/film_first.html +92 -0
  21. data/test/fixtures/showtimes/brighton/film_last.html +100 -0
  22. data/test/fixtures/showtimes/brighton.html +2071 -0
  23. data/test/fixtures/showtimes/liverpool_one/film_first_dbox.html +188 -0
  24. data/test/fixtures/showtimes/manchester/film_first_imax.html +182 -0
  25. data/test/fixtures/{odeon-sitemap.html → sitemap.html} +572 -444
  26. data/test/lib/odeon_uk/cinema_test.rb +129 -231
  27. data/test/lib/odeon_uk/film_test.rb +50 -3
  28. data/test/lib/odeon_uk/internal/film_with_screenings_parser_test.rb +79 -123
  29. data/test/lib/odeon_uk/internal/showtimes_page_test.rb +51 -0
  30. data/test/lib/odeon_uk/internal/title_sanitizer_test.rb +105 -0
  31. data/test/lib/odeon_uk/internal/website_test.rb +59 -0
  32. data/test/lib/odeon_uk/screening_test.rb +66 -32
  33. metadata +32 -53
  34. data/test/fixtures/brighton-showtimes/about-time.html +0 -175
  35. data/test/fixtures/brighton-showtimes/autism-friendly-planes.html +0 -75
  36. data/test/fixtures/brighton-showtimes/bolshoi-spartacus-live.html +0 -67
  37. data/test/fixtures/brighton-showtimes/cinemagic-echo-planet.html +0 -67
  38. data/test/fixtures/brighton-showtimes/globe-on-screen-twelfth-night.html +0 -67
  39. data/test/fixtures/brighton-showtimes/met-opera-eugene-onegin.html +0 -67
  40. data/test/fixtures/brighton-showtimes/national-theatre-live-frankenstein.html +0 -78
  41. data/test/fixtures/brighton-showtimes/nt-live-war-horse.html +0 -67
  42. data/test/fixtures/brighton-showtimes/royal-opera-house-turandot.html +0 -66
  43. data/test/fixtures/brighton-showtimes/rsc-richard-ii.html +0 -67
  44. data/test/fixtures/brighton-showtimes/star-trek-into-darkness-2d.html +0 -83
  45. data/test/fixtures/brighton-showtimes/ukjff-from-cable-street-to-brick-lane.html +0 -67
  46. data/test/fixtures/manchester-showtimes/thor-the-dark-world.html +0 -300
  47. data/test/fixtures/odeon-brighton-showtimes.html +0 -1238
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5f884c0b0fef03d462a08ad8b2aa1faba780fcaf
4
- data.tar.gz: 719bc6d321d5da808066734ec32d8c69556d619d
3
+ metadata.gz: 59c9821b04dd9e8262c25557b82d95b51a9fdf7d
4
+ data.tar.gz: b1950e1710b9a4bcda1dbe85611cb26ad3604c48
5
5
  SHA512:
6
- metadata.gz: 309f7d5b07ad1ebb13c8186f4e36cf4d64c4755b2cc56873fb20a622ddd44dc47f5e52505be05aed3e054117d09f794e23454e7f3d87a65b069d9e858c38acbd
7
- data.tar.gz: a0317f4d337308e42594380966133233732b15a4246596c7341696678d663547d771fff5afc6e7e9c399d08f5c59adff6d9f48ed8fe6c51115f9607feda96c0a
6
+ metadata.gz: dc7ef841b0748d993a87b43e77cebf818cefa48698a3cd461bb57178f96dc677b5f0014d488d49857e76512912034a5e7c37092dcf1109ee5eb5fddb6d5a81aa
7
+ data.tar.gz: 6be57bd2533423a9895ec6f9a4f6aed25c45721886ac244cbed0ab13720f3a31899d045f3d5fd09b936d556d732ece8f5636948b5787565fcb025e28506bc839
data/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
3
  - 2.0.0
4
+ - 2.1.2
data/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
-
1
+ ## 2.0.0, _22nd September 2014_
2
+
3
+ - add website utility class
4
+ - break up film parsing into internal classes
5
+ - Film#at builds films not cinema
6
+ - fixture update script
7
+ - title sanitizer class
8
+ - updated fixtures
2
9
  - Added changelog
3
10
 
4
11
  ## 1.1.3, _8th Nov 2013_
data/README.md CHANGED
@@ -5,6 +5,7 @@ A simple gem to parse the [Odeon UK website](http://odeon.co.uk) and spit out us
5
5
  [![Gem Version](https://badge.fury.io/rb/odeon_uk.png)](http://badge.fury.io/rb/odeon_uk)
6
6
  [![Code Climate](https://codeclimate.com/github/andycroll/odeon_uk.png)](https://codeclimate.com/github/andycroll/odeon_uk)
7
7
  [![Build Status](https://travis-ci.org/andycroll/odeon_uk.png?branch=master)](https://travis-ci.org/andycroll/odeon_uk)
8
+ [![Inline docs](http://inch-ci.org/github/andycroll/odeon_uk.png)](http://inch-ci.org/github/andycroll/odeon_uk)
8
9
 
9
10
  ## Installation
10
11
 
data/Rakefile CHANGED
@@ -1,20 +1,24 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
2
+ require 'bundler/gem_tasks'
3
3
 
4
4
  require 'rake/testtask'
5
5
 
6
6
  Rake::TestTask.new do |t|
7
7
  t.libs << 'lib/odeon_uk'
8
- t.test_files = FileList['test/lib/odeon_uk/*_test.rb', 'test/lib/odeon_uk/internal/*_test.rb']
8
+ t.test_files = FileList[
9
+ 'test/lib/odeon_uk/*_test.rb',
10
+ 'test/lib/odeon_uk/internal/*_test.rb'
11
+ ]
9
12
  t.verbose = true
10
13
  end
11
14
 
12
- task :build do
13
- system "gem build odeon_uk.gemspec"
15
+ # http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
16
+ task :console do
17
+ require 'irb'
18
+ require 'irb/completion'
19
+ require 'odeon_uk'
20
+ ARGV.clear
21
+ IRB.start
14
22
  end
15
23
 
16
- task :release => :build do
17
- system "gem push odeon_uk-#{OdeonUk::VERSION}"
18
- end
19
-
20
- task :default => :test
24
+ task default: :test
@@ -1,8 +1,6 @@
1
1
  module OdeonUk
2
-
3
2
  # The object representing a cinema on the Odeon UK website
4
3
  class Cinema
5
-
6
4
  # @return [String] the brand of the cinema
7
5
  attr_reader :brand
8
6
  # @return [Integer] the numeric id of the cinema on the Odeon website
@@ -21,16 +19,13 @@ module OdeonUk
21
19
  def initialize(id, name, url)
22
20
  @brand = 'Odeon'
23
21
  @id = id.to_i
24
- @name = name.gsub('London - ','')
25
- @slug = @name.downcase.gsub(/[^0-9a-z ]/,'').gsub(/\s+/, '-')
22
+ @name = name.gsub('London - ', '').gsub(' - ', ': ')
23
+ @slug = @name.downcase.gsub(/[^0-9a-z ]/, '').gsub(/\s+/, '-')
26
24
  @url = (url[0] == '/') ? "http://www.odeon.co.uk#{url}" : url
27
25
  end
28
26
 
29
27
  # Return basic cinema information for all cinemas
30
28
  # @return [Array<OdeonUk::Cinema>]
31
- # @example
32
- # OdeonUk::Cinema.all
33
- # #=> [<OdeonUk::Cinema brand="Odeon" name="Odeon Tunbridge Wells" slug="odeon-tunbridge-wells" id=23 url="...">, #=> <OdeonUk::Cinema brand="Odeon" name="Odeon Brighton" slug="odeon-brighton" chain_id="71" url="...">, ...]
34
29
  def self.all
35
30
  cinema_links.map do |link|
36
31
  new_from_link link
@@ -38,11 +33,12 @@ module OdeonUk
38
33
  end
39
34
 
40
35
  # Find a single cinema
41
- # @param [Integer, String] id the cinema id of the format 71/'71' as used on the odeon.co.uk website
36
+ # @param [Integer, String] id the cinema id of the format 71/'71' as used on
37
+ # the odeon.co.uk website
42
38
  # @return [OdeonUk::Cinema, nil]
43
39
  # @example
44
40
  # OdeonUk::Cinema.find('71')
45
- # #=> <OdeonUk::Cinema brand="Odeon" name="Brighton" slug="brighton" id=71 url="...">
41
+ # #=> <OdeonUk::Cinema brand="Odeon" name="Brighton" slug="brighton" ...>
46
42
  def self.find(id)
47
43
  id = id.to_i
48
44
  return nil unless id > 0
@@ -55,7 +51,10 @@ module OdeonUk
55
51
  # @example
56
52
  # cinema = OdeonUk::Cinema.find('71')
57
53
  # cinema.adr
58
- # #=> { street_address: 'Kingswest', locality: 'Brighton', postal_code: 'BN1 2RE', country_name: 'United Kingdom' }
54
+ # #=> { street_address: 'Kingswest',
55
+ # locality: 'Brighton',
56
+ # postal_code: 'BN1 2RE',
57
+ # country_name: 'United Kingdom' }
59
58
  def adr
60
59
  {
61
60
  street_address: street_address,
@@ -68,15 +67,8 @@ module OdeonUk
68
67
 
69
68
  # Films with showings scheduled at this cinema
70
69
  # @return [Array<OdeonUk::Film>]
71
- # @example
72
- # cinema = OdeonUk::Cinema.find('71')
73
- # cinema.films
74
- # #=> [<OdeonUk::Film name="Iron Man 3">, <OdeonUk::Film name="Star Trek Into Darkness">]
75
70
  def films
76
- film_nodes.map do |node|
77
- parser = OdeonUk::Internal::FilmWithScreeningsParser.new node.to_s
78
- OdeonUk::Film.new parser.film_name
79
- end.uniq
71
+ Film.at(id)
80
72
  end
81
73
 
82
74
  # The name of the cinema including the brand
@@ -99,7 +91,6 @@ module OdeonUk
99
91
  address_node.text.match(/\w+(\s\w+){0,}\s+(\w+(\s\w+){0,})/)[2]
100
92
  end
101
93
 
102
-
103
94
  # Post code of the cinema
104
95
  # @return [String]
105
96
  # @example
@@ -117,14 +108,7 @@ module OdeonUk
117
108
  # cinema.screenings
118
109
  # #=> [<OdeonUk::Screening film_name="Iron Man 3" cinema_name="Brighton" when="..." variant="...">, <OdeonUk::Screening ...>]
119
110
  def screenings
120
- film_nodes.map do |node|
121
- parser = OdeonUk::Internal::FilmWithScreeningsParser.new node.to_s
122
- parser.showings.map do |screening_type, times_urls|
123
- times_urls.map do |array|
124
- OdeonUk::Screening.new parser.film_name, self.name, array[0], array[1], screening_type
125
- end
126
- end
127
- end.flatten
111
+ Screening.at(id)
128
112
  end
129
113
 
130
114
  # Screenings for particular film
@@ -155,7 +139,7 @@ module OdeonUk
155
139
  private
156
140
 
157
141
  def self.cinema_links
158
- links = parsed_sitemap.css('.sitemap > .span12:nth-child(4) a[href*=cinemas]')
142
+ links = parsed_sitemap.css('.sitemap a[href*=cinemas]')
159
143
  links.select { |link| link.get_attribute('href').match(/\/\d+\/$/) }
160
144
  end
161
145
 
@@ -171,7 +155,7 @@ module OdeonUk
171
155
  end
172
156
 
173
157
  def self.sitemap_response
174
- @sitemap_response ||= HTTParty.get('http://www.odeon.co.uk/sitemap/')
158
+ @sitemap_response ||= OdeonUk::Internal::Website.new.sitemap
175
159
  end
176
160
 
177
161
  def address_node
@@ -179,23 +163,11 @@ module OdeonUk
179
163
  end
180
164
 
181
165
  def cinema_response
182
- @cinema_response ||= HTTParty.get(@url)
166
+ @cinema_response ||= OdeonUk::Internal::Website.new.cinema(@id)
183
167
  end
184
168
 
185
169
  def parsed_cinema
186
170
  Nokogiri::HTML(cinema_response)
187
171
  end
188
-
189
- def film_nodes
190
- parsed_showtimes.css('.film-detail')
191
- end
192
-
193
- def parsed_showtimes
194
- Nokogiri::HTML(showtimes_response)
195
- end
196
-
197
- def showtimes_response
198
- @showtimes_response ||= HTTParty.get("http://www.odeon.co.uk/showtimes/week/#{@id}?siteId=#{@id}")
199
- end
200
172
  end
201
173
  end
data/lib/odeon_uk/film.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  module OdeonUk
2
-
3
2
  # The object representing a film on the Odeon UK website
4
3
  class Film
5
4
  include Comparable
@@ -16,11 +15,20 @@ module OdeonUk
16
15
  @slug = name.downcase.gsub(/[^0-9a-z ]/,'').gsub(/\s+/, '-')
17
16
  end
18
17
 
18
+ # All currently listed films showing at a cinema
19
+ # @param [Integer] cinema_id id of the cinema on the website
20
+ # @return [Array<OdeonUk::Film>]
21
+ def self.at(cinema_id)
22
+ showtimes_page(cinema_id).to_a.map do |html|
23
+ new(screenings_parser(html).film_name)
24
+ end
25
+ end
26
+
19
27
  # Allows sort on objects
20
28
  # @param [OdeonUk::Film] other another film object
21
29
  # @return [Integer] -1, 0 or 1
22
- def <=> other
23
- self.slug <=> other.slug
30
+ def <=>(other)
31
+ slug <=> other.slug
24
32
  end
25
33
 
26
34
  # Check an object is the same as another object.
@@ -28,7 +36,7 @@ module OdeonUk
28
36
  # @return [Boolean] True if both objects are the same exact object, or if
29
37
  # they are of the same type and share an equal slug
30
38
  # @note Guided by http://woss.name/2011/01/20/equality-comparison-and-ordering-in-ruby/
31
- def eql? other
39
+ def eql?(other)
32
40
  self.class == other.class && self == other
33
41
  end
34
42
 
@@ -41,7 +49,17 @@ module OdeonUk
41
49
  # @return [Integer] hash of slug
42
50
  # @note Guided by http://woss.name/2011/01/20/equality-comparison-and-ordering-in-ruby/
43
51
  def hash
44
- self.slug.hash
52
+ slug.hash
53
+ end
54
+
55
+ private
56
+
57
+ def self.screenings_parser(html)
58
+ OdeonUk::Internal::FilmWithScreeningsParser.new(html)
59
+ end
60
+
61
+ def self.showtimes_page(cinema_id)
62
+ OdeonUk::Internal::ShowtimesPage.new(cinema_id)
45
63
  end
46
64
  end
47
65
  end
@@ -1,67 +1,154 @@
1
1
  module OdeonUk
2
-
3
2
  # Internal utility classes: Do not use
4
3
  # @api private
5
4
  module Internal
6
-
7
5
  # Parses a chunk of HTML to derive movie showing data
8
6
  class FilmWithScreeningsParser
7
+ # CSS selector for a film title inside an individual film HTML
8
+ NAME_CSS = '.presentation-info h4 a'
9
+ # CSS selector for a group of showtimes inside an individual film HTML
10
+ SHOWTIMES_CSS = '.times-all.accordion-group'
9
11
 
10
- # @param [String] film_html a chunk of html
11
- def initialize(film_html)
12
- @nokogiri_html = Nokogiri::HTML(film_html)
12
+ # @param [String] html a chunk of html
13
+ def initialize(html)
14
+ @html = html
13
15
  end
14
16
 
15
17
  # The film name
16
18
  # @return [String]
17
19
  def film_name
18
- name = @nokogiri_html.css('.presentation-info h4 a').children.first.to_s
19
-
20
- # screening types
21
- name = name.gsub /\s+[23][dD]/, '' #remove 2d or 3d from title
22
- name = name.gsub 'Autism Friendly Screening -', '' # remove autism friendly
23
-
24
- # special screenings
25
- name = name.gsub 'ROH -', 'Royal Opera House:' # fill out Royal Opera House
26
- name = name.gsub 'Met Opera -', 'Met Opera:' # fill out Met Opera
27
- name = name.gsub /Cinemagic \d{1,4} \-/, '' # remove cinemagic stuff
28
- name = name.gsub 'UKJFF -', '' # remove UK Jewish festival prefix
29
- name = name.gsub 'NT Live -', 'National Theatre:' # National theatre
30
- name = name.gsub 'National Theatre Live -', 'National Theatre:'
31
- name = name.gsub /\(Encore.+\)/, '' # National theatre, remove encore
32
- name = name.gsub 'Bolshoi -', 'Bolshoi:' # bolshoi ballet
33
- name = name.gsub /\([Ll]ive\)/, '' # remove bolshoi-style live
34
- name = name.gsub 'Globe On Screen:', 'Globe:' # globe
35
- if name.match /RSC Live/
36
- name = 'Royal Shakespeare Company: ' + name.gsub(/\- RSC Live \d{1,4}/, '')
20
+ @film_name ||= TitleSanitizer.new(raw_film_name).sanitized
21
+ end
22
+
23
+ # array containing hashes of screening attributes
24
+ # @return [Array<Hash>]
25
+ def to_a
26
+ doc.css(SHOWTIMES_CSS).map do |node|
27
+ dimension_parser(node).to_a.map do |hash|
28
+ hash.merge(film_name: film_name)
29
+ end
30
+ end.flatten
31
+ end
32
+
33
+ private
34
+
35
+ def doc
36
+ @doc ||= Nokogiri::HTML(@html)
37
+ end
38
+
39
+ def raw_film_name
40
+ @raw_film_name ||= doc.css(NAME_CSS).children.first.to_s
41
+ end
42
+
43
+ def dimension_parser(node)
44
+ DimensionNodeParser.new(node)
45
+ end
46
+ end
47
+
48
+ # parses chunk of screenings for a particular screening dimension
49
+ class DimensionNodeParser
50
+ # CSS selector for a single screening inside a 'group of showtimes' HTML
51
+ SCREENING_CSS = '.show'
52
+ # CSS selector for showing technology for a 'group of showtimes' HTML
53
+ TECH_CSS = '.tech a'
54
+
55
+ # @param [Nokigiri::Node] node a Nokogiri node object
56
+ def initialize(node)
57
+ @node = node
58
+ end
59
+
60
+ # array containing hashes of screening attributes for dimension
61
+ # @return [Array<Hash>]
62
+ def to_a
63
+ screening_hashes.map do |hash|
64
+ hash.merge(
65
+ dimension: dimension,
66
+ variant: add_imax(hash[:variant])
67
+ )
37
68
  end
69
+ end
70
+
71
+ private
72
+
73
+ def add_imax(original)
74
+ imax? ? "#{original} imax".strip : original
75
+ end
76
+
77
+ def dimension
78
+ tech.match(/[23]d/)[0]
79
+ end
80
+
81
+ def imax?
82
+ !!tech.match(/imax/)
83
+ end
38
84
 
39
- name = name.squeeze(' ') # spaces compressed
40
- name = name.gsub /\A\s+/, '' # remove leading spaces
41
- name = name.gsub /\s+\z/, '' # remove trailing spaces
85
+ def screening_hashes
86
+ screening_nodes.map { |node| ScreeningNodeParser.new(node).to_hash }
42
87
  end
43
88
 
44
- # Showings
89
+ def screening_nodes
90
+ @node.css(SCREENING_CSS)
91
+ end
92
+
93
+ def tech
94
+ @tech ||= @node.css(TECH_CSS).text.gsub('in ', '').downcase
95
+ end
96
+ end
97
+
98
+ # parses a single screening
99
+ class ScreeningNodeParser
100
+ # regex for time format
101
+ TIME_REGEX = %r(\d+/\d+/\d+ \d{2}\:\d{2})
102
+ # regex for D-Box screenings
103
+ DBOX_REGEX = /D-Box/
104
+
105
+ # @param [Nokigiri::Node] node a Nokogiri node object
106
+ def initialize(node)
107
+ @node = node
108
+ end
109
+
110
+ # hashes of screening attributes
45
111
  # @return [Hash]
46
- # @example
47
- # {
48
- # "2D" => [[Time.utc, 'http://www...'], [Time.utc, 'http://www...']],
49
- # "3D" => [[Time.utc, 'http://www...']]
50
- # }
51
- def showings
52
- tz = TZInfo::Timezone.get('Europe/London')
53
- @nokogiri_html.css('.times-all.accordion-group').inject({}) do |result, variant_node|
54
- variant = variant_node.css('.tech a').text.gsub('in ', '').upcase
55
-
56
- times_url = variant_node.css('.performance-detail').map do |screening_node|
57
- [
58
- tz.local_to_utc(Time.parse(screening_node['title'].match(/\d+\/\d+\/\d+ \d{2}\:\d{2}/).to_s + ' UTC')),
59
- "http://www.odeon.co.uk#{screening_node['href']}"
60
- ]
61
- end
112
+ def to_hash
113
+ {
114
+ booking_url: booking_url,
115
+ time: utc_time,
116
+ variant: variant
117
+ }
118
+ end
62
119
 
63
- result.merge(variant => times_url)
64
- end
120
+ private
121
+
122
+ def booking_url
123
+ link_attr('href').to_s
124
+ end
125
+
126
+ def link_attr(attribute)
127
+ @node.css('a').attribute(attribute)
128
+ end
129
+
130
+ def info
131
+ @node.css('i').to_s
132
+ end
133
+
134
+ def time
135
+ Time.parse(time_string)
136
+ end
137
+
138
+ def time_string
139
+ link_attr('title').to_s.match(TIME_REGEX).to_s + ' UTC'
140
+ end
141
+
142
+ def tz
143
+ @tz ||= TZInfo::Timezone.get('Europe/London')
144
+ end
145
+
146
+ def utc_time
147
+ tz.local_to_utc(time)
148
+ end
149
+
150
+ def variant
151
+ info.match(DBOX_REGEX) ? 'd-box' : ''
65
152
  end
66
153
  end
67
154
  end
@@ -0,0 +1,36 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Internal
5
+ # Parses a chunk of HTML to derive showing data for a single films
6
+ class ShowtimesPage
7
+ # css selector for film html chunks
8
+ FILM_CSS = '.film-detail'
9
+
10
+ # @param [Integer] cinema_id cineworld cinema id
11
+ def initialize(cinema_id)
12
+ @cinema_id = cinema_id
13
+ end
14
+
15
+ # break up the showtimes page into individual chunks for each film
16
+ # @return [Array<String>] html chunks for a film and it's screenings
17
+ def to_a
18
+ film_nodes.map { |node| node.to_s.gsub(/^\s+/, '') }
19
+ end
20
+
21
+ private
22
+
23
+ def film_nodes
24
+ @film_nodes ||= showtimes_doc.css(FILM_CSS)
25
+ end
26
+
27
+ def showtimes
28
+ @showtimes ||= OdeonUk::Internal::Website.new.showtimes(@cinema_id)
29
+ end
30
+
31
+ def showtimes_doc
32
+ @showtimes_doc ||= Nokogiri::HTML(showtimes)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Internal
5
+ # Sanitize and standardize film titles
6
+ class TitleSanitizer
7
+ # strings and regex to be removed
8
+ REMOVE = [
9
+ /\s+[23][dD]/, # dimension
10
+ 'Autism Friendly Screening -', # autism screening
11
+ /\ACinemagic \d{1,4} \-/, # cinemagic
12
+ /\(encore.+\)/i, # encore for NT Live
13
+ /\(live\)/i, # live in brackets
14
+ 'UKJFF -', # UK Jewish festival prefix
15
+ ]
16
+
17
+ # regexes and their replacements
18
+ REPLACE = {
19
+ /Bolshoi - (.*)/ => 'Bolshoi: ',
20
+ /Globe On Screen: (.*)/ => 'Globe: ',
21
+ /Met Opera - (.*)/ => 'Met Opera: ',
22
+ /National Theatre Live - (.*)/ => 'National Theatre: ',
23
+ /NT Live - (.*)/ => 'National Theatre: ',
24
+ /ROH - (.*)/ => 'Royal Opera House: ',
25
+ /(.*)\- RSC Live \d{1,4}/ => 'Royal Shakespeare Company: '
26
+ }
27
+
28
+ # @param [String] title a film title
29
+ def initialize(title)
30
+ @title = title
31
+ end
32
+
33
+ # sanitized and standardized title
34
+ # @return [String] title
35
+ def sanitized
36
+ @sanitzed ||= begin
37
+ sanitized = @title
38
+ REMOVE.each do |pattern|
39
+ sanitized.gsub! pattern, ''
40
+ end
41
+ REPLACE.each do |pattern, prefix|
42
+ sanitized.gsub!(pattern) { |_| prefix + $1 }
43
+ end
44
+ sanitized.squeeze(' ').strip
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ require 'open-uri'
2
+
3
+ module OdeonUk
4
+ # Internal utility classes: Do not use
5
+ # @api private
6
+ module Internal
7
+ # Utility class to make calls to the odeon website
8
+ class Website
9
+ # cinema page
10
+ # @param [Integer] cinema_id website id of the cinema
11
+ # @return [String] html of the page
12
+ def cinema(cinema_id)
13
+ get("cinemas/odeon/#{cinema_id}/")
14
+ end
15
+
16
+ # showtimes page for a single cinema
17
+ # @param [Integer] cinema_id website id of the cinema
18
+ # @return [String] html of the page
19
+ def showtimes(cinema_id)
20
+ get("showtimes/week/#{cinema_id}/?siteId=#{cinema_id}")
21
+ end
22
+
23
+ # sitemap page
24
+ # @return [String] html of the page
25
+ def sitemap
26
+ get('sitemap/')
27
+ end
28
+
29
+ private
30
+
31
+ def get(path)
32
+ URI("http://www.odeon.co.uk/#{path}").read
33
+ end
34
+ end
35
+ end
36
+ end