odeon_uk 2.0.4 → 3.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/CHANGELOG.md +6 -0
  4. data/Rakefile +29 -0
  5. data/lib/odeon_uk/api/cinema.rb +62 -0
  6. data/lib/odeon_uk/api/response.rb +55 -0
  7. data/lib/odeon_uk/api/screenings.rb +89 -0
  8. data/lib/odeon_uk/cinema.rb +53 -114
  9. data/lib/odeon_uk/configuration.rb +21 -0
  10. data/lib/odeon_uk/html/cinema.rb +101 -0
  11. data/lib/odeon_uk/html/parser/film_with_screenings.rb +160 -0
  12. data/lib/odeon_uk/html/screenings.rb +38 -0
  13. data/lib/odeon_uk/{internal → html}/website.rb +1 -1
  14. data/lib/odeon_uk/internal/title_sanitizer.rb +1 -0
  15. data/lib/odeon_uk/screening.rb +7 -15
  16. data/lib/odeon_uk/version.rb +2 -2
  17. data/lib/odeon_uk.rb +11 -4
  18. data/odeon_uk.gemspec +2 -0
  19. data/rake/fixture_creator.rb +98 -0
  20. data/test/fixtures/api/all_cinemas.plist +0 -0
  21. data/test/fixtures/api/app_init.plist +0 -0
  22. data/test/fixtures/api/film_times/71-100747.plist +0 -0
  23. data/test/fixtures/api/film_times/71-100750.plist +0 -0
  24. data/test/fixtures/api/film_times/71-100790.plist +0 -0
  25. data/test/fixtures/api/film_times/71-14646.plist +0 -0
  26. data/test/fixtures/api/film_times/71-14725.plist +0 -0
  27. data/test/fixtures/api/film_times/71-15086.plist +0 -0
  28. data/test/fixtures/api/film_times/71-15096.plist +0 -0
  29. data/test/fixtures/api/film_times/71-15122.plist +0 -0
  30. data/test/fixtures/api/film_times/71-15130.plist +0 -0
  31. data/test/fixtures/api/film_times/71-15141.plist +0 -0
  32. data/test/fixtures/api/film_times/71-15142.plist +0 -0
  33. data/test/fixtures/api/film_times/71-15143.plist +0 -0
  34. data/test/fixtures/api/film_times/71-15144.plist +0 -0
  35. data/test/fixtures/api/film_times/71-15145.plist +0 -0
  36. data/test/fixtures/api/film_times/71-15170.plist +0 -0
  37. data/test/fixtures/api/film_times/71-15172.plist +0 -0
  38. data/test/fixtures/api/film_times/71-15177.plist +0 -0
  39. data/test/fixtures/api/film_times/71-15179.plist +0 -0
  40. data/test/fixtures/api/film_times/71-15182.plist +0 -0
  41. data/test/fixtures/api/film_times/71-15286.plist +0 -0
  42. data/test/fixtures/api/film_times/71-15360.plist +0 -0
  43. data/test/fixtures/api/film_times/71-15586.plist +0 -0
  44. data/test/fixtures/api/film_times/71-15700.plist +0 -0
  45. data/test/fixtures/api/film_times/71-15718.plist +0 -0
  46. data/test/fixtures/api/film_times/71-15768.plist +0 -0
  47. data/test/fixtures/api/film_times/71-15788.plist +0 -0
  48. data/test/fixtures/api/film_times/71-15793.plist +0 -0
  49. data/test/fixtures/api/film_times/71-15794.plist +0 -0
  50. data/test/fixtures/api/film_times/71-15795.plist +0 -0
  51. data/test/fixtures/api/film_times/71-15796.plist +0 -0
  52. data/test/fixtures/api/film_times/71-15799.plist +0 -0
  53. data/test/fixtures/api/film_times/71-15802.plist +0 -0
  54. data/test/fixtures/api/film_times/71-15814.plist +0 -0
  55. data/test/fixtures/api/film_times/71-15817.plist +0 -0
  56. data/test/fixtures/api/film_times/71-15840.plist +0 -0
  57. data/test/fixtures/{cinema/leicester_square.html → html/cinema/105.html} +384 -313
  58. data/test/fixtures/{cinema/bfi_imax.html → html/cinema/211.html} +677 -355
  59. data/test/fixtures/{cinema/brighton.html → html/cinema/71.html} +899 -431
  60. data/test/fixtures/html/showtimes/11-imax.html +170 -0
  61. data/test/fixtures/html/showtimes/171-d-box.html +203 -0
  62. data/test/fixtures/html/showtimes/71-0.html +59 -0
  63. data/test/fixtures/html/showtimes/71.html +2395 -0
  64. data/test/fixtures/{sitemap.html → html/sitemap.html} +264 -352
  65. data/test/lib/odeon_uk/api/cinema_test.rb +149 -0
  66. data/test/lib/odeon_uk/api/response_test.rb +44 -0
  67. data/test/lib/odeon_uk/api/screenings_test.rb +43 -0
  68. data/test/lib/odeon_uk/cinema_test.rb +373 -148
  69. data/test/lib/odeon_uk/configuration_test.rb +29 -0
  70. data/test/lib/odeon_uk/html/cinema_test.rb +149 -0
  71. data/test/lib/odeon_uk/{internal/film_with_screenings_parser_test.rb → html/parser/film_with_screenings_test.rb} +12 -42
  72. data/test/lib/odeon_uk/html/screenings_test.rb +43 -0
  73. data/test/lib/odeon_uk/html/website_test.rb +37 -0
  74. data/test/lib/odeon_uk/screening_test.rb +17 -83
  75. data/test/support/api_fixtures_helper.rb +34 -0
  76. data/test/support/website_fixtures_helper.rb +25 -0
  77. data/test/test_helper.rb +7 -2
  78. metadata +149 -34
  79. data/lib/odeon_uk/film.rb +0 -65
  80. data/lib/odeon_uk/internal/film_with_screenings_parser.rb +0 -159
  81. data/lib/odeon_uk/internal/showtimes_page.rb +0 -36
  82. data/test/fixture_updater.rb +0 -72
  83. data/test/fixtures/showtimes/brighton/film_first.html +0 -92
  84. data/test/fixtures/showtimes/brighton/film_last.html +0 -100
  85. data/test/fixtures/showtimes/brighton.html +0 -2071
  86. data/test/fixtures/showtimes/liverpool_one/film_first_dbox.html +0 -188
  87. data/test/fixtures/showtimes/manchester/film_first_imax.html +0 -182
  88. data/test/lib/odeon_uk/film_test.rb +0 -142
  89. data/test/lib/odeon_uk/internal/showtimes_page_test.rb +0 -51
  90. data/test/lib/odeon_uk/internal/website_test.rb +0 -59
