trafikanten-travel 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/CHANGES +8 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.rdoc +66 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/trafikanten_travel/route.rb +155 -0
- data/lib/trafikanten_travel/station.rb +57 -0
- data/lib/trafikanten_travel/utils.rb +13 -0
- data/lib/trafikanten_travel.rb +13 -0
- data/spec/fixtures/route.html +63 -0
- data/spec/fixtures/route_search.html +29 -0
- data/spec/options +3 -0
- data/spec/trafikanten_travel/route_spec.rb +176 -0
- data/spec/trafikanten_travel/station_spec.rb +60 -0
- data/spec/trafikanten_travel/utils_spec.rb +5 -0
- data/trafikanten-travel.gemspec +69 -0
- metadata +134 -0
data/.gitignore
ADDED
data/CHANGES
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2010 Rune Botten
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= Trafikanten Travel Planner
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
This is a simple way to request information about travel routes within the
|
6
|
+
public transport system of greater Oslo, Norway. There is no API for looking
|
7
|
+
up route information, so this library gives you an unofficial one. Note that
|
8
|
+
since it is unofficial it might stop working at any time. It should however
|
9
|
+
be easy to adjust this code to make it work again.
|
10
|
+
|
11
|
+
There are typically two things you need to do to retrieve information about a
|
12
|
+
route:
|
13
|
+
|
14
|
+
* Find the stations you want to travel from and to
|
15
|
+
* Use these to query for a route between them
|
16
|
+
|
17
|
+
=== Finding stations
|
18
|
+
You query trafikanten.no by the name of the station and you receive an array of hits with information about each
|
19
|
+
|
20
|
+
stations = Station.find_by_name 'Sthanshaugen'
|
21
|
+
|
22
|
+
The station objects will have attributes for name, id, type, lat and lng. lat and lng might not be available, and are then set to nil.
|
23
|
+
|
24
|
+
=== Finding a route between stations
|
25
|
+
Once you have the stations, you use their IDs to create a Route object:
|
26
|
+
|
27
|
+
from = stations[0]
|
28
|
+
to = stations[1]
|
29
|
+
route = Route.find(from, to)
|
30
|
+
|
31
|
+
# or specify a time
|
32
|
+
route = Route.find(from, to, a_time_object)
|
33
|
+
|
34
|
+
This will query trafikanten.no for the first available route based on Time.now
|
35
|
+
or the passed in time and parse the route it found, if any. The Route object
|
36
|
+
will have a duration attribtue for the duration of the trip in minutes, and a
|
37
|
+
steps array with a Step object for each leg of the journey. Each Step tells
|
38
|
+
you its type (walk, wait, tram, subway, boat, train, bus), duration in
|
39
|
+
minutes, and arrival and departure station names along with times for these.
|
40
|
+
All these are attributes on the Step object.
|
41
|
+
|
42
|
+
== TODO
|
43
|
+
* Also include 'area' stations, which are not actual stations but regions
|
44
|
+
* Implement finding routes by street address
|
45
|
+
* Implement via-stations to chain routes together
|
46
|
+
* Let the depart and arrive stations in Step be actual Station objects
|
47
|
+
|
48
|
+
== Note on Patches/Pull Requests
|
49
|
+
|
50
|
+
* Fork the project.
|
51
|
+
* Make your feature addition or bug fix.
|
52
|
+
* Add tests for it in spec/
|
53
|
+
* Commit, do not mess with Rakefile or VERSION.
|
54
|
+
(if you want to have your own version, that is fine but
|
55
|
+
bump version in a commit by itself I can ignore when I pull)
|
56
|
+
* Send me a pull request. Bonus points for topic branches.
|
57
|
+
|
58
|
+
== Running tests
|
59
|
+
|
60
|
+
rake
|
61
|
+
|
62
|
+
If you get errors about missing gems - just install them.
|
63
|
+
|
64
|
+
== Copyright
|
65
|
+
|
66
|
+
Copyright (c) 2010 Rune Botten. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "trafikanten-travel"
|
8
|
+
gem.summary = %Q{Trafikanten.no Travel Planner}
|
9
|
+
gem.description = %Q{Query the travel planner at trafikanten.no with Ruby}
|
10
|
+
gem.email = "rbotten@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/runeb/trafikanten-travel"
|
12
|
+
gem.authors = ["Rune Botten"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_dependency('nokogiri', '>= 1.4.1')
|
15
|
+
gem.add_dependency('geoutm', '>= 0.0.4') # --source http://gems.github.com
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :spec => :check_dependencies
|
36
|
+
|
37
|
+
task :default => :spec
|
38
|
+
|
39
|
+
require 'rake/rdoctask'
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "trafikanten-travel #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TrafikantenTravel
|
4
|
+
class Error < StandardError;end
|
5
|
+
class BadRequest < Error;end
|
6
|
+
|
7
|
+
class Route
|
8
|
+
include TrafikantenTravel::Utils
|
9
|
+
attr_accessor :duration, :steps
|
10
|
+
BASE_URL = 'http://m.trafikanten.no/BetRes.asp?'
|
11
|
+
|
12
|
+
# Searches and returns a Route object for the found trip
|
13
|
+
def self.find(from, to, time = TrafikantenTravel::Utils.time_class.now)
|
14
|
+
route = Route.new(from, to, time)
|
15
|
+
route.parse
|
16
|
+
route
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(from, to, time = TrafikantenTravel::Utils.time_class.now)
|
20
|
+
@from = from
|
21
|
+
@to = to
|
22
|
+
@time = time
|
23
|
+
@steps = []
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parse the received HTML. First try some error-checking.
|
27
|
+
def parse
|
28
|
+
doc = TrafikantenTravel::Utils.fetch(BASE_URL + query_string)
|
29
|
+
|
30
|
+
if doc =~ /Ingen forbindelse funnet eller ingen stoppesteder funnet/
|
31
|
+
return {}
|
32
|
+
end
|
33
|
+
|
34
|
+
if doc =~ /Trafikanten - Feilmelding/
|
35
|
+
doc =~ /<p>(.+)<\/p>/
|
36
|
+
raise Error.new($1)
|
37
|
+
end
|
38
|
+
|
39
|
+
if doc =~ /Microsoft VBScript runtime/
|
40
|
+
raise BadRequest
|
41
|
+
end
|
42
|
+
|
43
|
+
do_parse(doc)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Do the actual parsing
|
49
|
+
def do_parse(raw)
|
50
|
+
doc = Nokogiri::HTML.parse(raw)
|
51
|
+
|
52
|
+
self.steps = doc.css('p')[1..-1].inject([]) do |ary, step|
|
53
|
+
# Clean the text
|
54
|
+
step = step.text.strip.gsub(/\s/, ' ')
|
55
|
+
|
56
|
+
# Fix for broken formatting
|
57
|
+
# All steps but this one are in their own paragraph-tag
|
58
|
+
# Need to split them and parse each
|
59
|
+
if step =~ /^(Vent .+ minutter|minutt)(.+)/
|
60
|
+
ary << Step.from_html(@time, $1)
|
61
|
+
ary << Step.from_html(@time, $2)
|
62
|
+
else
|
63
|
+
ary << Step.from_html(@time, step)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Duration is the sum of all steps
|
68
|
+
self.duration = self.steps.inject(0) do |i, step|
|
69
|
+
i += step.duration if step.duration
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# The crucial part of the URL we need to fetch to find the route
|
74
|
+
def query_string
|
75
|
+
"fra=#{@from.id}%3A&DepType=#{@from.type}&date=#{@time.strftime("%d.%m.%Y")}&til=#{@to.id}%3A&arrType=#{@to.type}&Transport=2,%207,%205,%208,%201,%206,%204&MaxRadius=700&type=1&tid=#{@time.strftime("%H.%M")}"
|
76
|
+
end
|
77
|
+
|
78
|
+
class Step
|
79
|
+
attr_accessor :type, :line, :duration, :arrive, :depart
|
80
|
+
|
81
|
+
# Regexes for matching steps in the HTML
|
82
|
+
WALK = /Gå\s+fra (.+) til (.+) ca. (\d) minutt/u
|
83
|
+
WAIT = /Vent\s+(\d+) minutt/
|
84
|
+
TRANSPORT = /(\S+) (.+).+Avg: (.+) (\d{2}.\d{2}).+Ank:(.+) (\d{2}.\d{2})/u
|
85
|
+
|
86
|
+
TYPE_MAP = {
|
87
|
+
'Tog' => :train,
|
88
|
+
'T-bane' => :subway,
|
89
|
+
'Sporvogn' => :tram,
|
90
|
+
'Båt' => :boat,
|
91
|
+
'Buss' => :bus
|
92
|
+
}
|
93
|
+
|
94
|
+
def self.from_html(time, html)
|
95
|
+
step = new
|
96
|
+
case html
|
97
|
+
when WAIT
|
98
|
+
step.type = :wait
|
99
|
+
step.duration = $1.to_i
|
100
|
+
when WALK
|
101
|
+
step.type = :walk
|
102
|
+
step.duration = $3.to_i
|
103
|
+
step.depart = {
|
104
|
+
:station => $1
|
105
|
+
}
|
106
|
+
step.arrive = {
|
107
|
+
:station => $2
|
108
|
+
}
|
109
|
+
when TRANSPORT
|
110
|
+
step.type = TYPE_MAP[$1]
|
111
|
+
step.line = $2.strip
|
112
|
+
step.duration = duration_between(time, $4, $6)
|
113
|
+
step.depart = {
|
114
|
+
:station => $3,
|
115
|
+
:time => timestr_to_time(time, $4, $4)
|
116
|
+
}
|
117
|
+
step.arrive = {
|
118
|
+
:station => $5,
|
119
|
+
:time => timestr_to_time(time, $4, $6)
|
120
|
+
}
|
121
|
+
end
|
122
|
+
step
|
123
|
+
end
|
124
|
+
|
125
|
+
# Accepts two HH.MM and will calculate and return the difference in minutes based on the @time date
|
126
|
+
# FIXME: This is baaad
|
127
|
+
def self.duration_between(date, from, to)
|
128
|
+
from = from.gsub('.', ':')
|
129
|
+
to = to.gsub('.', ':')
|
130
|
+
|
131
|
+
from_time = TrafikantenTravel::Utils.time_class.parse(date.strftime('%Y-%m-%d') + ' ' + from)
|
132
|
+
to_time = TrafikantenTravel::Utils.time_class.parse(date.strftime('%Y-%m-%d') + ' ' + to)
|
133
|
+
|
134
|
+
if(to.to_f < from.to_f)
|
135
|
+
to_time = to_time + (60 * 60 * 24)
|
136
|
+
end
|
137
|
+
|
138
|
+
((to_time - from_time) / 60).to_i
|
139
|
+
end
|
140
|
+
|
141
|
+
# FIXME: Also baaad
|
142
|
+
def self.timestr_to_time(date, from_timestr, to_timestr)
|
143
|
+
time = TrafikantenTravel::Utils.time_class.parse(date.strftime('%Y-%m-%d') + ' ' + to_timestr.gsub('.', ':'))
|
144
|
+
|
145
|
+
if(to_timestr.to_f < from_timestr.to_f)
|
146
|
+
time = time + (60 * 60 * 24)
|
147
|
+
end
|
148
|
+
time
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module TrafikantenTravel
|
2
|
+
class Station
|
3
|
+
BASE_URL = 'http://www5.trafikanten.no/txml/?type=1&stopname=%s'
|
4
|
+
|
5
|
+
attr_accessor :name, :id, :type, :lat, :lng
|
6
|
+
|
7
|
+
def initialize(attrs = {})
|
8
|
+
attrs.each do |k,v|
|
9
|
+
self.__send__("#{k}=", v)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Query the Travel XML API @ trafikanten.no for Stations. This will only
|
14
|
+
# include actual stations, not regions (type 2) We'll have to parse
|
15
|
+
# m.trafikanten.no to receive those, but that will not give us coordinates
|
16
|
+
# for the actual stations. See git history for an implementation that did
|
17
|
+
# this.
|
18
|
+
def self.find_by_name(name)
|
19
|
+
raw = open(BASE_URL % CGI.escape(name))
|
20
|
+
doc = Nokogiri::XML.parse raw
|
21
|
+
hits = doc.css('StopMatch').inject([]) do |ary, stop|
|
22
|
+
|
23
|
+
x_coord = stop.css('XCoordinate').text
|
24
|
+
y_coord = stop.css('YCoordinate').text
|
25
|
+
|
26
|
+
if x_coord != '0' && y_coord != '0'
|
27
|
+
lat_lng = GeoUtm::UTM.new('32V', x_coord.to_i, y_coord.to_i).to_lat_lon
|
28
|
+
end
|
29
|
+
|
30
|
+
ary << Station.new({
|
31
|
+
:id => stop.css('fromid').text,
|
32
|
+
:name => stop.css('StopName').text,
|
33
|
+
:lat => lat_lng ? lat_lng.lat.to_s : nil,
|
34
|
+
:lng => lat_lng ? lat_lng.lon.to_s : nil
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Internal type @ Trafikanten. Used with GET requests for routes in
|
40
|
+
# depType and arrType. We guess these unless set.
|
41
|
+
def type
|
42
|
+
return @type if @type
|
43
|
+
|
44
|
+
case @id.length
|
45
|
+
when 8
|
46
|
+
'1'
|
47
|
+
when 10
|
48
|
+
'2'
|
49
|
+
when 9
|
50
|
+
'4'
|
51
|
+
else
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'iconv'
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'nokogiri'
|
7
|
+
require 'geoutm'
|
8
|
+
|
9
|
+
require File.dirname(__FILE__) + '/trafikanten_travel/utils'
|
10
|
+
require File.dirname(__FILE__) + '/trafikanten_travel/station'
|
11
|
+
require File.dirname(__FILE__) + '/trafikanten_travel/route'
|
12
|
+
|
13
|
+
module TrafikantenTravel;end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
<!--++++++++++++-->
|
3
|
+
<!-- ASP code -->
|
4
|
+
<!--++++++++++++-->
|
5
|
+
|
6
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
7
|
+
<html>
|
8
|
+
<head>
|
9
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
10
|
+
<link rel="stylesheet" href="m.css" type="text/css" />
|
11
|
+
<title>Trafikanten - din reise</title>
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
|
17
|
+
<p>
|
18
|
+
Tog R20 Lillehammer
|
19
|
+
<br/>Avg: Holmestrand [tog] 12.24
|
20
|
+
|
21
|
+
<br/>Ank:Oslo Sentralstasjon [tog] 13.28 </p>
|
22
|
+
<p>
|
23
|
+
Gå fra Oslo Sentralstasjon [tog] til Jernbanetorget (foran Oslo S) ca. 4 minutter.
|
24
|
+
</p>
|
25
|
+
<p>
|
26
|
+
|
27
|
+
Vent 18 minutter<br/><br/>Buss 60 Vippetangen
|
28
|
+
<br/>Avg: Jernbanetorget (foran Oslo S) 13.50
|
29
|
+
|
30
|
+
<br/>Ank:Vippetangen (ved Fiskehallen) 13.56 </p>
|
31
|
+
<p>
|
32
|
+
Gå fra Vippetangen (ved Fiskehallen) til Vippetangen [båt] ca. 2 minutter.
|
33
|
+
</p>
|
34
|
+
<p>
|
35
|
+
|
36
|
+
Vent 7 minutter<br/><br/>Båt 93 Gressholmen
|
37
|
+
<br/>Avg: Vippetangen [båt] 14.05
|
38
|
+
|
39
|
+
<br/>Ank:Gressholmen 14.20 </p>
|
40
|
+
|
41
|
+
<br/>Tot. reisetid 1 time og 56 minutter<br/>
|
42
|
+
|
43
|
+
Maks gangavstand er satt til ca. 10 min eller 700 meter.
|
44
|
+
<br/>
|
45
|
+
<br/>
|
46
|
+
|
47
|
+
<a title="Forrige avgang" href="BetRes.asp?fra=07025050%3AHolmestrand+%5Btog%5D&DepType=1&date=19.05.2010&til=03010175%3AGressholmen&ArrType=1&transport=2, 7, 5, 8, 1, 6, 4&MaxRadius=700&type=2&tid=14.19">Forrige avgang</a>
|
48
|
+
<br/>
|
49
|
+
<a title="Neste avgang" href="BetRes.asp?fra=07025050%3AHolmestrand+%5Btog%5D&DepType=1&date=19.05.2010&til=03010175%3AGressholmen&ArrType=1&transport=2, 7, 5, 8, 1, 6, 4&MaxRadius=700&type=1&tid=12.25">Neste avgang</a>
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
<br/>
|
57
|
+
<a href="default.asp">Hjem</a>
|
58
|
+
<br/>
|
59
|
+
|
60
|
+
</small>
|
61
|
+
</p>
|
62
|
+
</body>
|
63
|
+
</html>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
5
|
+
<link rel="stylesheet" href="m.css" type="text/css" />
|
6
|
+
<title>Trafikanten - dine reiser</title>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<p>
|
10
|
+
|
11
|
+
Fra: Til: <br/>
|
12
|
+
Avga: Anko: Reisetid: Merkn.<br/>
|
13
|
+
|
14
|
+
|
15
|
+
<a href="BetRes.asp?fra=03010323%3A&DepType=1&date=19.05.2010&til=03010600%3A&arrType=1&Transport=2, 7, 5, 8, 1, 6, 4&MaxRadius=700&type=1&tid=11.17">11.17 11.30 00.13 MERK!</a><br/>
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
<a title="Tidligere avganger" href="BetOv.asp?fra=03010323%3A&DepType=1&date=19.05.2010&til=03010600%3A&arrType=1&Transport=2, 7, 5, 8, 1, 6, 4&MaxRadius=700&tid=11.29&type=4">Tidligere avganger</a>
|
20
|
+
<br/>
|
21
|
+
<a title="Senere avganger" href="BetOv.asp?fra=03010323%3A&DepType=1&date=19.05.2010&til=03010600%3A&arrType=1&Transport=2, 7, 5, 8, 1, 6, 4&MaxRadius=700&tid=11.18&type=3">Senere avganger</a>
|
22
|
+
<br/>
|
23
|
+
<a href="default.asp">Trafikanten - Hjem</a>
|
24
|
+
<br/>
|
25
|
+
|
26
|
+
|
27
|
+
</p>
|
28
|
+
</body>
|
29
|
+
</html>
|
data/spec/options
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/trafikanten_travel'
|
3
|
+
|
4
|
+
describe TrafikantenTravel::Route do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
@from = TrafikantenTravel::Station.new({:id => '1000013064'})
|
8
|
+
@to = TrafikantenTravel::Station.new({:id => '02350113'})
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'searching' do
|
12
|
+
|
13
|
+
it 'takes two stations and a Time and returns the Route found' do
|
14
|
+
doc = File.read(File.dirname(__FILE__) + '/../fixtures/route.html')
|
15
|
+
TrafikantenTravel::Utils.stub(:fetch).and_return(doc)
|
16
|
+
from = TrafikantenTravel::Station.new({:id => '07025050'})
|
17
|
+
to = TrafikantenTravel::Station.new({:id => '03010175'})
|
18
|
+
route = TrafikantenTravel::Route.find(from, to, Time.parse('2010-05-19 12:24 +0200'))
|
19
|
+
|
20
|
+
route.duration.should == 60 + 56
|
21
|
+
|
22
|
+
route.steps.class.should == Array
|
23
|
+
|
24
|
+
# Test first step
|
25
|
+
step = route.steps.first
|
26
|
+
step.class.should == TrafikantenTravel::Route::Step
|
27
|
+
step.duration.should == 64
|
28
|
+
|
29
|
+
step.depart.should == {
|
30
|
+
:station => "Holmestrand [tog]",
|
31
|
+
:time => Time.parse('2010-05-19 12:24 +0200')
|
32
|
+
}
|
33
|
+
|
34
|
+
step.arrive.should == {
|
35
|
+
:station => "Oslo Sentralstasjon [tog]",
|
36
|
+
:time => Time.parse('2010-05-19 13:28 +0200')
|
37
|
+
}
|
38
|
+
|
39
|
+
# Test last step
|
40
|
+
step = route.steps.last
|
41
|
+
step.duration.should == 15
|
42
|
+
step.depart.should == {
|
43
|
+
:station => 'Vippetangen [båt]',
|
44
|
+
:time => Time.parse('2010-05-19 14:05 +0200')
|
45
|
+
}
|
46
|
+
step.arrive.should == {
|
47
|
+
:station => 'Gressholmen',
|
48
|
+
:time => Time.parse('2010-05-19 14:20 +0200')
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'url generation' do
|
53
|
+
# Test the whole thing
|
54
|
+
it 'generates exactly this' do
|
55
|
+
route = TrafikantenTravel::Route.find(@from, @to )
|
56
|
+
query = route.send(:query_string)
|
57
|
+
query.should == "fra=1000013064%3A&DepType=2&date=#{Time.now.strftime("%d.%m.%Y")}&til=02350113%3A&arrType=1&Transport=2,%207,%205,%208,%201,%206,%204&MaxRadius=700&type=1&tid=#{Time.now.strftime("%H.%M")}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Test the parts in isolation
|
61
|
+
it 'takles stations and their types' do
|
62
|
+
route = TrafikantenTravel::Route.find(@from, @to )
|
63
|
+
query = route.send(:query_string)
|
64
|
+
query.should =~ /fra=1000013064%3A&DepType=2/
|
65
|
+
query.should =~ /til=02350113%3A&arrType=1/
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'defaults to Time.now' do
|
69
|
+
route = TrafikantenTravel::Route.find(@from, @to )
|
70
|
+
query = route.send(:query_string)
|
71
|
+
query.should =~ /date=#{Time.now.strftime("%d.%m.%Y")}/
|
72
|
+
query.should =~ /tid=#{Time.now.strftime("%H.%M")}/
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'uses the time passed in' do
|
76
|
+
route = TrafikantenTravel::Route.new(@from, @to, Time.parse('2010-04-29 13:29'))
|
77
|
+
query = route.send(:query_string)
|
78
|
+
query.should =~ /&date=29.04.2010/
|
79
|
+
query.should =~ /&tid=13.29/
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'steps' do
|
85
|
+
it 'can tell the duration in minutes, from messed up time information' do
|
86
|
+
TrafikantenTravel::Route::Step.send(:duration_between, Time.now, '12.00', '13.45').should == 60 + 45
|
87
|
+
TrafikantenTravel::Route::Step.send(:duration_between, Time.now, '23.30', '00.05').should == 30 + 5
|
88
|
+
TrafikantenTravel::Route::Step.send(:duration_between, Time.now, '00.00', '00.00').should == 0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'error handling' do
|
93
|
+
it 'knows about missing routes and return empty array for steps' do
|
94
|
+
missing = <<eos
|
95
|
+
<!--++++++++++++-->
|
96
|
+
<!-- ASP code -->
|
97
|
+
<!--++++++++++++-->
|
98
|
+
|
99
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
100
|
+
<html>
|
101
|
+
<head>
|
102
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
103
|
+
<link rel="stylesheet" href="m.css" type="text/css" />
|
104
|
+
<title>Trafikanten - Feilmelding</title>
|
105
|
+
<body>
|
106
|
+
<p>Ingen forbindelse funnet eller ingen stoppesteder funnet</p>
|
107
|
+
</body>
|
108
|
+
</html>
|
109
|
+
eos
|
110
|
+
|
111
|
+
TrafikantenTravel::Utils.stub(:fetch).and_return(missing)
|
112
|
+
route = TrafikantenTravel::Route.find(@from, @to)
|
113
|
+
route.steps.should == []
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'it parses errors and raises them locally' do
|
117
|
+
error = <<eos
|
118
|
+
|
119
|
+
<!--++++++++++++-->
|
120
|
+
<!-- ASP code -->
|
121
|
+
<!--++++++++++++-->
|
122
|
+
|
123
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
124
|
+
<html>
|
125
|
+
<head>
|
126
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
127
|
+
<link rel="stylesheet" href="m.css" type="text/css" />
|
128
|
+
<title>Trafikanten - Feilmelding</title>
|
129
|
+
<body>
|
130
|
+
<p>Some generic error</p>
|
131
|
+
</body>
|
132
|
+
</html>
|
133
|
+
eos
|
134
|
+
TrafikantenTravel::Utils.stub(:fetch).and_return(error)
|
135
|
+
lambda { TrafikantenTravel::Route.find(@from, @to) }.should raise_error(TrafikantenTravel::Error, "Some generic error")
|
136
|
+
|
137
|
+
error = <<eos
|
138
|
+
|
139
|
+
<!--++++++++++++-->
|
140
|
+
<!-- ASP code -->
|
141
|
+
<!--++++++++++++-->
|
142
|
+
|
143
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
144
|
+
<html>
|
145
|
+
<head>
|
146
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
147
|
+
<link rel="stylesheet" href="m.css" type="text/css" />
|
148
|
+
<title>Trafikanten - Feilmelding</title>
|
149
|
+
<body>
|
150
|
+
<p>Some totally different error</p>
|
151
|
+
</body>
|
152
|
+
</html>
|
153
|
+
eos
|
154
|
+
TrafikantenTravel::Utils.stub(:fetch).and_return(error)
|
155
|
+
lambda { TrafikantenTravel::Route.find(@from, @to) }.should raise_error(TrafikantenTravel::Error, "Some totally different error")
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'handles unpredicted errors as bad requests' do
|
159
|
+
weird_error = <<eos
|
160
|
+
|
161
|
+
<!--++++++++++++-->
|
162
|
+
<!-- ASP code -->
|
163
|
+
<!--++++++++++++-->
|
164
|
+
|
165
|
+
<font face="Arial" size=2>
|
166
|
+
<p>Microsoft VBScript runtime </font> <font face="Arial" size=2>error '800a0005'</font>
|
167
|
+
<p>
|
168
|
+
<font face="Arial" size=2>Invalid procedure call or argument: 'left'</font>
|
169
|
+
<p>
|
170
|
+
<font face="Arial" size=2>/BetRes.asp</font><font face="Arial" size=2>, line 71</font>
|
171
|
+
eos
|
172
|
+
TrafikantenTravel::Utils.stub(:fetch).and_return(weird_error)
|
173
|
+
lambda { TrafikantenTravel::Route.find(@from, @to) }.should raise_error(TrafikantenTravel::BadRequest)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#:encoding:utf-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/../../lib/trafikanten_travel/station'
|
4
|
+
|
5
|
+
describe TrafikantenTravel::Station do
|
6
|
+
context 'search' do
|
7
|
+
it 'searches by name and returns Stations array' do
|
8
|
+
stations = TrafikantenTravel::Station.find_by_name('Ullevåll')
|
9
|
+
stations.size.should == 6
|
10
|
+
|
11
|
+
# Test first
|
12
|
+
station = stations[0]
|
13
|
+
station.name.should == "Ullevål stadion (i Sognsveien)"
|
14
|
+
station.id.should == '03012211'
|
15
|
+
station.type.should == '1'
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns an empty array when searching for stations and did not find any' do
|
20
|
+
stations = TrafikantenTravel::Station.find_by_name('XXX')
|
21
|
+
stations.should == []
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'geodata' do
|
27
|
+
it 'has latitude and longitude' do
|
28
|
+
stations = TrafikantenTravel::Station.find_by_name('Sthanshaugen')
|
29
|
+
|
30
|
+
# Test first
|
31
|
+
station = stations.first
|
32
|
+
station.name.should == "St. Hanshaugen (v/ Markus krk)"
|
33
|
+
station.lat.should == '59.9239720442894'
|
34
|
+
station.lng.should == '10.7397078950283'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'type' do
|
39
|
+
it 'is guessed based on ID length when not known' do
|
40
|
+
s = TrafikantenTravel::Station.new
|
41
|
+
|
42
|
+
s.id = '1000020910'
|
43
|
+
s.type.should == "2"
|
44
|
+
|
45
|
+
s.id = '05440105'
|
46
|
+
s.type.should == "1"
|
47
|
+
|
48
|
+
s.id = '030117972'
|
49
|
+
s.type.should == "4"
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'is not guessed when known' do
|
53
|
+
s = TrafikantenTravel::Station.new
|
54
|
+
s.id = '1000020910'
|
55
|
+
s.type = "29"
|
56
|
+
s.type.should == "29"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{trafikanten-travel}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Rune Botten"]
|
12
|
+
s.date = %q{2010-05-25}
|
13
|
+
s.description = %q{Query the travel planner at trafikanten.no with Ruby}
|
14
|
+
s.email = %q{rbotten@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"CHANGES",
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/trafikanten_travel.rb",
|
28
|
+
"lib/trafikanten_travel/route.rb",
|
29
|
+
"lib/trafikanten_travel/station.rb",
|
30
|
+
"lib/trafikanten_travel/utils.rb",
|
31
|
+
"spec/fixtures/route.html",
|
32
|
+
"spec/fixtures/route_search.html",
|
33
|
+
"spec/options",
|
34
|
+
"spec/trafikanten_travel/route_spec.rb",
|
35
|
+
"spec/trafikanten_travel/station_spec.rb",
|
36
|
+
"spec/trafikanten_travel/utils_spec.rb",
|
37
|
+
"trafikanten-travel.gemspec"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/runeb/trafikanten-travel}
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.3.7}
|
43
|
+
s.summary = %q{Trafikanten.no Travel Planner}
|
44
|
+
s.test_files = [
|
45
|
+
"spec/trafikanten_travel/route_spec.rb",
|
46
|
+
"spec/trafikanten_travel/station_spec.rb",
|
47
|
+
"spec/trafikanten_travel/utils_spec.rb"
|
48
|
+
]
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
52
|
+
s.specification_version = 3
|
53
|
+
|
54
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
55
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
56
|
+
s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.1"])
|
57
|
+
s.add_runtime_dependency(%q<geoutm>, [">= 0.0.4"])
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
60
|
+
s.add_dependency(%q<nokogiri>, [">= 1.4.1"])
|
61
|
+
s.add_dependency(%q<geoutm>, [">= 0.0.4"])
|
62
|
+
end
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
65
|
+
s.add_dependency(%q<nokogiri>, [">= 1.4.1"])
|
66
|
+
s.add_dependency(%q<geoutm>, [">= 0.0.4"])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trafikanten-travel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Rune Botten
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-05-25 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
34
|
+
version: 1.2.9
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: nokogiri
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 5
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 4
|
49
|
+
- 1
|
50
|
+
version: 1.4.1
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: geoutm
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 23
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 0
|
65
|
+
- 4
|
66
|
+
version: 0.0.4
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
description: Query the travel planner at trafikanten.no with Ruby
|
70
|
+
email: rbotten@gmail.com
|
71
|
+
executables: []
|
72
|
+
|
73
|
+
extensions: []
|
74
|
+
|
75
|
+
extra_rdoc_files:
|
76
|
+
- LICENSE
|
77
|
+
- README.rdoc
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- CHANGES
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE
|
83
|
+
- README.rdoc
|
84
|
+
- Rakefile
|
85
|
+
- VERSION
|
86
|
+
- lib/trafikanten_travel.rb
|
87
|
+
- lib/trafikanten_travel/route.rb
|
88
|
+
- lib/trafikanten_travel/station.rb
|
89
|
+
- lib/trafikanten_travel/utils.rb
|
90
|
+
- spec/fixtures/route.html
|
91
|
+
- spec/fixtures/route_search.html
|
92
|
+
- spec/options
|
93
|
+
- spec/trafikanten_travel/route_spec.rb
|
94
|
+
- spec/trafikanten_travel/station_spec.rb
|
95
|
+
- spec/trafikanten_travel/utils_spec.rb
|
96
|
+
- trafikanten-travel.gemspec
|
97
|
+
has_rdoc: true
|
98
|
+
homepage: http://github.com/runeb/trafikanten-travel
|
99
|
+
licenses: []
|
100
|
+
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options:
|
103
|
+
- --charset=UTF-8
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
hash: 3
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
hash: 3
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
version: "0"
|
124
|
+
requirements: []
|
125
|
+
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 1.3.7
|
128
|
+
signing_key:
|
129
|
+
specification_version: 3
|
130
|
+
summary: Trafikanten.no Travel Planner
|
131
|
+
test_files:
|
132
|
+
- spec/trafikanten_travel/route_spec.rb
|
133
|
+
- spec/trafikanten_travel/station_spec.rb
|
134
|
+
- spec/trafikanten_travel/utils_spec.rb
|