fandango 1.0.0 → 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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +0 -1
  3. data/.irbrc +2 -3
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +2 -2
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +37 -0
  8. data/README.md +45 -22
  9. data/Rakefile +2 -2
  10. data/fandango.gemspec +3 -3
  11. data/lib/fandango.rb +13 -35
  12. data/lib/fandango/api.rb +14 -0
  13. data/lib/fandango/api/movies_near.rb +36 -0
  14. data/lib/fandango/api/theater_showtimes.rb +47 -0
  15. data/lib/fandango/movie.rb +34 -18
  16. data/lib/fandango/showtime.rb +18 -0
  17. data/lib/fandango/theater.rb +44 -35
  18. data/lib/fandango/version.rb +1 -1
  19. data/spec/api/movies_near.spec.rb +30 -0
  20. data/spec/api/theater_showtimes.spec.rb +36 -0
  21. data/spec/movie.spec.rb +17 -18
  22. data/spec/showtime.spec.rb +21 -0
  23. data/spec/spec_helper.rb +5 -10
  24. data/spec/support/fixture_helpers.rb +14 -0
  25. data/spec/support/fixtures/movies_near_me_73142.rss +185 -1
  26. data/spec/support/fixtures/{movies_near_me_73142.yaml → movies_near_me_73142.yml} +70 -70
  27. data/spec/support/fixtures/showtimes_amcquailspringsmall24_aaktw_2016_08_01.html +7264 -0
  28. data/spec/support/fixtures/theater_showtimes_amcquailspringsmall24.yml +203 -0
  29. data/spec/support/fixtures/theater_showtimes_amcquailspringsmall24_tomorrow.yml +190 -0
  30. data/spec/support/minitest.rb +9 -0
  31. data/spec/support/vcr.rb +6 -0
  32. data/spec/support/vcr_cassettes/theater_showtimes_amcquailspringsmall24.yml +10974 -0
  33. data/spec/support/vcr_cassettes/theater_showtimes_amcquailspringsmall24_tomorrow.yml +10759 -0
  34. data/spec/theater.spec.rb +58 -12
  35. metadata +79 -48
  36. data/.rvmrc +0 -1
  37. data/lib/fandango/parser.rb +0 -39
  38. data/spec/fandango.spec.rb +0 -41
  39. data/spec/parser.spec.rb +0 -12
  40. data/spec/support/fixtures/item.html +0 -1
  41. data/spec/support/helpers.rb +0 -25
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 852bc1a00a4783ec4b36469fe7db37179dd6201c
4
+ data.tar.gz: 3c9bd3977acc1d8e629bfbf84c6bc258d138b214
5
+ SHA512:
6
+ metadata.gz: a3ae6deafd0527b381e036167ff321277d8f86d4eed21fbe719ae30d2334819934e396ad0f8a2fb212a66a61457ed883a6a675d22e2b0b6c8e5db34a76d652e3
7
+ data.tar.gz: 9d6d73995bec4a0221385b667c65115e0e5cf99716f9894ea02428eb7c157d8156230bdfdda86ceca268aa9315ce2272e449d4afb6840ebc734b29058269ff20
data/.gitignore CHANGED
@@ -1,4 +1,3 @@
1
1
  *.gem
2
2
  .bundle
