fandango 1.0.0 → 2.0.0

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