cineworld_uk 1.0.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 +64 -0
  4. data/README.md +2 -1
  5. data/cineworld_uk.gemspec +3 -3
  6. data/lib/cineworld_uk/cinema.rb +60 -69
  7. data/lib/cineworld_uk/film.rb +25 -7
  8. data/lib/cineworld_uk/internal/film_with_screenings_parser.rb +29 -71
  9. data/lib/cineworld_uk/internal/name_parser.rb +20 -18
  10. data/lib/cineworld_uk/internal/screening_parser.rb +132 -0
  11. data/lib/cineworld_uk/internal/titleize.rb +19 -19
  12. data/lib/cineworld_uk/internal/website.rb +33 -0
  13. data/lib/cineworld_uk/internal/whatson_parser.rb +44 -0
  14. data/lib/cineworld_uk/screening.rb +65 -20
  15. data/lib/cineworld_uk/version.rb +2 -2
  16. data/lib/cineworld_uk.rb +4 -0
  17. data/test/fixture_updater.rb +64 -0
  18. data/test/fixtures/cinemas.html +134 -41
  19. data/test/fixtures/{cinemas/bury-st-edmunds.html → information/brighton.html} +488 -437
  20. data/test/fixtures/{cinemas → information}/bristol.html +463 -410
  21. data/test/fixtures/whatson/brighton/film_first.html +775 -0
  22. data/test/fixtures/whatson/brighton/film_last.html +52 -0
  23. data/test/fixtures/whatson/brighton/film_second.html +645 -0
  24. data/test/fixtures/whatson/brighton.html +3564 -2690
  25. data/test/fixtures/whatson/glasgow-imax-at-gsc/film_first.html +140 -0
  26. data/test/fixtures/whatson/the-o2-greenwich/film_first.html +1242 -0
  27. data/test/lib/cineworld_uk/cinema_test.rb +104 -301
  28. data/test/lib/cineworld_uk/film_test.rb +49 -2
  29. data/test/lib/cineworld_uk/internal/film_with_screenings_parser_test.rb +83 -81
  30. data/test/lib/cineworld_uk/internal/titleize_test.rb +12 -5
  31. data/test/lib/cineworld_uk/internal/website_test.rb +57 -0
  32. data/test/lib/cineworld_uk/internal/whatson_parser_test.rb +58 -0
  33. data/test/lib/cineworld_uk/screening_test.rb +155 -23
  34. data/test/lib/cineworld_uk/version_test.rb +1 -1
  35. data/test/test_helper.rb +3 -1
  36. metadata +30 -33
  37. data/test/fixtures/cinemas/brighton.html +0 -8
  38. data/test/fixtures/cinemas/chelsea.html +0 -1030
  39. data/test/fixtures/cinemas/glasgow-imax-at-gsc.html +0 -916
  40. data/test/fixtures/cinemas/the-o2-grenwich.html +0 -1191
  41. data/test/fixtures/whatson/brighton/gravity.html +0 -2129
  42. data/test/fixtures/whatson/glasgow-imax-at-gsc/the-hunger-games-catching-fire.html +0 -498
  43. data/test/fixtures/whatson/glasgow-imax-at-gsc-cinema.html +0 -3160
  44. data/test/fixtures/whatson/the-o2-greenwich/gravity.html +0 -784
  45. data/test/fixtures/whatson/the-o2-greenwich/the-hobbit-desolation-of-smaug.html +0 -764
  46. data/test/fixtures/whatson/the-o2-greenwich.html +0 -6854
  47. data/test/fixtures/whatson/wandsworth.html +0 -13729
