ns-api 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +40 -0
  4. data/README.md +82 -0
  5. data/Rakefile +1 -0
  6. data/lib/ns.rb +29 -0
  7. data/lib/ns/api/request/base.rb +58 -0
  8. data/lib/ns/api/request/disruption_collection.rb +30 -0
  9. data/lib/ns/api/request/travel_advice.rb +34 -0
  10. data/lib/ns/api/response/disruption_collection.rb +58 -0
  11. data/lib/ns/api/response/parser.rb +22 -0
  12. data/lib/ns/api/response/travel_advice.rb +46 -0
  13. data/lib/ns/configuration.rb +14 -0
  14. data/lib/ns/disruption.rb +26 -0
  15. data/lib/ns/disruption_collection.rb +45 -0
  16. data/lib/ns/model.rb +13 -0
  17. data/lib/ns/station.rb +8 -0
  18. data/lib/ns/travel_option.rb +28 -0
  19. data/lib/ns/trip.rb +80 -0
  20. data/lib/version.rb +3 -0
  21. data/ns.gemspec +24 -0
  22. data/spec/fixtures/ns_disruptions_no_results.xml +10 -0
  23. data/spec/fixtures/ns_disruptions_with_results.xml +23 -0
  24. data/spec/fixtures/ns_travel_advice_response.xml +2444 -0
  25. data/spec/ns/api/request/base_spec.rb +67 -0
  26. data/spec/ns/api/request/disruption_collection_spec.rb +34 -0
  27. data/spec/ns/api/request/travel_advice_spec.rb +53 -0
  28. data/spec/ns/api/response/disruption_collection_spec.rb +43 -0
  29. data/spec/ns/api/response/parser_spec.rb +17 -0
  30. data/spec/ns/api/response/travel_advice_spec.rb +61 -0
  31. data/spec/ns/disruption_collection_spec.rb +32 -0
  32. data/spec/ns/disruption_spec.rb +33 -0
  33. data/spec/ns/station_spec.rb +11 -0
  34. data/spec/ns/travel_option_spec.rb +41 -0
  35. data/spec/ns/trip_spec.rb +85 -0
  36. data/spec/ns_spec.rb +17 -0
  37. data/spec/spec_helper.rb +19 -0
  38. metadata +147 -0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'simplecov'
