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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb0f0e0ede5e635b08ea320250268c0788e200cf
4
- data.tar.gz: 997af2dfe1fb2d36ac715f807ae996d105c868d3
3
+ metadata.gz: 012176725c0f9d576455a7bbefd6da08dd415f43
4
+ data.tar.gz: 482cbfad8d2372243a791074fa356293098a3e90
5
5
  SHA512:
6
- metadata.gz: 38beb6721c4ec3c38ea9764177cd08836d18ab6cb8f9325f3fdb1a0c17206a219933cd4c27db44cb2b688c6224c27e2de6dd644ba82f73218be7469f1a6df660
7
- data.tar.gz: e70416c3e33a8e0600fcdf62bce224fd3cbc7a4f3c9a8241f7dc7fc27b53a61a6197dbb9809ee1fb87ef2d425fd84d7a30336a51ba0951e2c62cb101ba920e7b
6
+ metadata.gz: 36489dec0d6411c706399f4e64afdac6c12497ff86e2afc91865a6211b117985fddd52f68329a2786f88f11b574f3bf254bfdf928b146ad4bf140e004a591eee
7
+ data.tar.gz: 9c6e62a13a0201e4d83314a4818debfea7b08ee423f74970cb0f436eb62c40b13b0a3d148faa9a6e62f4cdf7bd8c052111354f9bb60fed5fdd1c63218cf5e53d
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.0.0
4
- - 2.1.2
4
+ - 2.1.5
5
+ - 2.2.1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 3.0.0 - 2015-03-15
2
+
3
+ - complete internal re-architecture ready for use of API
4
+ - include API source
5
+ - remove Film model (not used)
6
+
1
7
  ## 2.0.4 - 2015-02-18
2
8
 
3
9
  - protect against unopened cinemas
data/Rakefile CHANGED
@@ -7,12 +7,16 @@ Rake::TestTask.new do |t|
7
7
  t.libs << 'lib/odeon_uk'
8
8
  t.test_files = FileList[
9
9
  'test/lib/odeon_uk/*_test.rb',
10
+ 'test/lib/odeon_uk/api/*_test.rb',
11
+ 'test/lib/odeon_uk/html/*_test.rb',
12
+ 'test/lib/odeon_uk/html/parser/*_test.rb',
10
13
  'test/lib/odeon_uk/internal/*_test.rb'
11
14
  ]
12
15
  t.verbose = true
13
16
  end
14
17
 
15
18
  # http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
19
+ desc 'run gem console'
16
20
  task :console do
17
21
  require 'irb'
18
22
  require 'irb/completion'
@@ -21,4 +25,29 @@ task :console do
21
25
  IRB.start
22
26
  end
23
27
 
28
+ desc 'recreate test fixtures'
29
+ namespace :fixtures do
30
+ require 'odeon_uk'
31
+ require_relative 'rake/fixture_creator'
32
+
33
+ desc 'html'
34
+ task :html do
35
+ FixtureCreator::Html.new(nil).sitemap!
36
+ FixtureCreator::Html.new(71).cinema! # brighton
37
+ FixtureCreator::Html.new(211).cinema! # bfi imax
38
+ FixtureCreator::Html.new(105).cinema! # leceister square
39
+ FixtureCreator::Html.new(71).showtimes!
40
+ FixtureCreator::Html.new(71).film_node!(0)
41
+ FixtureCreator::Html.new(11).film_node!('imax') # manchester imax
42
+ FixtureCreator::Html.new(171).film_node!('d-box') # liverpool dbox
43
+ end
44
+
45
+ desc 'api'
46
+ task :api do
47
+ FixtureCreator::Api.new.app_init!
48
+ FixtureCreator::Api.new.all_cinemas!
49
+ FixtureCreator::Api.new.film_times!(71)
50
+ end
51
+ end
52
+
24
53
  task default: :test