@@ -0,0 +1,132 @@
1
+ module CineworldUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Internal
5
+ # parse a single performance
6
+ class ScreeningParser
7
+ # regex for date in url
8
+ DATE_REGEX = /date\=(\d{4})(\d{2})(\d{2})/
9
+ # regex for time in url
10
+ TIME_REGEX = /time\=(\d{2})\:(\d{2})/
11
+
12
+ # css selector for dimension
13
+ DIMENSION_CSS = '.icon-service-2d, .icon-service-3d'
14
+ # css selector for baby screening
15
+ BABY_CSS = '.icon-service-cb'
16
+ # css selector for d-box screening
17
+ DBOX_CSS = '.icon-service-dx'
18
+ # css selector for hfr screening
19
+ HFR_CSS = '.icon-service-hfr'
20
+ # css selector for imax screening
21
+ IMAX_CSS = '.icon-service-ix'
22
+ # css selector for kids screening
23
+ KIDS_CSS = '.icon-service-m4j'
24
+
25
+ # @param [String] html a chunk of html representing one screening
26
+ def initialize(html)
27
+ @html = html.to_s
28
+ end
29
+
30
+ # url to book this screening
31
+ # @return [String]
32
+ def booking_url
33
+ return unless bookable?
34
+ 'http://www.cineworld.co.uk' + doc.css('a').attribute('href')
35
+ end
36
+
37
+ # the dimension of the screening
38
+ # @return [String]
39
+ def dimension
40
+ return unless bookable?
41
+ doc.css(DIMENSION_CSS).text.downcase
42
+ end
43
+
44
+ # utc time of the screening
45
+ # @return [Time]
46
+ def time
47
+ return unless bookable?
48
+ timezone.local_to_utc(Time.utc(*date_array + time_array))
49
+ end
50
+
51
+ # anything special about the screening
52
+ # @return [Array<String>]
53
+ # @example
54
+ # Cineworld::Internal::ScreeningParser.new(html).tags
55
+ # => ["imax"]
56
+ def variant
57
+ return unless bookable?
58
+ [baby, dbox, hfr, imax, kids].compact
59
+ end
60
+
61
+ # a attributes of a single screening
62
+ # @return [Hash]
63
+ # @example
64
+ # Cineworld::Internal::ScreeningParser.new(html).to_hash
65
+ # => {
66
+ # booking_url: 'http://...',
67
+ # dimension: '2d',
68
+ # time: <Time>,
69
+ # variant: ['imax']
70
+ # }
71
+ def to_hash
72
+ return unless bookable?
73
+ {
74
+ booking_url: booking_url,
75
+ dimension: dimension,
76
+ time: time,
77
+ variant: variant
78
+ }
79
+ end
80
+
81
+ private
82
+
83
+ def baby
84
+ 'baby' if doc.css(BABY_CSS).length > 0
85
+ end
86
+
87
+ def bookable?
88
+ @bookable ||= doc.css('a.performance').size > 0
89
+ end
90
+
91
+ def date_array
92
+ @date_array ||= performance_link_html.match(DATE_REGEX) do |match|
93
+ [match[1], match[2], match[3]]
94
+ end
95
+ end
96
+
97
+ def dbox
98
+ 'dbox' if doc.css(DBOX_CSS).length > 0
99
+ end
100
+
101
+ def doc
102
+ @doc ||= Nokogiri::HTML(@html)
103
+ end
104
+
105
+ def hfr
106
+ 'hfr' if doc.css(HFR_CSS).length > 0
107
+ end
108
+
109
+ def imax
110
+ 'imax' if doc.css(IMAX_CSS).length > 0
111
+ end
112
+
113
+ def kids
114
+ 'kids' if doc.css(KIDS_CSS).length > 0
115
+ end
116
+
117
+ def performance_link_html
118
+ @performance_link_html ||= doc.css('a.performance').to_s
119
+ end
120
+
121
+ def time_array
122
+ @time_array ||= performance_link_html.match(TIME_REGEX) do |match|
123
+ [match[1], match[2]]
124
+ end
125
+ end
126
+
127
+ def timezone
128
+ @timezone ||= TZInfo::Timezone.get('Europe/London')
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,15 +1,13 @@
1
+ # encoding: UTF-8
1
2
  module CineworldUk
2
-
3
3
  # Internal utility classes: Do not use
4
4
  # @api private
5
5
  module Internal
6
-
7
6
  # @note Modified from titleize gem
8
7
  # https://github.com/granth/titleize
9
8
  module Titleize
10
-
11
9
  # List of words not to capitalize unless they lead a phrase
12
- SMALL_WORDS = %w{a an and as at but by en for if in of on or the to via vs vs.}
10
+ WORDS = %w(a an and as at but by en for if in of on or the to via vs vs.)
13
11
 
14
12
  extend self
15
13
 
@@ -27,24 +25,24 @@ module CineworldUk
27
25
  # @return [String]
28
26
  def titleize(title)
29
27
  title = title.dup
30
- title.downcase! unless title[/[[:lower:]]/] # assume all-caps need fixing
28
+ title.downcase! unless title[/[[:lower:]]/] # assume all-caps fixing
31
29
 
32
30
  phrases(title).map do |phrase|
33
31
  words = phrase.split
34
32
  words.map do |word|
35
33
  def word.capitalize
36
34
  # like String#capitalize, but it starts with the first letter
