gares 1.1.1 → 2.0.0.pre.pre

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