3
- Gemfile.lock
4
3
  pkg/*
data/.irbrc CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'bundler/setup'
2
2
  Bundler.require :default, :development
3
3
 
4
- require 'fandango'
4
+ WebMock.allow_net_connect!
5
5
 
6
- require './spec/support/helpers'
7
- include SpecHelpers
6
+ require 'fandango'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.3.1
data/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.2
4
- - 1.9.3
3
+ - 2.3.0
4
+ - 2.3.1
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in fandango.gemspec
4
4
  gemspec
5
+
6
+ gem 'rake' # For Travis-CI.
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fandango (2.0.0)
5
+ nokogiri (~> 1.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ addressable (2.4.0)
11
+ awesome_print (1.7.0)
12
+ crack (0.4.3)
13
+ safe_yaml (~> 1.0.0)
14
+ mini_portile2 (2.1.0)
15
+ minitest (5.9.0)
16
+ nokogiri (1.6.8.1)
17
+ mini_portile2 (~> 2.1.0)
18
+ rake (11.2.2)
19
+ safe_yaml (1.0.4)
20
+ vcr (3.0.3)
21
+ webmock (1.9.3)
22
+ addressable (>= 2.2.7)
23
+ crack (>= 0.3.2)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ awesome_print
30
+ fandango!
31
+ minitest
32
+ rake
33
+ vcr
34
+ webmock
35
+
36
+ BUNDLED WITH
37
+ 1.13.1
data/README.md CHANGED
@@ -6,34 +6,57 @@ Uses Fandango's RSS moviesnearme feed. E.g. http://www.fandango.com/rss/moviesne
6
6
 
7
7
  ## Usage
8
8
 
9
- `Fandango.movies_near(73142)` returns an array of hashes.
10
- Each hash has 2 items: theater info and movies on sale at that theater.
11
- A theater is a hash of data containing: name, Fandango's theater id, address, and postal code.
12
- The movies are an array of hashes. Each hash contains title and Fandango's id.
13
-
14
- ### Example output format
15
-
16
- ```
9
+ ```ruby
10
+ theaters_and_movies = Fandango.movies_near(73142)
11
+ =begin
17
12
  [
18
13
  [ 0] {
19
- :theater => {
20
- :name => "AMC",
21
- :id => "abcde",
22
- :address => "123 Baker St., New York, NY 10001",
23
- :postal_code => "10001"
24
- },
25
- :movies => [
26
- [0] {
27
- :title => "Sherlock Holmes",
28
- :id => "123456"
14
+ :name => "AMC Quail Springs Mall 24",
15
+ :id => "aaktw",
16
+ :address => "2501 West Memorial Oklahoma City, OK 73134",
17
+ :postal_code => "73134",
18
+ :showtimes_link => "http://www.fandango.com/amcquailspringsmall24_aaktw/theaterpage?wssaffid=11836&wssac=123",
19
+ :movies => [
20
+ [ 0] {
21
+ :title => "Abraham Lincoln: Vampire Hunter",
22
+ :id => "141897"
29
23
  },
30
- # more movies...
24
+ # ...
31
25
  ]
32
26
  },
33
- # more hashes...
27
+ # ...
34
28
  ]
29
+ =end
30
+
31
+ movies_and_showtimes = Fandango.theater_showtimes(theaters_and_movies.first.fetch(:showtimes_link))
32
+ =begin
33
+ [
34
+ {
35
+ :title => "Bad Moms",
36
+ :id => "191125",
37
+ :runtime => 101,
38
+ :showtimes => [
39
+ [0] {
40
+ :datetime => #<DateTime: 2016-08-02T11:30:00-05:00 ((2457603j,59400s,0n),-18000s,2299161j)>
41
+ },
42
+ # ...
43
+ ]
44
+ },
45
+ # ...
46
+ =end
47
+ ```
48
+
49
+ ### `theaters_and_movies` with id and different date
50
+
51
+ ```ruby
52
+ Fandango.theater_showtimes(
53
+ :theater_id => theaters_and_movies.first.fetch(:id),
54
+ :date => Date.tomorrow, # optional, defaults to `Date.today`
55
+ )
35
56
  ```
36
57
 
37
- ## Todo
58
+ ## Compatibility
38
59
 
39
- * Remove Feezirra. For what we're doing, it's not worth the dependency. Just make request and parse ourselves.
60
+ Development of this gem will only officially support Ruby versions 2.3.0. (see `.travis.yml`)
61
+ It used to support 1.9.2. and 1.9.3, and it still may work, but it's not tested as of now.
62
+ Contributions are very welcome, and I will do what I can to help.
data/Rakefile CHANGED
@@ -4,6 +4,6 @@ require 'rake/testtask'
4
4
  task :default => :test
5
5
 
6
6
  Rake::TestTask.new(:test) do |t|
7
- t.libs << 'spec'
8
- t.pattern = 'spec/*.spec.rb'
7
+ t.pattern = 'spec/**/*.spec.rb'
8
+ t.warning = false
9
9
  end
