odeon_uk 2.0.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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