gares 1.1.1 → 2.0.0.pre.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37c2b4d26cf3ffa144fb673445cca829225fab7b
4
- data.tar.gz: dbb3e6740437f1dab018ee9da896123192e84ef4
3
+ metadata.gz: f87e7dc8b8d185548c8dcc4c12c789e07802caea
4
+ data.tar.gz: 971aa5a58e033f79d41f901899bc6d862c56f5f0
5
5
  SHA512:
6
- metadata.gz: ffffb7fe4abf9d9dd92cb16d5e416b67b4d950004dfebf6ed1e457b431f85d3ee4786dfef5acaca6784770b186f1ba08f74dbd06a17f4ef8e0b75c98704bcdd9
7
- data.tar.gz: 914ebb3686e8539e739e4ae009825104df72ddaccb28f657abe6a128461ae3b18db5602b75f6856603ce8b8c844aacafd540fb3df0eb194b02659d49c14986d2
6
+ metadata.gz: 4432ec3fa4011d82f5b22f63aa647e7b75fca52fa2222335032f9cbb0b5078bcb59008d9d9bfd4fe9474143248d212beac197c2c5758b871519968bed5a8fd38
7
+ data.tar.gz: 9afd2bd055b35a12e10b15b10d9a29c4208744c0ae1a119c39403f4b8ccfb9ad06c66e1f6d92c8b6bdd09113e6d9c9a068ee9b41412cbad0586127a4ced2f589
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  group 'gem' do
2
2
  guard 'rspec', :cmd => "bundle exec rspec", :all_on_start => false, :all_after_pass => false, :failed_mode => :focus do
3
3
  watch(%r{^spec/.+_spec\.rb$})
4
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/gares/#{m[1]}_spec.rb" }
4
+ watch(%r{^lib/gares/(.+)\.rb$}) { |m| "spec/gares/#{m[1]}_spec.rb" }
5
5
  end
6
6
  end
data/README.md CHANGED
@@ -22,7 +22,7 @@ Gares currently features the following:
22
22
  g = Gares::Station.new("frlpd")
23
23
 
24
24
  g.name
25
- #=> "Lyon Part Dieu"
25
+ #=> "Lyon Part-Dieu"
26
26
 
27
27
  g.wifi?
28
28
  #=> true
@@ -33,7 +33,7 @@ Gares currently features the following:
33
33
  g.services.first
34
34
  #=> "Services à la clientèle"
35
35
 
36
- [g.lat, g.long]
36
+ [g.latitude, g.longitude]
37
37
  #=> [45.760281, 4.859801]
38
38
 