@@ -0,0 +1,160 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Html
5
+ # Parses HTML with Nokogiri
6
+ module Parser
7
+ # Parses a chunk of HTML to derive movie showing data
8
+ class FilmWithScreenings
9
+ # CSS selector for a film title inside an individual film HTML
10
+ NAME_CSS = '.presentation-info h4 a'
11
+ # CSS selector for a group of showtimes inside an individual film HTML
12
+ SHOWTIMES_CSS = '.times-all.accordion-group'
13
+
14
+ # @param [String] html a chunk of html
15
+ def initialize(html)
16
+ @html = html
17
+ end
18
+
19
+ # The film name
20
+ # @return [String]
21
+ def film_name
22
+ @film_name ||= Internal::TitleSanitizer.new(raw_film_name).sanitized
23
+ end
24
+
25
+ # array containing hashes of screening attributes
26
+ # @return [Array<Hash>]
27
+ def to_a
28
+ doc.css(SHOWTIMES_CSS).map do |node|
29
+ dimension_parser(node).to_a.map do |hash|
30
+ hash.merge(film_name: film_name)
31
+ end
32
+ end.flatten
33
+ end
34
+
35
+ private
36
+
37
+ def doc
38
+ @doc ||= Nokogiri::HTML(@html)
39
+ end
40
+
41
+ def raw_film_name
42
+ @raw_film_name ||= doc.css(NAME_CSS).children.first.to_s
43
+ end
44
+
45
+ def dimension_parser(node)
46
+ DimensionNodeParser.new(node)
47
+ end
48
+ end
49
+
50
+ # parses chunk of screenings for a particular screening dimension
51
+ class DimensionNodeParser
52
+ # CSS selector for a single screening inside a 'group of showtimes' HTML
53
+ SCREENING_CSS = '.show'
54
+ # CSS selector for showing technology for a 'group of showtimes' HTML
55
+ TECH_CSS = '.tech a'
56
+
57
+ # @param [Nokigiri::Node] node a Nokogiri node object
58
+ def initialize(node)
59
+ @node = node
60
+ end
61
+
62
+ # array containing hashes of screening attributes for dimension
63
+ # @return [Array<Hash>]
64
+ def to_a
65
+ screening_hashes.map do |hash|
66
+ hash.merge(dimension: dimension,
67
+ variant: add_imax(hash[:variant]))
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
+ dimension_from_tech || '2d'
79
+ end
80
+
81
+ def dimension_from_tech
82
+ tech.match(/[23]d/i) { |data| data[0] }
83
+ end
84
+
85
+ def imax?
86
+ !!tech.match(/imax/)
87
+ end
88
+
89
+ def screening_hashes
90
+ screening_nodes.map { |node| ScreeningNodeParser.new(node).to_hash }
91
+ end
92
+
93
+ def screening_nodes
94
+ @node.css(SCREENING_CSS)
95
+ end
96
+
97
+ def tech
98
+ @tech ||= @node.css(TECH_CSS).text.gsub('in ', '').downcase
99
+ end
100
+ end
101
+
102
+ # parses a single screening
103
+ class ScreeningNodeParser
104
+ # regex for time format
105
+ TIME_REGEX = %r(\d+/\d+/\d+ \d{2}\:\d{2})
106
+ # regex for D-Box screenings
107
+ DBOX_REGEX = /D-Box/
108
+
109
+ # @param [Nokigiri::Node] node a Nokogiri node object
110
+ def initialize(node)
111
+ @node = node
112
+ end
113
+
114
+ # hashes of screening attributes
115
+ # @return [Hash]
116
+ def to_hash
117
+ {
118
+ booking_url: booking_url,
119
+ time: utc_time,
120
+ variant: variant
121
+ }
122
+ end
123
+
124
+ private
125
+
126
+ def booking_url
127
+ link_attr('href').to_s
128
+ end
129
+
130
+ def link_attr(attribute)
131
+ @node.css('a').attribute(attribute)
132
+ end
133
+
134
+ def info
135
+ @node.css('i').to_s
136
+ end
137
+
138
+ def time
139
+ Time.parse(time_string)
140
+ end
141
+
142
+ def time_string
143
+ link_attr('title').to_s.match(TIME_REGEX).to_s + ' UTC'
144
+ end
145
+
146
+ def tz
147
+ @tz ||= TZInfo::Timezone.get('Europe/London')
148
+ end
149
+
150
+ def utc_time
151
+ tz.local_to_utc(time)
152
+ end
153
+
154
+ def variant
155
+ info.match(DBOX_REGEX) ? 'd-box' : ''
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,38 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Html
5
+ # The object representing a single screening of a film on the Odeon UK website
6
+ class Screenings
7
+ # css selector for film html chunks
8
+ FILM_CSS = '.film-detail'
9
+
10
+ # All currently listed films showing at a cinema
11
+ # @param [Integer] cinema_id id of the cinema on the website
12
+ # @return [Array<Hash>]
13
+ def self.at(cinema_id)
14
+ film_nodes(cinema_id).flat_map do |node|
15
+ screenings_parser(node).to_a
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def self.screenings_parser(html)
22
+ Parser::FilmWithScreenings.new(html)
23
+ end
24
+
25
+ def self.film_nodes(id)
26
+ showtimes_doc(id).css(FILM_CSS).map { |node| node.to_s.gsub(/^\s+/, '') }
27
+ end
28
+
29
+ def self.showtimes(id)
30
+ OdeonUk::Html::Website.new.showtimes(id)
31
+ end
32
+
33
+ def self.showtimes_doc(id)
34
+ Nokogiri::HTML(showtimes(id))
35
+ end
36
+ end
37
+ end
38
+ end
@@ -3,7 +3,7 @@ require 'open-uri'
3
3
  module OdeonUk