37
- self.sub(/[[:alpha:]].*/) {|subword| subword.capitalize}
35
+ sub(/[[:alpha:]].*/) { |subword| subword.capitalize }
38
36
  end
39
37
 
40
38
  case word
41
- when /[[:alpha:]]\.[[:alpha:]]/ # words with dots in, like "example.com"
39
+ when /[[:alpha:]]\.[[:alpha:]]/ # words with dots in
42
40
  word
43
41
  when /[-‑]/ # hyphenated word (regular and non-breaking)
44
42
  word.split(/([-‑])/).map do |part|
45
- SMALL_WORDS.include?(part) ? part : part.capitalize
43
+ WORDS.include?(part) ? part : part.capitalize
46
44
  end.join
47
- when /^[[:alpha:]].*[[:upper:]]/ # non-first letter capitalized already
45
+ when /^[[:alpha:]].*[[:upper:]]/ # non-first letter capitalized
48
46
  word
49
47
  when /^[[:digit:]]/ # first character is a number
50
48
  word
@@ -52,31 +50,33 @@ module CineworldUk
52
50
  word.upcase
53
51
  when words.first, words.last
54
52
  word.capitalize
55
- when *(SMALL_WORDS + SMALL_WORDS.map {|small| small.capitalize })
53
+ when *(WORDS + WORDS.map { |small| small.capitalize })
56
54
  word.downcase
57
55
  else
58
56
  word.capitalize
59
57
  end
60
- end.join(" ")
61
- end.join(" ")
58
+ end.join(' ')
59
+ end.join(' ')
62
60
  end
63
61
 
64
62
  # Splits a title into an array based on punctuation.
65
63
  # @param [String] title Film title
66
64
  # @return [Array<String>]
67
65
  #
68
- # "simple title" # => ["simple title"]
69
- # "more complicated: titling" # => ["more complicated:", "titling"]
70
- # "even more: complicated - titling" # => ["even more:", "complicated -", "titling"]
66
+ # Titleize.phrases("simple title")
67
+ # #=> ["simple title"]
68
+ # Titleize.phrases("more complicated: titling")
69
+ # #=> ["more complicated:", "titling"]
70
+ # Titleize.phrases("even more: complicated - titling")
71
+ # #=> ["even more:", "complicated -", "titling"]
71
72
  def phrases(title)
72
- phrases = title.scan(/.+?(?:[-:.;?!] |$)/).map {|phrase| phrase.strip }
73
+ phrases = title.scan(/.+?(?:[-:.;?!] |$)/).map { |phrase| phrase.strip }
73
74
 
74
75
  # rejoin phrases that were split on the '.' from a small word
75
76
  if phrases.size > 1
76
77
  phrases[0..-2].each_with_index do |phrase, index|
77
- if SMALL_WORDS.include?(phrase.split.last.downcase)
78
- phrases[index] << " " + phrases.slice!(index + 1)
79
- end
78
+ next unless WORDS.include?(phrase.split.last.downcase)
79
+ phrases[index] << ' ' + phrases.slice!(index + 1)
80
80
  end
81
81
  end
82
82
 
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+ module CineworldUk
3
+ # Internal utility classes: Do not use
4
+ # @api private
5
+ module Internal
6
+ # fetches pages from the cineworld.co.uk website
7
+ class Website
8
+ # get the cinema information page for passed id
9
+ # @return [String]
10
+ def cinema_information(id)
11
+ get("cinemas/#{id}/information")
12
+ end
13
+
14
+ # get the cinemas page
15
+ # @return [String]
16
+ def cinemas
17
+ get('cinemas')
18
+ end
19
+
20
+ # get the cinema page containing all upcoming films and screenings
21
+ # @return [String]
22
+ def whatson(id)
23
+ get("whatson?cinema=#{id}")
24
+ end
25
+
26
+ private
27
+
28
+ def get(path)
29
+ Net::HTTP.get(URI("http://www.cineworld.co.uk/#{path}"))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ module CineworldUk
2
+ # Internal utility classes: Do not use
3
+ # @api private
4
+ module Internal
5
+ # Parses a chunk of HTML to derive movie showing data
6
+ class WhatsonParser
7
+ # css selector for film html chunks
8
+ FILM_CSS = '#filter-reload > .span9 > .row:not(.schedule)'
9
+ # css selector for screenings html chunks
10
+ SCREENINGS_CSS = '#filter-reload > .span9 > .schedule'
11
+
12
+ # @param [Integer] cinema_id cineworld cinema id
13
+ def initialize(cinema_id)
14
+ @cinema_id = cinema_id
15
+ end
16
+
17
+ # break up the whats on page into individual chunks for each film
18
+ # @return [Array<String>] html chunks for a film and it's screenings
19
+ def films_with_screenings
20
+ films_html.each_with_index.map do |html, i|
21
+ html + screenings_html[i]
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def films_html
28
+ whatson_doc.css(FILM_CSS).map { |n| n.to_s.gsub(/^\s+/, '') }
29
+ end
30
+
31
+ def screenings_html
32
+ whatson_doc.css(SCREENINGS_CSS).map { |n| n.to_s.gsub(/^\s+/, '') }
33
+ end
34
+
35
+ def whatson
36
+ @whatson ||= CineworldUk::Internal::Website.new.whatson(@cinema_id)
37
+ end
38
+
39
+ def whatson_doc
40
+ @whatson_doc ||= Nokogiri::HTML(whatson)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,39 +1,84 @@
1
1
  module CineworldUk