data/fandango.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_runtime_dependency 'nokogiri', '~> 1.0'
22
22
 
23
23
  s.add_development_dependency 'awesome_print'
24
- s.add_development_dependency 'fakeweb', '1.3.0'
25
- s.add_development_dependency 'minitest', '3.0.1'
26
- s.add_development_dependency 'vcr', '2.1.1'
24
+ s.add_development_dependency 'webmock'
25
+ s.add_development_dependency 'minitest'
26
+ s.add_development_dependency 'vcr'
27
27
  end
data/lib/fandango.rb CHANGED
@@ -3,47 +3,25 @@ require "fandango/version"
3
3
  require 'open-uri'
4
4
  require 'nokogiri'
5
5
 
6
- require 'fandango/parser'
6
+ require 'fandango/api'
7
7
 
8
- module Fandango
9
-
10
- class << self
11
-
12
- def movies_near(postal_code)
13
- raise ArgumentError, "postal code cannot be blank" if postal_code.nil? || postal_code == ''
14
- response = request(postal_code)
15
- raise BadResponse.new(response) unless response.status.first == '200'
16
- source = response.read
17
- parse source
18
- end
19
-
20
- def request(postal_code)
21
- open(url_for_postal_code(postal_code))
22
- end
8
+ require 'fandango/theater'
9
+ require 'fandango/movie'
10
+ require 'fandango/showtime'
23
11
 
24
- # Given RSS source string, parse using Nokogiri.
25
- # Return hash of theaters and movies playing at each..
26
- def parse(source)
27
- Parser.parse(source)
28
- end
29
-
30
- private
31
-
32
- def url_for_postal_code(postal_code)
33
- cleaned_postal_code = clean_postal_code(postal_code)
34
- "http://www.fandango.com/rss/moviesnearme_#{cleaned_postal_code}.rss"
35
- end
12
+ module Fandango
36
13
 
37
- # Remove spaces.
38
- def clean_postal_code(postal_code)
39
- postal_code.to_s.gsub(' ', '')
40
- end
14
+ module_function
41
15
 
16
+ def movies_near(postal_code)
17
+ MoviesNear.(postal_code)
42
18
  end
43
19
 
44
- class BadResponse < StandardError
45
- def initialize(response)
46
- super "Bad response:\n#{response.inspect}"
20
+ def theater_showtimes(showtimes_link_or_options)
21
+ if showtimes_link_or_options.is_a?(Hash)
22
+ TheaterShowtimes.by_id_and_date(showtimes_link_or_options)
23
+ else
24
+ TheaterShowtimes.(showtimes_link_or_options)
47
25
  end
48
26
  end
49
27
 