4
4
  # Internal utility classes: Do not use
5
5
  # @api private
6
- module Internal
6
+ module Html
7
7
  # Utility class to make calls to the odeon website
8
8
  class Website
9
9
  # cinema page
@@ -13,6 +13,7 @@ module OdeonUk
13
13
  /\(live\)/i, # live in brackets
14
14
  'UKJFF -', # UK Jewish festival prefix
15
15
  /\bsing\-?a\-?long\b/i, # singalong
16
+ 'Autism Friendly - ', # autism friendly
16
17
  ]
17
18
 
18
19
  # regexes and their replacements
@@ -5,6 +5,8 @@ module OdeonUk
5
5
  attr_reader :booking_url
6
6
  # @return [String] 2d or 3d
7
7
  attr_reader :dimension
8
+ # @return [String] the cinema id
9
+ attr_reader :cinema_id
8
10
  # @return [String] the cinema name
9
11
  attr_reader :cinema_name
10
12
  # @return [String] the film name
@@ -32,11 +34,9 @@ module OdeonUk
32
34
  # @param [Integer] cinema_id id of the cinema on the website
33
35
  # @return [Array<OdeonUk::Screening>]
34
36
  def self.at(cinema_id)
35
- showtimes_page(cinema_id).to_a.map do |html|
36
- screenings_parser(html).to_a.map do |attributes|
37
- new(attributes.merge(cinema_hash(cinema_id)))
38
- end
39
- end.flatten
37
+ cinema = { cinema_id: cinema_id, cinema_name: Cinema.new(cinema_id).name }
38
+
39
+ parser.at(cinema_id).map { |hash| new(hash.merge(cinema)) }
40
40
  end