2
-
3
2
  # The object representing a single screening on the Cineworld UK website
4
3
  class Screening
5
-
6
4
  # @return [String] the booking URL on the cinema website
7
5
  attr_reader :booking_url
6
+ # @return [String] 2d or 3d
7
+ attr_reader :dimension
8
8
  # @return [String] the cinema name
9
9
  attr_reader :cinema_name
10
10
  # @return [String] the film name
11
11
  attr_reader :film_name
12
- # @return [Time] the UTC time of the screening
13
- attr_reader :when
14
- # @return [String] the type of screening (2D, 3D, IMAX...)
15
- attr_reader :variant
16
12
 
17
- # @param [String] film_name the film name
18
- # @param [String] cinema_name the cinema name
19
- # @param [Time] time datetime of the screening (UTC preferred)
20
- # @param [String] booking_url direct link to the booking page for this screening
21
- # @param [String] variant the type of showing (e.g. 3d/baby/live)
22
- def initialize(film_name, cinema_name, time, booking_url=nil, variant=nil)
23
- @booking_url, @cinema_name, @film_name, @variant = booking_url, cinema_name, film_name, variant
24
- @when = time.utc? ? time : TZInfo::Timezone.get('Europe/London').local_to_utc(time)
13
+ # @param [Hash] options options hash
14
+ # @option options [String] :booking_url (nil) booking url for the screening
15
+ # @option options [String] :cinema_name name of the cinema
16
+ # @option options [String] :cinema_id website id of the cinema
17
+ # @option options [String] :dimension ('2d') dimension of the screening
18
+ # @option options [String] :film_name name of the film
19
+ # @option options [Time] :time utc time of the screening
20
+ # @option options [Array<String>] :variant ([]) type of screening
21
+ def initialize(options)
22
+ @booking_url = options.fetch(:booking_url, nil)
23
+ @cinema_name = options.fetch(:cinema_name)
24
+ @cinema_id = options.fetch(:cinema_id)
25
+ @dimension = options.fetch(:dimension, '2d')
26
+ @film_name = options.fetch(:film_name)
27
+ @time = options.fetch(:time)
28
+ @variant = options.fetch(:variant, [])
29
+ end
30
+
31
+ # All currently listed films showing at a cinema
32
+ # @param [Integer] cinema_id id of the cinema on the website
33
+ # @return [Array<CineworldUk::Screening>]
34
+ def self.at(cinema_id)
35
+ whatson_parser(cinema_id).films_with_screenings.map do |html|
36
+ create_for_single_film(html, cinema_id)
37
+ end.flatten
25
38
  end
26
39
 
27
40
  # The date of the screening
28
41
  # @return [Date]
29
- def date
30
- @when.to_date
42
+ def showing_on
43
+ showing_at.to_date
44
+ end
45
+
46
+ # The UTC time of the screening
47
+ # @return [Time]
48
+ def showing_at
49
+ @when ||= begin
50
+ if @time.utc?
51
+ @time
52
+ else
53
+ TZInfo::Timezone.get('Europe/London').local_to_utc(@time)
54
+ end
55
+ end
56
+ end
57
+
58
+ # The kinds of screening
59
+ # @return <Array[String]>
60
+ def variant
61
+ @variant.split(' ').map(&:downcase).sort
62
+ end
63
+
64
+ private
65
+
66
+ def self.cinema_name_hash(cinema_id)
67
+ { cinema_name: CineworldUk::Cinema.find(cinema_id).name }
68
+ end
69
+
70
+ def self.create_for_single_film(html, cinema_id)
71
+ screenings_parser(html).to_a.map do |attributes|
72
+ new cinema_name_hash(cinema_id).merge(attributes)
73
+ end
74
+ end
75
+
76
+ def self.screenings_parser(html)
77
+ CineworldUk::Internal::FilmWithScreeningsParser.new(html)
31
78
  end
