odeon_uk 1.1.5 → 2.0.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.
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