41
41
 
42
42
  # The date of the screening
@@ -60,16 +60,8 @@ module OdeonUk
60
60
 
61
61
  private
62
62
 
63
- def self.screenings_parser(html)
64
- OdeonUk::Internal::FilmWithScreeningsParser.new(html)
65
- end
66
-
67
- def self.showtimes_page(cinema_id)
68
- OdeonUk::Internal::ShowtimesPage.new(cinema_id)
69
- end
70
-
71
- def self.cinema_hash(id)
72
- { cinema_id: id, cinema_name: Cinema.find(id).name }
63
+ def self.parser
64
+ Html::Screenings
73
65
  end
74
66
  end
75
67
  end
@@ -1,6 +1,6 @@
1
1
  # Ruby interface for http://www.odeon.co.uk
2
- # @version 2.0.4
2
+ # @version 3.0.0
3
3
  module OdeonUk
4
4
  # Gem version
5
- VERSION = '2.0.4'
5
+ VERSION = '3.0.0'
6
6
  end
data/lib/odeon_uk.rb CHANGED
@@ -1,16 +1,23 @@
1
+ require 'forwardable'
1
2
  require 'nokogiri'
2
3
  require 'tzinfo'
3
4
  require 'tzinfo/data'
4
5
 
5
6
  require_relative './odeon_uk/version'
7
+ require_relative './odeon_uk/configuration'
6
8
 
7
- require_relative './odeon_uk/internal/film_with_screenings_parser'
8
- require_relative './odeon_uk/internal/showtimes_page'
9
9
  require_relative './odeon_uk/internal/title_sanitizer'
10
- require_relative './odeon_uk/internal/website'
10
+
11
+ require_relative './odeon_uk/api/response'
12
+ require_relative './odeon_uk/api/cinema'
13
+ require_relative './odeon_uk/api/screenings'
14
+
15
+ require_relative './odeon_uk/html/parser/film_with_screenings'
16
+ require_relative './odeon_uk/html/website'
17
+ require_relative './odeon_uk/html/cinema'
18
+ require_relative './odeon_uk/html/screenings'
11
19
 
12
20
  require_relative './odeon_uk/cinema'