32
79
 
33
- # @deprecated Please use {#variant} instead, I can't spell
34
- def varient
35
- warn "Please use #variant instead, I can't spell"
36
- variant
80
+ def self.whatson_parser(id)
81
+ CineworldUk::Internal::WhatsonParser.new(id)
37
82
  end
38
83
  end
39
84
  end
@@ -1,6 +1,6 @@
1
1
  # Ruby interface for http://www.cineworld.co.uk
2
- # @version 1.0.5
2
+ # @version 2.0.0
3
3
  module CineworldUk
4
4
  # Gem version
5
- VERSION = "1.0.5"
5
+ VERSION = '2.0.0'
6
6
  end
data/lib/cineworld_uk.rb CHANGED
@@ -8,10 +8,14 @@ require_relative './cineworld_uk/version'
8
8
  require_relative './cineworld_uk/internal/film_with_screenings_parser'
9
9
  require_relative './cineworld_uk/internal/name_parser'
10
10
  require_relative './cineworld_uk/internal/titleize'
11
+ require_relative './cineworld_uk/internal/screening_parser'
12
+ require_relative './cineworld_uk/internal/whatson_parser'
13
+ require_relative './cineworld_uk/internal/website'
11
14
 
12
15
  require_relative './cineworld_uk/cinema'
13
16
  require_relative './cineworld_uk/film'
14
17
  require_relative './cineworld_uk/screening'
15
18
 
19
+ # cineworld uk gem
16
20
  module CineworldUk
17
21
  end
@@ -0,0 +1,64 @@
1
+ require File.expand_path('../../lib/cineworld_uk.rb', __FILE__)
2
+
3
+ def fixture(name)
4
+ File.expand_path("../fixtures/#{name}.html", __FILE__)
5
+ end
6
+
7
+ File.open(fixture('cinemas'), 'w') do |file|
8
+ puts '* Cinemas'
9
+ file.write CineworldUk::Internal::Website.new.cinemas
10
+ end
11
+
12
+ # BRIGHTON
13
+
14
+ File.open(fixture('information/brighton'), 'w') do |file|
15
+ puts '* Brighton Information'
16
+ file.write CineworldUk::Internal::Website.new.cinema_information(3)
17
+ end
18
+
19
+ File.open(fixture('whatson/brighton'), 'w') do |file|
20
+ puts '* Brighton Whats On'
21
+ file.write CineworldUk::Internal::Website.new.whatson(3)
22
+ end
23
+
24
+ parser = CineworldUk::Internal::WhatsonParser.new(3)
25
+
26
+ File.open(fixture('whatson/brighton/film_first'), 'w') do |file|
27
+ puts '* Brighton Main Film'
28
+ file.write parser.films_with_screenings[0]
29
+ end
30
+
31
+ File.open(fixture('whatson/brighton/film_second'), 'w') do |file|
32
+ puts '* Brighton Second Film'
33
+ file.write parser.films_with_screenings[1]
34
+ end
35
+
36
+ File.open(fixture('whatson/brighton/film_last'), 'w') do |file|
37
+ puts '* Brighton Last Film'
38
+ file.write parser.films_with_screenings[-1]
39
+ end
40
+
41
+ # BRISTOL
42
+
43
+ File.open(fixture('information/bristol'), 'w') do |file|
44
+ puts '* Bristol Information'
45
+ file.write CineworldUk::Internal::Website.new.cinema_information(4)
46
+ end
47
+
48
+ # O2
49
+
50
+ parser = CineworldUk::Internal::WhatsonParser.new(79)
51
+
52
+ File.open(fixture('whatson/the-o2-greenwich/film_first'), 'w') do |file|
53
+ puts '* The O2 Greenwich Main Film'
54
+ file.write parser.films_with_screenings[0]
55
+ end
56
+
57
+ # GLASGOW IMAX
58
+
59
+ parser = CineworldUk::Internal::WhatsonParser.new(88)
60
+
61
+ File.open(fixture('whatson/glasgow-imax-at-gsc/film_first'), 'w') do |file|
62
+ puts '* Glasgow IMAX Main Film'
63
+ file.write parser.films_with_screenings[0]
64
+ end