@@ -0,0 +1,14 @@
1
+ module Fandango
2
+ autoload :MoviesNear, 'fandango/api/movies_near'
3
+ autoload :TheaterShowtimes, 'fandango/api/theater_showtimes'
4
+
5
+ module API
6
+
7
+ class BadResponse < StandardError
8
+ def initialize(response)
9
+ super "Bad response:\n#{response.inspect}"
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ module Fandango
2
+ module MoviesNear
3
+
4
+ module_function
5
+
6
+ def call(postal_code)
7
+ response = request(postal_code)
8
+ raise BadResponse.new(response) unless response.status.first == '200'
9
+
10
+ xml = response.read
11
+ Parser.(xml)
12
+ end
13
+
14
+ def request(postal_code)
15
+ cleaned_postal_code = postal_code.to_s.gsub(' ', '')
16
+ url_for_postal_code = "http://www.fandango.com/rss/moviesnearme_#{cleaned_postal_code}.rss"
17
+ open(url_for_postal_code)
18
+ end
19
+
20
+ module Parser
21
+
22
+ module_function
23
+
24
+ def call(xml)
25
+ doc = Nokogiri.XML(xml)
26
+ doc.css('item').map do |item_node|
27
+ Theater.parse(item_node)
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ class BadResponse < API::BadResponse; end
34
+
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ module Fandango
2
+ module TheaterShowtimes
3
+
4
+ BASE_URL = 'http://www.fandango.com/theater_%{theater_id}/theaterpage?date=%{date}'
5
+
6
+ module_function
7
+
8
+ def call(showtimes_link)
9
+ response = request(showtimes_link)
10
+ raise BadResponse.new(response) unless response.status.first == '200'
11
+
12
+ html = response.read
13
+ Parser.(html)
14
+ end
15
+
16
+ def by_id_and_date(theater_id:, date: Date.today)
17
+ url = BASE_URL % {:theater_id => theater_id, :date => date}
18
+ call url
19
+ end
20
+
21
+ def request(showtimes_link)
22
+ open(showtimes_link)
23
+ end
24
+
25
+ module Parser
26
+
27
+ module_function
28
+
29
+ def call(html)
30
+ doc = Nokogiri.HTML(html)
31
+ doc.css('.showtimes-movie-container').map do |movie_node|
32
+ movie = Movie.parse(movie_node)
33
+ movie[:showtimes] =
34
+ movie_node.at_css('.showtimes-times').css('a').map do |showtime_node|
35
+ Showtime.parse(showtime_node)
36
+ end
37
+ movie
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ class BadResponse < API::BadResponse; end
46
+
47
+ end
@@ -1,29 +1,45 @@
1
1
  module Fandango
2
2
  module Movie
3
3
 
4
- class << self
5
-
6
- # Return array of movie attributes.
7
- def parse(description_node)
8
- description_node.css('li').map do |li|
9
- {
10
- title: parse_title(li),
11
- id: parse_id(li),
12
- }
13
- end
14
- end
4
+ module_function
15
5
 
16
- private
6
+ def parse(node)
7
+ a_tag = node.at_css('a.showtimes-movie-title') || node.at_css('a')
8
+ hash = {
9
+ title: parse_title(a_tag),
10
+ id: parse_id(a_tag),
11
+ }
17
12
 
18
- def parse_title(li)
19
- li.at_css('a').content
13
+ if a_tag.attr(:class)&.include?('showtimes-movie-title')
14
+ hash[:runtime] = parse_runtime(node)
20
15
  end
21
16
 
22
- # E.g. '141081' in fandango.com/the+adventures+of+tintin+3d_141081/movietimes
23
- def parse_id(li)
24
- li.at_css('a')['href'].match(%r{fandango\.com/.*_(?<id>\d+)/movietimes})[:id]
25
- end
17
+ hash
18
+ end
19
+
20
+ def parse_title(a_tag)
21
+ a_tag.content
22
+ end
23
+
24
+ # E.g. '141081' in fandango.com/the+adventures+of+tintin+3d_141081/movietimes
25
+ def parse_id(a_tag)
26
+ a_tag['href'].match(%r{fandango\.com/.*_(?<id>\d+)/})[:id]
27
+ end
26
28
 
29
+ # <div class="showtimes-movie-rating-runtime">
30
+ # <!-- Display rating -->
31
+ # R ,
32
+ # <!-- Display runtime -->
33
+ # 1 hr 41 min
34
+ # </div>
35
+ def parse_runtime(node)
36
+ rating_runtime = node.at_css('.showtimes-movie-rating-runtime')
37
+ %r{(?<hour>\d+)\s+hr\s+(?<min>\d+)} =~ rating_runtime.content
38
+ begin
39
+ Integer(hour) * 60 + Integer(min)
40
+ rescue TypeError
41
+ #puts rating_runtime
42
+ end
27
43
  end