39
39
  See the [`Gares::Base` class documentation](http://www.rubydoc.info/github/paulrbr/gares/master/Gares/Base) for all available data on a station.
@@ -43,7 +43,7 @@ See the [`Gares::Base` class documentation](http://www.rubydoc.info/github/paulr
43
43
  g = Gares::Search.new("Aix")
44
44
 
45
45
  g.stations.size
46
- #=> 7
46
+ #=> 28
47
47
 
48
48
  # or
49
49
 
@@ -51,14 +51,14 @@ See the [`Gares::Base` class documentation](http://www.rubydoc.info/github/paulr
51
51
  station = stations.last
52
52
 
53
53
  station.name
54
- #=> "Paris Gare de Lyon"
54
+ #=> "Aéroport-Lyon-Saint-Exupéry"
55
55
 
56
56
  ### Train information:
57
57
 
58
58
  train = Gares::Train.new(11641, Time.now)
59
59
 
60
60
  train.departure.station
61
- #=> #<Gares::Station:0x000f0000000000 @slug="frpst", @name="Paris Est">
61
+ #=> #<Gares::Station:0x000f0000000000 @name="Paris-Gare-de-l’Est", ...>
62
62
 
63
63
  train.departure.departure_date
64
64
  #=> 2015-04-25 06:42:00 +0200
@@ -70,7 +70,7 @@ See the [`Gares::Base` class documentation](http://www.rubydoc.info/github/paulr
70
70
  #=> false
71
71
 
72
72
  train.arrival.station.name
73
- #=> "Culmont - Chalindrey"
73
+ #=> "Culmont-Chalindrey"
74
74
 
75
75
  train.arrival.platform
76
76
  #=> "B"
@@ -132,3 +132,5 @@ _This gem is not endorsed or affiliated with gares-en-mouvement.com, nor SNCF, I
132
132
  ## License
133
133
 
134
134
  See [MIT-LICENSE](https://github.com/paulrbr/gares/blob/master/MIT-LICENSE)
135
+
136
+ Station database [ODbL LICENSE](https://raw.githubusercontent.com/capitainetrain/stations/master/LICENCE.txt)
data/gares.gemspec CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.add_dependency 'nokogiri', '~> 1.6'
23
23
  s.add_dependency 'hashie', '~> 3.4'
24
+ s.add_dependency 'smarter_csv', '~> 1.0'
24
25
  s.add_dependency 'unidecoder', '~> 1.1'
25
26
  s.add_dependency 'httparty', '~> 0.13'
26
27
 
data/lib/gares/base.rb CHANGED
@@ -1,92 +1,118 @@
1
1
  module Gares
2
2
  # Represents something on gare-en-mouvement.com
3
- class Base
4
- attr_accessor :slug, :name
5
-
6
- GPS_COORD = 'Coordonnées GPS : '
7
- NAME = 'En direct de '
8
-
9
- # Initialize a new Station object with it's gare-en-mouvemnt id (as a String)
10
- #
11
- # station = Gares::Station.new("frabt")
12
- #
13
- # Gares::Station objects are lazy loaded, meaning that no HTTP request
14
- # will be performed when a new object is created. An HTTP request is made (once)
15
- # Only when you use an accessor that needs the remote data.
16
- #
17
- def initialize(slug, name = nil)
18
- @slug = slug
19
- @name = name if name
20
- end
21
-
22
- def lat
23
- coordinates.first.to_f
24
- end
25
-
26
- def long
27
- coordinates.last.to_f
28
- end
29
-
3
+ class Base < Hashie::Dash
4
+ property :id
5
+ property :name
6
+ property :slug
7
+ property :sncf_id
8
+ property :longitude
9
+ property :latitude
10
+ property :uic
11
+ property :uic8_sncf
12
+ property :parent_station_id
13
+ property :is_city
14
+ property :country
15
+ property :is_main_station
16
+ property :time_zone
17
+ property :is_suggestable
18
+ property :sncf_is_enabled
19
+ property :idtgv_id
20
+ property :idtgv_is_enabled
21
+ property :db_id
22
+ property :db_is_enabled
23
+ property :idbus_id
24
+ property :idbus_is_enabled
25
+ property :ouigo_id
26
+ property :ouigo_is_enabled
27
+ property :trenitalia_id
28
+ property :trenitalia_is_enabled
29
+ property :ntv_id
30
+ property :ntv_is_enabled
31
+ property :'info:fr'
32
+ property :'info:en'
33
+ property :'info:de'
34
+ property :'info:it'
35
+ property :same_as
36
+
37
+ # @deprecated
30
38
  def services
31
- @services ||= Services.new(@slug)
39
+ warn "[DEPRECATION] since gares-en-mouvement.com does not exist anymore."
40
+ @services ||= Services.new(sncf_id: sncf_id)
32
41
  @services.all
33
42
  end
34
43
 
44
+ # @deprecated
35
45
  def sales
36
- @sales ||= Sales.new(@slug)
46
+ warn "[DEPRECATION] since gares-en-mouvement.com does not exist anymore."
47
+ @sales ||= Sales.new(sncf_id: sncf_id)
37
48
  @sales.all
38
49
  end
39
50
 
51
+ # @deprecated
40
52
  def horaires
53
+ warn "[DEPRECATION] since gares-en-mouvement.com does not exist anymore."
41
54
  document.search('ul.ouverture_heure li').
42
55
  map { |horaire| horaire.inner_html } rescue []
43
56
  end
44
57
  alias opening_hours horaires
45
58
 
46
59
  # Whether the gare has a defibrillator or not
60
+ # @deprecated
47
61
  def defibrillateur?
62
+ warn "[DEPRECATION] since gares-en-mouvement.com does not exist anymore."
48
63
  !document.at('div.defibrillateur').nil?
49
64
  end
50
65
  alias defibrillator? defibrillateur?
51
66
 
52
67
  # Whether the gare is equipped with wifi or not
68
+ # @deprecated
53
69
  def wifi?
70
+ warn "[DEPRECATION] since gares-en-mouvement.com does not exist anymore."
54
71
  !document.at('div.wifi').nil?
55
72
  end
56
73
 
74
+ # @deprecated
57
75
  def has_borne?
76
+ warn "[DEPRECATION] since gares-en-mouvement.com does not exist anymore."
58
77
  sales.any? { |sales_service| sales_service =~ /bornes?.libre.service/i }
59
78
  end
60
79
 
61
- # Returns a string containing the name
62
- def name(force_refresh = false)
63
- if @name && !force_refresh
64
- @name
65
- else
66
- @name = document.at('h1').inner_html.gsub(NAME, '') rescue nil
67
- end
80
+ # deprecated
81
+ def slug
82
+ sncf_id.downcase
68
83
  end
69
84
 
70
- private
85
+ # deprecated
86
+ def lat
87
+ latitude
88
+ end
71
89
 
72
- def coordinates
73
- @coordinates ||= document.xpath("//p/strong[contains(text(), '#{GPS_COORD}')]").first.parent.text
74
- .gsub(GPS_COORD, '').split(',')
90
+ # deprecated
91
+ def long
92
+ longitude
75
93
  end
76
94
 
95
+ private
96
+
77
97
  # Returns a new Nokogiri document for parsing.
78
98
  def document
79
- @document ||= Nokogiri::HTML(Gares::Station.find_by_slug(@slug))
99
+ @document ||= Nokogiri::HTML(self.class.external_data(sncf_id))
80
100
  end
81
101
 
82
102
  # Use HTTParty to fetch the raw HTML for this gare.
83
- def self.find_by_slug(slug, page = :"votre-gare")
84
- open("http://www.gares-en-mouvement.com/fr/#{slug}/#{page}")
103
+ # @deprecated
104
+ def self.external_data(sncf_id, page = :"votre-gare")
105
+ open("http://www.gares-en-mouvement.com/fr/#{sncf_id.downcase}/#{page}")
85
106
  end
86
107
 
87
- # Convenience method for search
108
+ # Convenience method for search (by name)
88
109
  def self.search(query)
89
110
  Gares::Search.new(query).stations
90
111
  end
112
+
113
+ # Convenience method for search_by_sncf_id
114
+ def self.search_by_sncf_id(query)
115
+ Gares::Search.new(query, :sncf_id).stations
116
+ end
91
117
  end # Base
92
118
  end # Gares
@@ -0,0 +1,14 @@
1
+ module Gares
2
+ # All errors from this gem will inherit from this one.
3
+ class Error < StandardError
4
+ end
5
+ class UnsupportedIndex < StandardError
6
+ end
7
+
8
+ # Raised when requesting a train that does not exist.
9
+ class TrainNotFound < Error
10
+ end
11
+ # Raised when requesting a station that does not exist.
12
+ class StationNotFound < Error
13
+ end
14
+ end
data/lib/gares/sales.rb CHANGED
@@ -9,8 +9,7 @@ module Gares
9
9
  private
10
10
 
11
11
  def document
12
- @document ||= Nokogiri::HTML(Gares::Station.find_by_slug(
13
- @slug, :"services-en-gare/vente/"))
12
+ @document ||= Nokogiri::HTML(self.class.external_data(sncf_id, :"services-en-gare/vente/"))
14
13
  end
15
14
  end
16
15
  end
data/lib/gares/search.rb CHANGED
@@ -3,11 +3,11 @@ module Gares
3
3
  class Search < StationList
4
4
  attr_reader :query
5
5
 
6
- # This is a file containing minimal information (name and slug) of all stations of gares-en-mouvement.com
7
- GARES_LIST_URL = "https://www.kimonolabs.com/api/7jys32dy?apikey=lsOO4tNm78cH9JxqWg9gAk9l4nYaou9j&kimmodify=1"
6
+ # This is the stations database from capitainetrain.com
7
+ GARES_LIST_URL = "https://raw.githubusercontent.com/capitainetrain/stations/master/stations.csv"
8
8
 
9
9
  # List of keywords to ignore while searching
10
- IGNORE_KEYWORDS = ["ST"]
10
+ IGNORE_KEYWORDS = ["ST", "SAINT", "GARE", "SNCF"]
11
11
  # Initialize a new Station search with the specified query
12
12
  #
13
13
  # search = Gares::Search.new("Aix")
@@ -15,8 +15,12 @@ module Gares
15
15
  # Gares::Search is lazy loaded, meaning that unless you access the +stations+
16
16
  # attribute, no remomte query is made.
17
17
  #
18
- def initialize(query)
18
+ # Search can by done via the :name or :sncf_id field given in parameter.
19
+ # Defaults to the :name field.
20
+ def initialize(query, field = :name)
21
+ fail UnsupportedIndex unless %w(name sncf_id).include?(field.to_s)
19
22
  @query = query
23
+ @by = field
20
24
  end
21
25
 
22
26
  # Returns an array of Gares::Station objects in order to easily search result yielded.
@@ -27,24 +31,42 @@ module Gares
27
31
 
28
32
  private
29
33
 
30
- def document
31
- @document ||= Hashie::Mash.new(JSON.load(Gares::Search.query))
34
+ def result
35
+ @raw_result ||= case @by
36
+ when :name
37
+ keywords = @query.to_ascii.split(/[ -]/).select { |keyword| !IGNORE_KEYWORDS.include?(keyword.upcase) }
38
+ regexp_query = keywords.join(".*")
39
+ self.class.data(@by).select do |index, v|
40
+ index && index =~ /#{regexp_query}/i
41
+ end
42
+ when :sncf_id
43
+ { @query.downcase => self.class.data(@by)[@query.downcase] }
44
+ end
45
+
46
+ @result ||= @raw_result.map { |_, raw_station| Gares::Station.new(raw_station) }
32
47
  end
33
48
 
34
- def result
35
- keywords = @query.split(" ").select { |keyword| !IGNORE_KEYWORDS.include?(keyword) }
36
- @result ||= document.results.collection1.map(&:station).select do |station|
37
- station.name.to_ascii =~ /#{keywords.join(".*")}/i
49
+ # Read stations.csv file into memory
50
+ # @param index either :name or :sncf_id
51
+ # @return [Hash<String, Hash>] list of stations indexed in a Hash
52
+ def self.data(index)
53
+ @@raw_data ||= SmarterCSV.process(open(GARES_LIST_URL), col_sep: ";")
54
+ case index
55
+ when :name
56
+ @@data_by_name ||= index_data(@@raw_data, index)
57
+ when :sncf_id
58
+ @@data_by_sncf_id ||= index_data(@@raw_data, index)
38
59
  end
39
60
  end
40
61
 
41
- def self.query
42
- open(GARES_LIST_URL)
62
+ def self.index_data(data, by)
63
+ data.map do |raw_station|
64
+ [raw_station[by].to_ascii.downcase, raw_station] if raw_station[by] && raw_station[:uic]
65
+ end.compact.to_h
43
66
  end
44
67
 
45
68
  def parse_station
46
- station = result.first
47
- [Gares::Station.new(station.slug, station.name)]
69
+ [result.first]
48
70
  end
49
71
 
50
72
  def exact_match?
@@ -1,4 +1,5 @@
1
1
  module Gares
2
+ # @deprecated
2
3
  class Services < Base
3
4
 
4
5
  def all
@@ -9,8 +10,7 @@ module Gares
9
10
  private
10
11
 
11
12
  def document
12
- @document ||= Nokogiri::HTML(Gares::Station.find_by_slug(
13
- @slug, :"services-en-gare/service/"))
13
+ @document ||= Nokogiri::HTML(self.class.external_data(sncf_id, :"services-en-gare/service/"))
14
14
  end
15
15
  end
16
16
  end
@@ -4,9 +4,7 @@ module Gares
4
4
  private
5
5
 
6
6
  def parse_stations
7
- result.compact.uniq.map do |station|
8
- Gares::Station.new(station.slug, station.name)
9
- end
7
+ result.compact.uniq
10
8
  end
11
9
  end # StationList
12
10
  end # Gares
data/lib/gares/train.rb CHANGED
@@ -42,19 +42,44 @@ module Gares
42
42
 
43
43
  private
44
44
 
45
+ def itinerary_available?
46
+ document.css('ul.itinerary-details').size > 0
47
+ end
48
+
45
49
  # Returns a new Nokogiri document for parsing.
46
50
  def document
47
- @document ||= Nokogiri::HTML(self.class.request_sncf(@number, @date))
51
+ if !@document
52
+ @document = Nokogiri::HTML(self.class.request_sncf(@number, @date))
53
+ if !itinerary_available?
54
+ @document = Nokogiri::HTML(self.class.request_sncf_itinerary(0))
55
+ end
56
+ end
57
+ if @document.at('#no_results')
58
+ fail TrainNotFound, @document.at('#no_results b').inner_html
59
+ end
60
+ @document
48
61
  end
49
62
 
50
63
  def self.request_sncf(number, date)
51
64
  uri = URI.parse("http://www.sncf.com/sncf/train")
52
65
  response = Net::HTTP.post_form(uri, {"numeroTrain" => number, "date" => date.strftime("%d/%m/%Y")})
53
- cookies = response.get_fields('Set-Cookie').map { |cookie| cookie.split(";").first }.join(";")
66
+ @cookies = response.get_fields('Set-Cookie').map { |cookie| cookie.split(";").first }.join(";")
54
67
 
55
68
  uri = URI.parse("http://www.sncf.com/en/horaires-info-trafic/train/resultats")
56
69
  req = Net::HTTP::Get.new(uri.path)
57
- req.add_field("Cookie", cookies)
70
+ req.add_field("Cookie", @cookies)
71
+ Net::HTTP.new(uri.host, uri.port).start { |http| http.request(req) }.body
72
+ end
73
+
74
+ def self.request_sncf_itinerary(index)
75
+ uri = URI.parse("http://www.sncf.com/sncf/train/displayDetailTrain?idItineraire=#{index}")
76
+ req = Net::HTTP::Get.new(uri)
77
+ req.add_field("Cookie", @cookies) if @cookies
78
+ Net::HTTP.new(uri.host, uri.port).start { |http| http.request(req) }.body
79
+
80
+ uri = URI.parse("http://www.sncf.com/en/horaires-info-trafic/train/resultats?#{index}")
81
+ req = Net::HTTP::Get.new(uri)
82
+ req.add_field("Cookie", @cookies) if @cookies
58
83
  Net::HTTP.new(uri.host, uri.port).start { |http| http.request(req) }.body
59
84
  end
60
85
 
@@ -74,9 +74,22 @@ module Gares
74
74
  raw_name = node.at('td.stations div.station').inner_html.strip
75
75
  stations = Station.search(raw_name)
76
76
  @station = if stations.size > 1
77
+ raw_name.gsub!(/[ -]/, '.*')
78
+ exact_match = /^#{raw_name}$/i
79
+ begin_match = /^#{raw_name}/i
80
+ end_match = /#{raw_name}$/
81
+ middle_match = /#{raw_name}/i
77
82
  stations.find do |station|
78
- name = /(^#{raw_name}$|^#{raw_name} | #{raw_name}$| #{raw_name} )/i
79
- station.name.match(name)
83
+ station.name.to_ascii.match(exact_match)
84
+ end ||
85
+ stations.find do |station|
86
+ station.name.to_ascii.match(begin_match)
87
+ end ||
88
+ stations.find do |station|
89
+ station.name.to_ascii.match(end_match)
90
+ end ||
91
+ stations.find do |station|
92
+ station.name.to_ascii.match(middle_match)
80
93
  end
81
94
  else
82
95
  stations.first
data/lib/gares/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Gares
2
- VERSION = '1.1.1'
2
+ VERSION = '2.0.0-pre'
3
3
  end
data/lib/gares.rb CHANGED
@@ -6,9 +6,11 @@ require 'rubygems'
6
6
  require 'nokogiri'
7
7
  require 'json'
8
8
  require 'hashie'
9
+ require 'smarter_csv'
9
10
  require 'unidecoder'
10
11
  require 'httparty'
11
12
 
13
+ require 'gares/errors'
12
14
  require 'gares/base'
13
15
  require 'gares/station'
14
16
  require 'gares/station_list'