@@ -0,0 +1,62 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Api
5
+ # The object representing a cinema on the Odeon UK website
6
+ class Cinema
7
+ # @return [Integer] the numeric id of the cinema via the API
8
+ attr_reader :id
9
+
10
+ # @param [Integer, String] id cinema id
11
+ # @return [OdeonUk::Cinema]
12
+ def initialize(id)
13
+ @id = id.to_i
14
+ end
15
+
16
+ # Return basic cinema information for all cinemas
17
+ # @return [Array<OdeonUk::Cinema>]
18
+ def self.ids
19
+ cinemas_hash.keys.map(&:to_i)
20
+ end
21
+
22
+ # The locality (town) of the cinema
23
+ # @return [String]
24
+ def locality
25
+ cinema_hash['siteAddress2']
26
+ end
27
+
28
+ # The name of the cinema
29
+ # @return [String]
30
+ def name
31
+ cinema_hash['siteName']
32
+ end
33
+
34
+ # The url of the cinema
35
+ # @return [Nil]
36
+ def url
37
+ end
38
+
39
+ # Post code of the cinema
40
+ # @return [String]
41
+ def postal_code
42
+ cinema_hash['sitePostcode']
43
+ end
44
+
45
+ # The street adress of the cinema
46
+ # @return [String]
47
+ def street_address
48
+ cinema_hash['siteAddress1']
49
+ end
50
+
51
+ private
52
+
53
+ def self.cinemas_hash
54
+ @@cinemas_hash ||= Api::Response.new.all_cinemas['data']['sites']
55
+ end
56
+
57
+ def cinema_hash
58
+ @cinema_hash ||= self.class.cinemas_hash[id.to_s]
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,55 @@
1
+ require 'cfpropertylist'
2
+ require 'net/http'
3
+
4
+ module OdeonUk
5
+ # Internal utility classes: Do not use
6
+ # @api private
7
+ module Api
8
+ # Utility class to make calls to the odeon website
9
+ class Response
10
+ VERSION = '2.1'
11
+
12
+ # cinemas information
13
+ # @return [Hash] decoded response of api containing cinema details
14
+ def all_cinemas
15
+ parse(all_cinemas_raw)
16
+ end
17
+
18
+ # application initialize
19
+ # @return [Hash] decoded response of api, mostly films
20
+ def app_init
21
+ parse(app_init_raw)
22
+ end
23
+
24
+ # showings for a film at a cinema
25
+ # @return [Hash] decoded response of api, day split times
26
+ def film_times(cinema_id, film_id)
27
+ parse(film_times_raw(cinema_id, film_id))
28
+ end
29
+
30
+ private
31
+
32
+ def all_cinemas_raw
33
+ post('all-cinemas').body
34
+ end
35
+
36
+ def app_init_raw
37
+ post('app-init').body
38
+ end
39
+
40
+ def film_times_raw(cinema_id, film_id)
41
+ post('film-times', { s: cinema_id, m: film_id }).body
42
+ end
43
+
44
+ def post(path, request_body={})
45
+ uri = URI("https://api.odeon.co.uk/#{VERSION}/api/#{path}")
46
+ Net::HTTP.post_form(uri, request_body)
47
+ end
48
+
49
+ def parse(content)
50
+ plist = CFPropertyList::List.new(data: content)
51
+ CFPropertyList.native_types(plist.value).fetch('data', {})
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,89 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Api
5
+ # The object representing a single screening of a film via the Odeon UK api
6
+ class Screenings
7
+ # All currently listed screenings at a cinema
8
+ # @param [Integer] cinema_id id of the cinema
9
+ # @return [Array<Hash>]
10
+ def self.at(cinema_id)
11
+ film_ids(cinema_id).flat_map do |film_id|
12
+ times_response = response.film_times(cinema_id, film_id)
13
+
14
+ times_response.flat_map do |day|
15
+ date = day['date']
16
+ screenings = day['attributes']
17
+
18
+ screenings.flat_map do |screening|
19
+ performances = screening['showtimes']
20
+
21
+ performances.flat_map do |p|
22
+ {
23
+ dimension: screening['attribute'].include?('3D') ? '3d' : '2d',
24
+ film_name: films_map(cinema_id)[film_id],
25
+ time: TimeParser.new(date, p[0]['performanceTime']).to_utc,
26
+ variant: '',
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def self.app_init
37
+ @@app_init ||= Response.new.app_init
38
+ end
39
+
40
+ def self.film_ids(cinema_id)
41
+ films_map(cinema_id).keys
42
+ end
43
+
44
+ def self.films_map(cinema_id)
45
+ films_at(cinema_id).each_with_object({}) do |f, result|
46
+ result[f['filmMasterId']] = f['title']
47
+ end
48
+ end
49
+
50
+ def self.films_at(cinema_id)
51
+ app_init['films'].select { |f| f['sites'].include?(cinema_id) }
52
+ end
53
+
54
+ def self.response
55
+ @@response ||= Response.new
56
+ end
57
+ end
58
+
59
+ TimeParser = Struct.new(:date, :time) do
60
+ def to_utc
61
+ tz.local_to_utc(parsed_time)
62
+ end
63
+
64
+ private
65
+
66
+ def early_morning_screening?
67
+ !!time.match(/\A0/)
68
+ end
69
+
70
+ def parsed_date
71
+ case date
72
+ when 'Today' then Time.now.strftime '%Y-%m-%d'
73
+ when 'Tomorrow' then (Time.now + 60 * 60 * 24).strftime '%Y-%m-%d'
74
+ else date.gsub('.', '').match(/\d+\s\w+\z/)[0]
75
+ end
76
+ end
77
+
78
+ def parsed_time
79
+ parsed_time = Time.parse("#{parsed_date} #{time} UTC")
80
+ # Odeon deliberately mislabel their 00:01 screenings as the day before
81
+ early_morning_screening? ? parsed_time + 60 * 60 * 24 : parsed_time
82
+ end
83
+
84
+ def tz
85
+ @tz ||= TZInfo::Timezone.get('Europe/London')
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,49 +1,49 @@
1
1
  module OdeonUk
2
2
  # The object representing a cinema on the Odeon UK website
3
3
  class Cinema
4
- # @return [String] the brand of the cinema
5
- attr_reader :brand
4
+ extend Forwardable
5
+
6
6
  # @return [Integer] the numeric id of the cinema on the Odeon website
7
7
  attr_reader :id
8
- # @return [String] the name of the cinema
9
- attr_reader :name
10
- # @return [String] the slug of the cinema
11
- attr_reader :slug
12
- # @return [String] the url of the cinema on the Odeon website
13
- attr_reader :url
14
8
 
15
- # @param [Integer, String] id cinema id
16
- # @param [String] name cinema name
17
- # @param [String] url url on Odeon website
9
+ # @!method locality
10
+ # The locality (town) of the cinema
11
+ # @return [String]
12
+ # @example
13
+ # cinema = OdeonUk::Cinema.find('71')
14
+ # cinema.locality
15
+ # #=> 'Brighton'
16
+ # @!method postal_code
17
+ # Post code of the cinema
18
+ # @return [String]
19
+ # @example
20
+ # cinema = OdeonUk::Cinema.find('71')
21
+ # cinema.postal_code
22
+ # #=> 'BN1 2RE'
23
+ # @!method street_address
24
+ # The street adress of the cinema
25
+ # @return [String]
26
+ # @example
27
+ # cinema = OdeonUk::Cinema.find('71')
28
+ # cinema.street_address
29
+ # #=> 'Kingswest'
30
+ # @!method url
31
+ # Website URI for the cinema
32
+ # @return [String] the url of the cinema on the Odeon website
33
+ def_delegators :cinema_parser, :locality, :postal_code, :street_address,
34
+ :url
35
+
36
+ # @param [Integer, String] id the cinema id of the format 71/'71' as used on
37
+ # the odeon.co.uk website
18
38
  # @return [OdeonUk::Cinema]
19
- def initialize(id, name, url)
20
- @brand = 'Odeon'
21
- @id = id.to_i
22
- @name = name.gsub('London - ', '').gsub(' - ', ': ')
23
- @slug = @name.downcase.gsub(/[^0-9a-z ]/, '').gsub(/\s+/, '-')
24
- @url = (url[0] == '/') ? "http://www.odeon.co.uk#{url}" : url
39
+ def initialize(id)
40
+ @id = id
25
41
  end
26
42
 
27
43
  # Return basic cinema information for all cinemas
28
44
  # @return [Array<OdeonUk::Cinema>]
29
45
  def self.all
30
- cinema_links.map do |link|
31
- new_from_link link
32
- end
33
- end
34
-
35
- # Find a single cinema
36
- # @param [Integer, String] id the cinema id of the format 71/'71' as used on
37
- # the odeon.co.uk website
38
- # @return [OdeonUk::Cinema, nil]
39
- # @example
40
- # OdeonUk::Cinema.find('71')
41
- # #=> <OdeonUk::Cinema brand="Odeon" name="Brighton" slug="brighton" ...>
42
- def self.find(id)
43
- id = id.to_i
44
- return nil unless id > 0
45
-
46
- all.select { |cinema| cinema.id == id }[0]
46
+ cinema_parser_class.ids.map { |cinema_id| new(cinema_id) }
47
47
  end
48
48
 
49
49
  # Address of the cinema
@@ -58,17 +58,16 @@ module OdeonUk
58
58
  def adr
59
59
  {
60
60
  street_address: street_address,
61
- locality: locality,
62
- postal_code: postal_code,
63
- country: 'United Kingdom'
61
+ locality: locality,
62
+ postal_code: postal_code,
63
+ country_name: 'United Kingdom'
64
64
  }
65
65
  end
66
66
  alias_method :address, :adr
67
67
 
68
- # Films with showings scheduled at this cinema
69
- # @return [Array<OdeonUk::Film>]
70
- def films
71
- Film.at(id)
68
+ # @return [String] 'Odeon'
69
+ def brand
70
+ 'Odeon'
72
71
  end
73
72
 
74
73
  # The name of the cinema including the brand
@@ -78,29 +77,13 @@ module OdeonUk
78
77
  # cinema.full_name
79
78
  # #=> 'Odeon Brighton'
80
79
  def full_name
81
- "#{brand} #{name}"
82
- end
83
-
84
- # The locality (town) of the cinema
85
- # @return [String]
86
- # @example
87
- # cinema = OdeonUk::Cinema.find('71')
88
- # cinema.locality
89
- # #=> 'Brighton'
90
- def locality
91
- return unless address_node
92
- address_node.text.match(/\w+(\s\w+){0,}\s+(\w+(\s\w+){0,})/)[2]
80
+ @full_name ||= "#{brand} #{name}"
93
81
  end
94
82
 
95
- # Post code of the cinema
96
- # @return [String]
97
- # @example
98
- # cinema = OdeonUk::Cinema.find('71')
99
- # cinema.postal_code
100
- # #=> 'BN1 2RE'
101
- def postal_code
102
- return unless address_node
103
- address_node.text.match(/[A-Z]{1,2}\d{1,2}[A-Z]?\s\d{1,2}[A-Z]{1,2}/)[0]
83
+ # Cinema name, slightly sanitized
84
+ # @return [String] the name of the cinema
85
+ def name
86
+ @name ||= cinema_parser.name.gsub('London - ', '').gsub(' - ', ': ')
104
87
  end
105
88
 
106
89
  # All planned screenings
@@ -113,64 +96,20 @@ module OdeonUk
113
96
  Screening.at(id)
114
97
  end
115
98
 
116
- # Screenings for particular film
117
- # @param [OdeonUk::Film, String] film a film object or title of the film
118
- # @return [Array<Odeon::Screening>]
119
- # @example
120
- # cinema = OdeonUk::Cinema.find('71')
121
- # cinema.screenings_of('Iron Man 3')
122
- # #=> [<OdeonUk::Screening film_name="Iron Man 3" cinema_name="Brighton" when="..." variant="...">, <OdeonUk::Screening ...>]
123
- # iron_man_3 = OdeonUk::Film.new "Iron Man 3"
124
- # cinema.screenings_of(iron_man_3)
125
- # #=> [<OdeonUk::Screening film_name="Iron Man 3" cinema_name="Brighton" when="..." variant="...">, <OdeonUk::Screening ...>]
126
- def screenings_of film
127
- film_name = (film.is_a?(OdeonUk::Film) ? film.name : film)
128
- screenings.select { |s| s.film_name == film_name }
129
- end
130
-
131
- # The street adress of the cinema
132
- # @return a String
133
- # @example
134
- # cinema = OdeonUk::Cinema.find('71')
135
- # cinema.street_address
136
- # #=> 'Kingswest'
137
- def street_address
138
- return unless address_node
139
- address_node.text.match(/\A\s+(\w+(\s\w+){0,})/)[1]
99
+ # slug from the hotel name
100
+ # @return [String] the slug of the cinema
101
+ def slug
102
+ @slug ||= full_name.downcase.gsub(/[^0-9a-z ]/, '').gsub(/\s+/, '-')
140
103
  end
141
104
 
142
105
  private
143
106
 
144
- def self.cinema_links
145
- links = parsed_sitemap.css('.sitemap a[href*=cinemas]')
146
- links.select { |link| link.get_attribute('href').match(/\/\d+\/$/) }
147
- end
148
-
149
- def self.new_from_link(link)
150
- url = link.get_attribute('href')
151
- id = url.match(/\/(\d+)\/$/)[1]
152
- name = link.children.first.to_s
153
- new id, name, url
154
- end
155
-
156
- def self.parsed_sitemap
157
- Nokogiri::HTML(sitemap_response)
158
- end
159
-
160
- def self.sitemap_response
161
- @sitemap_response ||= OdeonUk::Internal::Website.new.sitemap
162
- end
163
-
164
- def address_node
165
- parsed_cinema.css('.gethere .span4 .description')[0]
166
- end
167
-
168
- def cinema_response
169
- @cinema_response ||= OdeonUk::Internal::Website.new.cinema(@id)
107
+ def self.cinema_parser_class
108
+ OdeonUk.configuration.method == :api ? Api::Cinema : Html::Cinema
170
109
  end
171
110
 
172
- def parsed_cinema
173
- Nokogiri::HTML(cinema_response)
111
+ def cinema_parser
112
+ @cinema_parser ||= self.class.cinema_parser_class.new(id)
174
113
  end
175
114
  end
176
115
  end
@@ -0,0 +1,21 @@
1
+ module OdeonUk
2
+ class << self
3
+ attr_accessor :configuration
4
+ end
5
+
6
+ def self.configuration
7
+ @configuration ||= Configuration.new
8
+ end
9
+
10
+ def self.configure
11
+ yield(configuration)
12
+ end
13
+
14
+ class Configuration
15
+ attr_accessor :method
16
+
17
+ def initialize
18
+ @method = :html
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,101 @@
1
+ module OdeonUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Html
5
+ # The object representing a cinema on the Odeon UK website
6
+ class Cinema
7
+ ADDRESS_REGEX = '.gethere .span4 .description'
8
+ ALL_LINKS = '.sitemap a[href*=cinemas]'
9
+
10
+ # @return [Integer] the numeric id of the cinema on the Odeon website
11
+ attr_reader :id
12
+
13
+ # @param [Integer, String] id cinema id
14
+ # @return [OdeonUk::Cinema]
15
+ def initialize(id)
16
+ @id = id.to_i
17
+ end
18
+
19
+ # Return basic cinema information for all cinemas
20
+ # @return [Array<OdeonUk::Cinema>]
21
+ def self.ids
22
+ cinema_links.map { |link| id_from(link) }
23
+ end
24
+
25
+ # The locality (town) of the cinema
26
+ # @return [String]
27
+ def locality
28
+ return unless address_node
29
+ address_node.text.match(/\w+(\s\w+){0,}\s+(\w+(\s\w+){0,})/)[2]
30
+ end
31
+
32
+ # The name of the cinema
33
+ # @return [String]
34
+ def name
35
+ cinema_link_doc.children.first.to_s
36
+ end
37
+
38
+ # The web url of the cinema
39
+ # @return [String]
40
+ def url
41
+ @url ||= begin
42
+ link = cinema_link_doc.get_attribute('href')
43
+ (link[0] == '/') ? "http://www.odeon.co.uk#{link}" : link
44
+ end
45
+ end
46
+
47
+ # Post code of the cinema
48
+ # @return [String]
49
+ def postal_code
50
+ return unless address_node
51
+ address_node.text.match(/[A-Z]{1,2}\d{1,2}[A-Z]?\s\d{1,2}[A-Z]{1,2}/)[0]
52
+ end
53
+
54
+ # The street adress of the cinema
55
+ # @return [String]
56
+ def street_address
57
+ return unless address_node
58
+ address_node.text.match(/\A\s+(\w+(\s\w+){0,})/)[1]
59
+ end
60
+
61
+ private
62
+
63
+ def self.id_from(link)
64
+ link.get_attribute('href').match(/\/(\d+)\/$/)[1].to_i
65
+ end
66
+
67
+ def self.cinema_links
68
+ @@cinema_links ||= begin
69
+ links = sitemap_doc.css(ALL_LINKS)
70
+ links.select { |link| link.get_attribute('href').match(/\/\d+\/$/) }
71
+ end
72
+ end
73
+
74
+ def self.sitemap_doc
75
+ @@sitemap_doc ||= Nokogiri::HTML(sitemap_response)
76
+ end
77
+
78
+ def self.sitemap_response
79
+ @@sitemap_response ||= Html::Website.new.sitemap
80
+ end
81
+
82
+ def address_node
83
+ cinema_doc.css(ADDRESS_REGEX)[0]
84
+ end
85
+
86
+ def cinema_response
87
+ @cinema_response ||= Html::Website.new.cinema(id)
88
+ end
89
+
90
+ def cinema_doc
91
+ @cinema_doc ||= Nokogiri::HTML(cinema_response)
92
+ end
93
+
94
+ def cinema_link_doc
95
+ self.class.cinema_links.select do |link|
96
+ link.get_attribute('href').include?("/#{id}/")
97
+ end.first
98
+ end
99
+ end
100
+ end
101
+ end