8
+ gem 'fakeweb'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ns (0.0.2)
5
+ httpi
6
+ nokogiri
7
+ nori
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ diff-lcs (1.1.3)
13
+ fakeweb (1.3.0)
14
+ httpi (2.0.2)
15
+ rack
16
+ multi_json (1.6.0)
17
+ nokogiri (1.5.6)
18
+ nori (2.0.3)
19
+ rack (1.5.2)
20
+ rspec (2.12.0)
21
+ rspec-core (~> 2.12.0)
22
+ rspec-expectations (~> 2.12.0)
23
+ rspec-mocks (~> 2.12.0)
24
+ rspec-core (2.12.2)
25
+ rspec-expectations (2.12.1)
26
+ diff-lcs (~> 1.1.3)
27
+ rspec-mocks (2.12.2)
28
+ simplecov (0.7.1)
29
+ multi_json (~> 1.0)
30
+ simplecov-html (~> 0.7.1)
31
+ simplecov-html (0.7.1)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ fakeweb
38
+ ns!
39
+ rspec
40
+ simplecov
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ Ruby implementation of the NS API
2
+ =================================
3
+
4
+ Ruby implementation of the NS (Dutch Railways) API
5
+
6
+ ## Request an API username and password from NS
7
+
8
+ Before using this gem you should request a username and password from the
9
+ NS API pages. This allows you to authenticate your API requests and will
10
+ make sure you get actual data back.
11
+
12
+ Head over to http://www.ns.nl/api/api to request your credentials.
13
+
14
+ ## Configuring the gem
15
+
16
+ To configure the gem, place an <tt>Ns.configure</tt> block in your code:
17
+
18
+ ```
19
+ Ns.configure do |config|
20
+ config.username = 'john@doe.com'
21
+ config.password = 'your-secret-password'
22
+ end
23
+ ```
24
+
25
+ ## Using the gem
26
+
27
+ Currently the gem implements the following API calls:
28
+
29
+ - Requesting a travel advice from one station to another
30
+
31
+ Each of these calls is explained below.
32
+
33
+ ### Requesting a travel advice
34
+
35
+ To request a travel advice you need at least the names of the departure and
36
+ the arrival station. You may use the full string name of the station, e.g.
37
+ "Utrecht Centraal" or the abbreviated station code, e.g. "ut".
38
+
39
+ Requesting travel advice is done by creating a new <tt>Ns::Trip</tt>. A
40
+ trip has a <tt>travel_options</tt> method that returns a collection of
41
+ <tt>Ns::TravelOption</tt> objects. See the code for the methods these objects
42
+ expose.
43
+
44
+ ```
45
+ trip = Ns::Trip.new(from: 'Amsterdam Centraal', to: 'Ede Centrum')
46
+ trip.travel_options # <= returns Ns::TravelOption objects
47
+ ```
48
+
49
+ Optionally you may supply a desired departure or arrival time to the trip:
50
+
51
+ ```
52
+ trip = Ns::Trip.new(from: 'Amsterdam Centraal', to: 'Ede Centrum', arrival: Time.now)
53
+ trip_2 = Ns::Trip.new(from: 'Amsterdam Centraal', to: 'Ede Centrum', departure: Time.now)
54
+ ```
55
+
56
+ You may also specify a <tt>via</tt> station:
57
+
58
+ ```
59
+ trip = Ns::Trip.new(from: 'Amsterdam Centraal', to: 'Ede Centrum', via: 'Utrecht Centraal', arrival: Time.now)
60
+ ```
61
+
62
+ The returned <tt>Ns::TravelOption</tt> objects have a <tt>optimal</tt> attribute that tells you wether or not the
63
+ travel option is regarded as the optimal option for your trip by the NS.
64
+
65
+ **Note**: the times returned by an instance of <tt>Ns::TravelOption</tt> (e.g.
66
+ trip durations, delays) are all in seconds.
67
+
68
+ ### Requesting a list of interruptions
69
+
70
+ Requesting disruptions requires a station. Disruptions that affect the given station will be returned:
71
+
72
+ ```
73
+ disruption_collection = Ns::DisruptionCollection.new(station: 'Amsterdam Centraal')
74
+ ```
75
+
76
+ An instance of <tt>Ns::DisruptionCollection</tt> has a <tt>planned_disruptions</tt> method and a <tt>unplanned_disruptions</tt>
77
+ method. The results of these methods are <tt>Ns::Disruption</tt> objects.
78
+
79
+ ## Development
80
+
81
+ Pull requests are welcome! To add your feature: create a fork, implement the
82
+ feature on a topic branch, add specs and create a pull request here on Github.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/ns.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'time'
2
+ require 'httpi'
3
+ require 'nori'
4
+
5
+ this = Pathname.new(__FILE__).realpath
6
+ lib_path = File.expand_path("..", this)
7
+ $:.unshift(lib_path)
8
+
9
+ $ROOT = File.expand_path("../", lib_path)
10
+
11
+ require 'ns/model'
12
+
13
+ Dir.glob(File.join(lib_path, '/**/*.rb')).each do |file|
14
+ require file
15
+ end
16
+
17
+ module Ns
18
+ class << self
19
+
20
+ def configure
21
+ yield configuration
22
+ end
23
+
24
+ def configuration
25
+ @configuration ||= Ns::Configuration.new
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ module Ns
2
+ module Api
3
+ module Request
4
+ class Base
5
+
6
+ attr_reader :response, :parsed_response
7
+
8
+ def initialize(attributes = {})
9
+ HTTPI.log = false
10
+ end
11
+
12
+ def response
13
+ self.class.response_class.new(parsed_response: parsed_response)
14
+ end
15
+
16
+ private
17
+
18
+ def url_for_request
19
+ http_request_with_query_and_authentication
20
+ end
21
+
22
+ def http_request_with_query_and_authentication
23
+ http_request.query = query
24
+ http_request.auth.basic(Ns.configuration.username, Ns.configuration.password)
25
+
26
+ http_request
27
+ end
28
+
29
+ def http_request
30
+ @http_request ||= HTTPI::Request.new(self.class.base_uri)
31
+ end
32
+
33
+ def parsed_response
34
+ @parsed_response ||= response_parser.parsed_response
35
+ end
36
+
37
+ def response_parser
38
+ @response_parser ||= Ns::Api::Response::Parser.new(response_text: response_body)
39
+ end
40
+
41
+ def response_body
42
+ @response_body ||= plain_text_response.body
43
+ #@response_body ||= File.read(File.join($ROOT, 'spec/fixtures/ns_travel_advice_response.xml'))
44
+ #@response_body ||= File.read(File.join($ROOT, 'spec/fixtures/ns_disruptions_with_results.xml'))
45
+ end
46
+
47
+ def plain_text_response
48
+ @plain_text_response ||= HTTPI.get(url_for_request)
49
+ end
50
+
51
+ def self.base_uri; raise "Implement me in a subclass"; end
52
+ def self.response_class; raise "Implement me in a subclass"; end
53
+ def query; raise "Implement me in a subclass"; end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ module Ns
2
+ module Api
3
+ module Request
4
+ class DisruptionCollection < Ns::Api::Request::Base
5
+ include Ns::Model
6
+
7
+ attr_accessor :disruption_collection
8
+
9
+ def self.base_uri
10
+ 'http://webservices.ns.nl/ns-api-storingen'
11
+ end
12
+
13
+ def self.response_class
14
+ Ns::Api::Response::DisruptionCollection
15
+ end
16
+
17
+ private
18
+
19
+ def query
20
+ {
21
+ station: disruption_collection.station,
22
+ actual: disruption_collection.actual,
23
+ unplanned: disruption_collection.include_planned
24
+ }
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ module Ns
2
+ module Api
3
+ module Request
4
+ class TravelAdvice < Ns::Api::Request::Base
5
+ include Ns::Model
6
+
7
+ attr_accessor :trip
8
+
9
+ def self.base_uri
10
+ 'http://webservices.ns.nl/ns-api-treinplanner'
11
+ end
12
+
13
+ def self.response_class
14
+ Ns::Api::Response::TravelAdvice
15
+ end
16
+
17
+ private
18
+
19
+ def query
20
+ {
21
+ fromStation: trip.from.name,
22
+ toStation: trip.to.name,
23
+ viaStation: trip.via.name,
24
+ dateTime: trip.formatted_time,
25
+ departure: trip.departure?,
26
+ hslAllowed: trip.allow_hsl,
27
+ yearCard: trip.year_card
28
+ }
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ module Ns
2
+ module Api
3
+ module Response
4
+ class DisruptionCollection
5
+ include Ns::Model
6
+
7
+ attr_accessor :parsed_response
8
+
9
+ def planned_disruptions
10
+ ( raw_disruptions['Gepland'] || [] ).map do |raw_disruption|
11
+ new_disruption(raw_disruption)
12
+ end.flatten.compact
13
+ end
14
+
15
+ def unplanned_disruptions
16
+ ( raw_disruptions['Ongepland'] || [] ).map do |raw_disruption|
17
+ new_disruption(raw_disruption)
18
+ end.flatten.compact
19
+ end
20
+
21
+ private
22
+
23
+ #
24
+ # Each disruption looks like this:
25
+ #
26
+ # [ 'Storing', { id: '12345', 'Traject': ... } ]
27
+ #
28
+ # which is why we do a check for size, shift the array, and return
29
+ # the first element in the array which is the actual disruption
30
+ # Hash
31
+ #
32
+ def new_disruption(raw_disruption)
33
+ if raw_disruption.size >= 2
34
+ raw_disruption = raw_disruption[1]
35
+ raw_disruption = raw_disruption[0] if raw_disruption.is_a?(Array)
36
+
37
+ Ns::Disruption.new(
38
+ route: raw_disruption['Traject'],
39
+ reason: raw_disruption['Reason'],
40
+ message: raw_disruption['Bericht'],
41
+ advice: raw_disruption['Advies'],
42
+ period: raw_disruption['Periode']
43
+ )
44
+ end
45
+ end
46
+
47
+ def raw_disruptions
48
+ if parsed_response.has_key?('Storingen') && ( parsed_response['Storingen'].has_key?('Gepland') || parsed_response['Storingen'].has_key?('Ongepland') )
49
+ parsed_response['Storingen']
50
+ else
51
+ {}
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ module Ns
2
+ module Api
3
+ module Response
4
+ class Parser
5
+ include Ns::Model
6
+
7
+ attr_accessor :response_text
8
+
9
+ def parsed_response
10
+ @parsed_response ||= parser.parse(response_text)
11
+ end
12
+
13
+ private
14
+
15
+ def parser
16
+ @parser ||= Nori.new
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ module Ns
2
+ module Api
3
+ module Response
4
+ class TravelAdvice
5
+ include Ns::Model
6
+
7
+ attr_accessor :parsed_response
8
+
9
+ def travel_options
10
+ raw_travel_options.map do |travel_option|
11
+ Ns::TravelOption.new(
12
+ planned_departure: travel_option['GeplandeVertrekTijd'],
13
+ actual_departure: travel_option['ActueleVertrekTijd'],
14
+ planned_arrival: travel_option['GeplandeAankomstTijd'],
15
+ actual_arrival: travel_option['ActueleAankomstTijd'],
16
+ changes: travel_option['AantalOverstappen'],
17
+ platform: platform(travel_option),
18
+ optimal: travel_option['Optimaal']
19
+ )
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def raw_travel_options
26
+ if parsed_response.has_key?('ReisMogelijkheden') && parsed_response['ReisMogelijkheden'].has_key?('ReisMogelijkheid')
27
+ parsed_response['ReisMogelijkheden']['ReisMogelijkheid']
28
+ else
29
+ []
30
+ end
31
+ end
32
+
33
+ def platform(travel_option)
34
+ travel_part = travel_option['ReisDeel']
35
+
36
+ if travel_part.is_a?(Array)
37
+ travel_part.first['ReisStop'].first['Spoor']
38
+ else
39
+ travel_part['ReisStop'].first['Spoor']
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end