13
- require_relative './odeon_uk/film'
14
21
  require_relative './odeon_uk/screening'
15
22
 
16
23
  # odeon_uk gem
data/odeon_uk.gemspec CHANGED
@@ -21,8 +21,10 @@ Gem::Specification.new do |gem|
21
21
  gem.version = OdeonUk::VERSION
22
22
 
23
23
  gem.add_development_dependency 'rake'
24
+ gem.add_development_dependency 'minitest-reporters'
24
25
  gem.add_development_dependency 'webmock'
25
26
 
27
+ gem.add_runtime_dependency 'CFPropertyList'
26
28
  gem.add_runtime_dependency 'nokogiri'
27
29
  gem.add_runtime_dependency 'tzinfo'
28
30
  gem.add_runtime_dependency 'tzinfo-data'
@@ -0,0 +1,98 @@
1
+ module FixtureCreator
2
+ class Api
3
+ def all_cinemas!
4
+ write_fixture(:all_cinemas)
5
+ end
6
+
7
+ def app_init!
8
+ write_fixture(:app_init)
9
+ end
10
+
11
+ def film_times!(cinema_id)
12
+ OdeonUk::Api::Screenings.send(:film_ids, cinema_id).each do |i|
13
+ write_film_times_fixture(cinema_id, i)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def fixture(name)
20
+ File.expand_path("../../test/fixtures/api/#{name}.plist", __FILE__)
21
+ end
22
+
23
+ def log(message)
24
+ puts "Create API fixture: #{message}"
25
+ end
26
+
27
+ def write_film_times_fixture(cinema_id, film_id)
28
+ FileUtils.mkdir_p 'film_times'
29
+ text = "film_times/#{cinema_id}-#{film_id}"
30
+ File.open(fixture(text), 'w+') do |file|
31
+ log(text)
32
+ file.write OdeonUk::Api::Response.new.send(:film_times_raw,
33
+ cinema_id,
34
+ film_id)
35
+ end
36
+ end
37
+
38
+ def write_fixture(kind)
39
+ FileUtils.mkdir_p kind.to_s
40
+ File.open(fixture(kind), 'w+') do |file|
41
+ log(kind)
42
+ file.write OdeonUk::Api::Response.new.send("#{kind}_raw".to_sym)
43
+ end
44
+ end
45
+ end
46
+
47
+ class Html < Struct.new(:cinema_id)
48
+ def cinema!
49
+ write_page_fixture(:cinema)
50
+ end
51
+
52
+ def film_node!(index)
53
+ write_node_fixture(index)
54
+ end
55
+
56
+ def showtimes!
57
+ write_page_fixture(:showtimes)
58
+ end
59
+
60
+ def sitemap!
61
+ write_page_fixture(:sitemap)
62
+ end
63
+
64
+ private
65
+
66
+ def fixture(name)
67
+ File.expand_path("../../test/fixtures/html/#{name}.html", __FILE__)
68
+ end
69
+
70
+ def log(message)
71
+ puts "Create HTML fixture: #{message}"
72
+ end
73
+
74
+ def write_node_fixture(index)
75
+ FileUtils.mkdir_p 'showtimes'
76
+ text = "showtimes/#{cinema_id}-#{index}"
77
+ File.open(fixture(text), 'w+') do |file|
78
+ log(text)
79
+ nodes = OdeonUk::Html::Screenings.send(:film_nodes, cinema_id)
80
+ node = if index.is_a?(Fixnum)
81
+ nodes[index]
82
+ else
83
+ nodes.select { |n| !!n.to_s.match(/#{index}/i) }[0]
84
+ end
85
+ file.write(node.to_s)
86
+ end
87
+ end
88
+
89
+ def write_page_fixture(kind)
90
+ FileUtils.mkdir_p kind.to_s
91
+ text = kind.to_s + (cinema_id ? "/#{cinema_id}" : '')
92
+ File.open(fixture(text), 'w+') do |file|
93
+ log(text)
94
+ file.write OdeonUk::Html::Website.new.send(*[kind, cinema_id].compact)
95
+ end
96
+ end
97
+ end
98
+ end
Binary file
Binary file