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
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