28
44
 
29
45
  end
@@ -0,0 +1,18 @@
1
+ module Fandango
2
+ module Showtime
3
+
4
+ module_function
5
+
6
+ def parse(node)
7
+ {
8
+ datetime: parse_datetime(node),
9
+ }
10
+ end
11
+
12
+ def parse_datetime(node)
13
+ datetime_str = node.at_css('time').attr('datetime')
14
+ DateTime.parse(datetime_str)
15
+ end
16
+
17
+ end
18
+ end
@@ -1,49 +1,58 @@
1
1
  module Fandango
2
2
  module Theater
3
3
 
4
- class << self
4
+ module_function
5
5
 
6
- def initialize(entry)
7
- @entry = entry
8
- end
6
+ def parse(item_node)
7
+ hash = {}
9
8
 
10
- def parse(item_node, description_node = nil)
11
- description_node ||= Fandango::Parser.parse_description(item_node)
12
- name = parse_name(item_node)
13
- id = parse_id(item_node)
14
- address = parse_address(description_node)
15
- postal_code = parse_postal_code(address)
16
-
17
- {
18
- name: name,
19
- id: id,
20
- address: address,
21
- postal_code: postal_code,
22
- }
23
- end
9
+ description_node = parse_description_node(item_node)
24
10
 
25
- private
11
+ hash[:name] = parse_name(item_node)
12
+ hash[:id] = parse_id(item_node)
13
+ hash[:address] = parse_address(description_node)
14
+ hash[:postal_code] = parse_postal_code(hash[:address])
15
+ hash[:showtimes_link] = parse_showtimes_link(item_node)
16
+ hash[:movies] = parse_movies(description_node)
26
17
 
27
- def parse_name(item_node)
28
- item_node.at_css('title').content
29
- end
18
+ hash
19
+ end
30
20
 
31
- # E.g. 'aaicu' in http://www.fandango.com/northpark7_aaicu/theaterpage
32
- def parse_id(item_node)
33
- item_node.
34
- at_css('link').
35
- content.
36
- match(%r{fandango\.com/.*_(?<id>.*)/theaterpage})[:id]
37
- end
21
+ # Description content is in the form of HTML wrapped in CDATA.
22
+ # Parse it and return a parsed Nokogiri node.
23
+ def parse_description_node(item_node)
24
+ cdata = item_node.at_css('description')
25
+ Nokogiri::HTML(cdata.content)
26
+ end
38
27
 
39
- def parse_address(description_node)
40
- description_node.at_css('p').content
41
- end
28
+ def parse_name(item_node)
29
+ item_node.at_css('title').content.strip
30
+ end
42
31
 
43
- def parse_postal_code(address)
44
- address.match(/(?<postal_code>\d+)$/)[:postal_code]
45
- end
32
+ # E.g. 'aaicu' in http://www.fandango.com/northpark7_aaicu/theaterpage
33
+ def parse_id(item_node)
34
+ item_node.
35
+ at_css('link').
36
+ content.
37
+ match(%r{fandango\.com/.*_(?<id>.*)/theaterpage})[:id]
38
+ end
39
+
40
+ def parse_address(description_node)
41
+ description_node.at_css('p').content
42
+ end
43
+
44
+ def parse_postal_code(address)
45
+ address.match(/(?<postal_code>\d+)$/)[:postal_code]
46
+ end
47
+
48
+ def parse_showtimes_link(item_node)
49
+ item_node.at_css('link').content.strip
50
+ end
46
51
 
52
+ def parse_movies(description_node)
53
+ description_node.css('li').map do |li|
54
+ Movie.parse(li)
55
+ end
47
56
  end
48
57
 
49
58
  end