bahn 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,12 @@
1
+ === 1.0.2 / 2011-01-23
2
+ * Quick fix to departure board scraper to work in 2011 :-/
3
+
4
+ === 1.0.1 / 2009-05-17
5
+ * Added long hashes (Service#long_hash)
6
+
7
+ === 1.0.0 / 2009-05-17
8
+
9
+ * 1 major enhancement
10
+
11
+ * Birthday!
12
+
data/Manifest.txt ADDED
@@ -0,0 +1,6 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/bahn.rb
6
+ test/test_bahn.rb
data/README.txt ADDED
@@ -0,0 +1,64 @@
1
+ = bahn
2
+
3
+ == DESCRIPTION:
4
+
5
+ A library for accessing train information from Deutsche Bahn in an
6
+ object-oriented way
7
+
8
+ == FEATURES
9
+
10
+ * Exposes Station, Service and Stop objects
11
+ * Does hairy screen-scraping
12
+ * Works around bahn.de's crazy transient URLs (provided you don't
13
+ leave objects in scope for 24 hours or so)
14
+
15
+ == BUGS
16
+
17
+ * This is not an official Deutsche Bahn service, nor does it use an official API;
18
+ it does screen-scraping on their HTML, and is therefore liable to break if Deutsche Bahn
19
+ update their website code.
20
+ * Train travel times do not take timezones into account, and may end up 24 hours out
21
+ because whenever it sees a time going 'backwards' (e.g. depart 08:40, arrive 08:20) it
22
+ assumes that just under 24 hours has elapsed. Also, summary timetables for some very long
23
+ distance trains (such as the Trans-Siberian Express) may only show stations that are more
24
+ than 24 hours apart, so there's no way to know that we should be adding on 24 hours to
25
+ the time.
26
+
27
+ == SYNOPSIS:
28
+
29
+ require 'bahn'
30
+ ulm = Bahn::Station.find(:first, :name => 'Ulm')
31
+ #=> #<Bahn::Station:0x55fb4 @id=8000170, @y_coord="48399019", @name="Ulm Hbf", @x_coord="9982161">
32
+ my_train = ulm.departures(:include => :ice).find{|departure| departure.service.name == 'ICE 699'}
33
+ #=> #<Bahn::Stop @time=07:55 @station="Ulm Hbf" @destination="M\303\274nchen Hbf">
34
+ my_train.destination.arrival_time
35
+ #=> 09:17
36
+
37
+ == INSTALL:
38
+
39
+ * sudo gem install bahn
40
+
41
+ == LICENSE:
42
+
43
+ (The MIT License)
44
+
45
+ Copyright (c) 2009 Matthew Westcott
46
+
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of this software and associated documentation files (the
49
+ 'Software'), to deal in the Software without restriction, including
50
+ without limitation the rights to use, copy, modify, merge, publish,
51
+ distribute, sublicense, and/or sell copies of the Software, and to
52
+ permit persons to whom the Software is furnished to do so, subject to
53
+ the following conditions:
54
+
55
+ The above copyright notice and this permission notice shall be
56
+ included in all copies or substantial portions of the Software.
57
+
58
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
59
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
60
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
61
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
62
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
63
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
64
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/bahn.rb'
6
+
7
+ Hoe.new('bahn', Bahn::VERSION) do |p|
8
+ p.developer('Matt Westcott', 'matt@west.co.tt')
9
+ end
10
+
11
+ # vim: syntax=Ruby
data/lib/bahn.rb ADDED
@@ -0,0 +1,478 @@
1
+ require 'rubygems'
2
+ require 'open-uri'
3
+ require 'json'
4
+ require 'iconv'
5
+ require 'hpricot'
6
+ require 'cgi'
7
+ require 'digest/sha1'
8
+
9
+ module Bahn
10
+ VERSION = '1.0.2'
11
+
12
+ # Represents a time of day on a 24-hour clock, without implying any particular date.
13
+ class ClockTime < Time
14
+ # Create a new ClockTime object for the time specified by hours and mins
15
+ def self.clock(hours, mins)
16
+ self.utc(1970, 1, 1, hours, mins, 0)
17
+ end
18
+
19
+ # Parses a string of the form "hh:mm" and converts it to a ClockTime object
20
+ def self.parse(str)
21
+ if str =~ /(\d+):(\d+)/ then self.utc(1970, 1, 1, $1, $2, 0) end
22
+ end
23
+
24
+ def inspect # :nodoc:
25
+ to_s
26
+ end
27
+
28
+ def to_s # :nodoc:
29
+ sprintf("%02d:%02d", hour, min)
30
+ end
31
+ end
32
+
33
+ class Station
34
+ # Finds and returns one or many Station objects. Invoked through one of the following forms:
35
+ #
36
+ # * <tt>Bahn::Station.find(id)</tt> - returns the Station with the given ID
37
+ # * <tt>Bahn::Station.find(:first, :name => name)</tt> - returns the first station matching name
38
+ # * <tt>Bahn::Station.find(:all, :name => name)</tt> - returns an array of all stations matching name
39
+ def self.find(id_or_type, opts = {})
40
+ case id_or_type
41
+ when :first
42
+ query = autocomplete_query(opts[:name])
43
+ query.size ? self.new(:autocomplete_result => query.first) : nil
44
+ when :all
45
+ query = autocomplete_query(opts[:name])
46
+ query.collect {|result| self.new(:autocomplete_result => result)}
47
+ else # assume a numeric ID
48
+ self.new(:id => id_or_type)
49
+ end
50
+ end
51
+
52
+ def initialize(opts) # :nodoc:
53
+ if opts[:autocomplete_result]
54
+ populate_from_autocomplete_result(opts[:autocomplete_result])
55
+ else
56
+ @id = opts[:id].to_i
57
+ @name = opts[:name]
58
+ @x_coord = opts[:x_coord]
59
+ @y_coord = opts[:y_coord]
60
+ end
61
+ end
62
+
63
+ # The numeric ID of this station.
64
+ attr_reader :id
65
+
66
+ # Returns the station name.
67
+ def name
68
+ fetch_autocomplete_result if @name.nil?
69
+ @name
70
+ end
71
+
72
+ # Returns the X coordinate of the station; appears to be a WGS84 longitude in units of 10^-6 degrees.
73
+ def x_coord
74
+ fetch_autocomplete_result if @x_coord.nil?
75
+ @x_coord
76
+ end
77
+
78
+ # Returns the Y coordinate of the station; appears to be a WGS84 latitude in units of 10^-6 degrees.
79
+ def y_coord
80
+ fetch_autocomplete_result if @y_coord.nil?
81
+ @y_coord
82
+ end
83
+
84
+ # Returns an Enumerable listing of Stop objects for all services departing from this station.
85
+ # The list can be filtered by transport type through the following options:
86
+ # * <tt>:include</tt> - One of the symbols :ice, :ic_ec, :ir, :regional, :urban, :bus, :boat, :subway, :tram, or an array of them, to determine which transport types should be included in the list
87
+ # * <tt>:exclude</tt> - Specifies one or more of the above transport types to be excluded from the results.
88
+ def departures(opts = {})
89
+ DepartureBoard.new(self, opts)
90
+ end
91
+
92
+ # =====
93
+ private
94
+ # =====
95
+ def self.autocomplete_query(term)
96
+ uri = "http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=1&REQ0JourneyStopsS0G=#{CGI.escape(term.to_s)}"
97
+ io = open(uri)
98
+ response_text = Iconv.iconv('utf-8', io.charset, io.read).first
99
+ response_json = JSON.parse(response_text.scan(/\{.*\}/).first)
100
+ response_json['suggestions']
101
+ end
102
+
103
+ def fetch_autocomplete_result
104
+ populate_from_autocomplete_result(Bahn::Station.autocomplete_query(@id).first)
105
+ end
106
+
107
+ def populate_from_autocomplete_result(autocomplete_data)
108
+ @id = autocomplete_data['id'].scan(/\@L=(\d+)\@/).first.first.to_i
109
+ @name = autocomplete_data['id'].scan(/\@O=([^\@]*)\@/).first.first
110
+ @x_coord = autocomplete_data['xcoord']
111
+ @y_coord = autocomplete_data['ycoord']
112
+ end
113
+ end
114
+
115
+ class DepartureBoard
116
+ TRANSPORT_TYPES = {
117
+ :ice =>0x100,
118
+ :ic_ec => 0x80,
119
+ :ir => 0x40,
120
+ :regional => 0x20,
121
+ :urban => 0x10,
122
+ :bus => 0x08,
123
+ :boat => 0x04,
124
+ :subway => 0x02,
125
+ :tram => 0x01
126
+ }
127
+ include Enumerable
128
+ def initialize(station, opts)
129
+ @station = station
130
+
131
+ transport_types = Array(opts[:include] || TRANSPORT_TYPES.keys) - Array(opts[:exclude])
132
+ filter_num = transport_types.inject(0) {|sum, type| sum + (TRANSPORT_TYPES[type] || 0) }
133
+ filter = sprintf("%09b", filter_num)
134
+ @url_prefix = "http://reiseauskunft.bahn.de/bin/bhftafel.exe/en?input=#{station.id}&productsFilter=#{filter}&start=1&boardType=dep&dateBegin=12.12.10&dateEnd=10.12.11&time=00:00"
135
+ @departure_pages = []
136
+ end
137
+
138
+ def each
139
+ 0.upto(23) do |hour|
140
+ doc = departure_page(hour)
141
+ # find all tr children of table.result which contain a td.train
142
+ # and do not have class 'browse'
143
+ departure_docs = doc / 'table.result tr[td.train]:not(.browse)'
144
+ departure_docs.each do |departure_doc|
145
+ service_link = departure_doc % 'td.train:nth(2) a'
146
+ destination_link = departure_doc % 'td.route span.bold a'
147
+ (destination_id, destination_time_string), = destination_link['onclick'].scan(/^sHC\(this, '', '(\d+)','([^']*)'\)/)
148
+ destination_time = ClockTime.parse(destination_time_string)
149
+
150
+ intermediate_stops = (departure_doc % 'td.route').children.find_all{|node| node.text?}.join(' ')
151
+ departure_time = ClockTime.parse((departure_doc % 'td.time').inner_text)
152
+ last_time = departure_time
153
+ days_passed = 0
154
+
155
+ intermediate_stops.scan(/\d+:\d+/) do |time_string|
156
+ time = ClockTime.parse(time_string)
157
+ days_passed += 1 if time < last_time
158
+ last_time = time
159
+ end
160
+ inferred_time_to_destination = days_passed * 24*60*60 + (destination_time - departure_time)
161
+
162
+ platform_td = departure_doc % 'td.platform'
163
+ if platform_td.nil?
164
+ platform = :none
165
+ else
166
+ platform = platform_td.inner_text.strip
167
+ end
168
+
169
+ yield Stop.new(
170
+ :station => @station,
171
+ :service => Service.new(
172
+ :path => service_link['href'].sub(/\?.*/, ''),
173
+ :name => service_link.inner_text.strip.gsub(/\s+/, ' '),
174
+ :destination_info => {
175
+ :station => Station.new(
176
+ :id => destination_id,
177
+ :name => destination_link.inner_text.strip
178
+ ),
179
+ :arrival_time => destination_time
180
+ }
181
+ ),
182
+ :departure_time => departure_time,
183
+ :platform => (platform == '' ? :none : platform),
184
+ :inferred_time_to_destination => inferred_time_to_destination
185
+ )
186
+ end
187
+ end
188
+ end
189
+
190
+ # =====
191
+ private
192
+ # =====
193
+ def departure_page(hour)
194
+ @departure_pages[hour] ||= Hpricot(open("#{@url_prefix}%2B#{hour * 60}"))
195
+ end
196
+ end
197
+
198
+ # Represents a scheduled train (or other transportation) service. This is not specific to
199
+ # a particular day's train, but encompasses all trains taking this route at this
200
+ # time of day. When services are subject to daily or seasonal variations, each such variation
201
+ # is considered a Service of its own, even if they share the same service name.
202
+ class Service
203
+ def initialize(opts) # :nodoc:
204
+ @path = opts[:path]
205
+ @name = opts[:name]
206
+ if opts[:origin_info]
207
+ @origin = Stop.new(opts[:origin_info].merge(
208
+ :service => self,
209
+ :arrival_time => :none,
210
+ :arrival_time_from_origin => :none,
211
+ :arrival_time_to_destination => :none,
212
+ :departure_time_from_origin => 0
213
+ ))
214
+ end
215
+ if opts[:destination_info]
216
+ @destination = Stop.new(opts[:destination_info].merge(
217
+ :service => self,
218
+ :arrival_time_to_destination => 0,
219
+ :departure_time => :none,
220
+ :departure_time_from_origin => :none,
221
+ :departure_time_to_destination => :none
222
+ ))
223
+ end
224
+ end
225
+
226
+ # The designated name for this service, if any - e.g. "ICE 692".
227
+ attr_reader :name
228
+
229
+ # Returns an array of Stop objects for all the stops that this service makes.
230
+ def stops
231
+ if !@stops
232
+ stop_docs = traininfo_response_doc / "table.result tr[td.station]"
233
+ last_time = nil
234
+ days_passed = 0
235
+
236
+ origin_departure_time = nil
237
+
238
+ @stops = stop_docs.collect {|stop_doc|
239
+ station_link = stop_doc % 'td.station a'
240
+
241
+ arrival_time = ClockTime.parse((stop_doc % 'td.arrival').inner_text)
242
+ departure_time = ClockTime.parse((stop_doc % 'td.departure').inner_text)
243
+ origin_departure_time ||= departure_time
244
+
245
+ if arrival_time.nil?
246
+ arrival_time_from_origin = :none
247
+ else
248
+ days_passed += 1 if (!last_time.nil? && arrival_time < last_time)
249
+ arrival_time_from_origin = days_passed * 24*60*60 + (arrival_time - origin_departure_time)
250
+ last_time = arrival_time
251
+ end
252
+
253
+ if departure_time.nil?
254
+ departure_time_from_origin = :none
255
+ else
256
+ days_passed += 1 if (!last_time.nil? && departure_time < last_time)
257
+ departure_time_from_origin = days_passed * 24*60*60 + (departure_time - origin_departure_time)
258
+ last_time = departure_time
259
+ end
260
+
261
+ platform = (stop_doc % 'td.platform').inner_text.strip
262
+
263
+ Stop.new(
264
+ :station => Station.new(
265
+ :id => station_link['href'].scan(/&input=[^&]*%23(\d+)&/).first.first,
266
+ :name => station_link.inner_text
267
+ ),
268
+ :service => self,
269
+ :arrival_time => arrival_time || :none,
270
+ :departure_time => departure_time || :none,
271
+ :platform => (platform == '' ? :none : platform),
272
+ :arrival_time_from_origin => arrival_time_from_origin,
273
+ :departure_time_from_origin => departure_time_from_origin
274
+ )
275
+ }
276
+ end
277
+ @stops
278
+ end
279
+
280
+ # Returns the Stop object for the departure station of this service.
281
+ def origin
282
+ @origin ||= stops.first
283
+ end
284
+
285
+ # Returns the Stop object for the destination station of this service.
286
+ def destination
287
+ @destination ||= stops.last
288
+ end
289
+
290
+ def inspect # :nodoc:
291
+ "#<#{self.class} @name=#{@name.inspect} @origin=#{@origin.inspect} @destination=#{@destination.inspect}>"
292
+ end
293
+
294
+ # Returns a hash code which will match for any two Service objects that stop at the same list of
295
+ # stations at the same times.
296
+ def hash
297
+ stops.collect{|stop| stop.subhash}.hash
298
+ end
299
+
300
+ # Returns a hash code which will match for any two Service objects that stop at the same list of
301
+ # stations at the same times; like hash, but using SHA1 to make it more collision-resistant.
302
+ # (This is the nearest thing we have to a unique ID, as Deutsche Bahn do not expose
303
+ # unique IDs for services)
304
+ def long_hash
305
+ Digest::SHA1.hexdigest(stops.collect{|stop| stop.long_hash_element}.join)
306
+ end
307
+
308
+ # Returns an array of strings indicating the features of this train, as listed as 'Comments:' on
309
+ # the Deutsche Bahn website; e.g. "Subject to compulsory reservation", "Sleeping-car". There
310
+ # doesn't seem to be a consistent format for these.
311
+ def features
312
+ if @features.nil?
313
+ tr = (traininfo_response_doc / 'table.remarks tr').find {|tr| (tr % 'th').inner_text == 'Comments:'}
314
+ if tr
315
+ @features = (tr / 'td strong').collect {|elem| elem.inner_text}.uniq
316
+ else
317
+ @features = []
318
+ end
319
+ end
320
+ @features
321
+ end
322
+
323
+ # =====
324
+ private
325
+ # =====
326
+ def traininfo_response_doc
327
+ @traininfo_response_doc ||= Hpricot(open("http://reiseauskunft.bahn.de#{@path}"))
328
+ end
329
+ end
330
+
331
+ # Represents a stop made at a Station by a Service.
332
+ class Stop
333
+ def initialize(opts) # :nodoc:
334
+ # for the following fields, use :none to mean none supplied (as opposed to not fetched yet):
335
+ # @arrival_time, @departure_time, @platform, @arrival_time_from_origin, @departure_time_from_origin
336
+
337
+ @station = opts[:station] # required
338
+ @service = opts[:service] # required
339
+ @arrival_time = opts[:arrival_time]
340
+ @departure_time = opts[:departure_time] # arrival_time or departure_time is required
341
+ @platform = opts[:platform]
342
+ @arrival_time_from_origin = opts[:arrival_time_from_origin]
343
+ @departure_time_from_origin = opts[:departure_time_from_origin]
344
+
345
+ @inferred_time_to_destination = opts[:inferred_time_to_destination]
346
+
347
+ # these can be calculated from arrival_time_from_origin and departure_time_from_origin
348
+ # (but require service.origin, and therefore service.stops, to have been looked up)
349
+ @arrival_time_to_destination = opts[:arrival_time_to_destination]
350
+ @departure_time_to_destination = opts[:departure_time_to_destination]
351
+ end
352
+
353
+ # The Station where this stop occurs.
354
+ attr_reader :station
355
+ # The Service making this stop.
356
+ attr_reader :service
357
+
358
+ # The name of the station for this stop (provided as a shortcut for stop.station.name).
359
+ def name
360
+ @station.name
361
+ end
362
+
363
+ # The Stop object for the departure station (provided as a shortcut for stop.service.origin).
364
+ def origin
365
+ @service.origin
366
+ end
367
+
368
+ # The Stop object for the destination station (provided as a shortcut for stop.service.destination).
369
+ def destination
370
+ @service.destination
371
+ end
372
+
373
+ # Returns the platform number or name for this stop, or nil if not specified.
374
+ def platform
375
+ get_full_details if @platform.nil?
376
+ @platform == :none ? nil : @platform
377
+ end
378
+
379
+ # Returns the ClockTime at which the service arrives at this station, or nil if not specified
380
+ # (e.g. because this is the origin station for the service, or is for boarding only).
381
+ def arrival_time
382
+ get_full_details if @arrival_time.nil?
383
+ @arrival_time == :none ? nil : @arrival_time
384
+ end
385
+
386
+ # Returns the ClockTime at which the service leaves at this station, or nil if not specified
387
+ # (e.g. because this is the destination station for the service, or is for alighting only).
388
+ def departure_time
389
+ get_full_details if @departure_time.nil?
390
+ @departure_time == :none ? nil : @departure_time
391
+ end
392
+
393
+ # Returns the time between departing the origin station and arriving at this station,
394
+ # as a number of seconds, or nil if arrival time is not specified.
395
+ def arrival_time_from_origin
396
+ get_full_details if @arrival_time_from_origin.nil?
397
+ @arrival_time_from_origin == :none ? nil : @arrival_time_from_origin
398
+ end
399
+
400
+ # Returns the time between departing the origin station and departing from this station,
401
+ # as a number of seconds, or nil if departure time is not specified.
402
+ def departure_time_from_origin
403
+ get_full_details if @departure_time_from_origin.nil?
404
+ @departure_time_from_origin == :none ? nil : @departure_time_from_origin
405
+ end
406
+
407
+ # Returns the time between arriving at this station and arriving at the destination station,
408
+ # as a number of seconds, or nil if arrival time is not specified.
409
+ def arrival_time_to_destination
410
+ if @arrival_time_to_destination.nil?
411
+ if arrival_time_from_origin == :none
412
+ @arrival_time_to_destination == :none
413
+ else
414
+ @arrival_time_to_destination = @service.destination.arrival_time_from_origin - arrival_time_from_origin
415
+ end
416
+ end
417
+ @arrival_time_to_destination
418
+ end
419
+
420
+ # Returns the time between departing from this station and arriving at the destination station,
421
+ # as a number of seconds, or nil if departure time is not specified.
422
+ def departure_time_to_destination
423
+ if @departure_time_to_destination.nil?
424
+ if departure_time_from_origin == :none
425
+ @departure_time_to_destination == :none
426
+ else
427
+ @departure_time_to_destination = @service.destination.arrival_time_from_origin - departure_time_from_origin
428
+ end
429
+ end
430
+ @departure_time_to_destination
431
+ end
432
+
433
+ # Returns calculated time to destination while minimising additional hits to the Deutsche Bahn website:
434
+ # * if the service timetable has already been fetched, use that;
435
+ # * if nothing relevant has been fetched yet, fetch the service timetable and use that;
436
+ # * if only the station timetable has been fetched, use that. This may result in an inaccurate count -
437
+ # see BUGS in README.txt.
438
+ def inferred_time_to_destination
439
+ @inferred_time_to_destination ||= departure_time_to_destination || arrival_time_to_destination
440
+ end
441
+
442
+ def inspect # :nodoc:
443
+ "#<#{self.class} @time=#{(@departure_time.nil? || @departure_time == :none ? @arrival_time : @departure_time).inspect} @station=#{@station.name.inspect} @destination=#{service.destination.station.name.inspect}>"
444
+ end
445
+
446
+ # Code identifying this stop, which can form part of the hash for the Service.
447
+ # Not quite suitable as a hash for this stop in its own right, as different trains
448
+ # at the same station at the same time will have the same hash...
449
+ def subhash # :nodoc:
450
+ [@station.id, departure_time, arrival_time].hash
451
+ end
452
+
453
+ # alternative implementation, because standard Ruby hashes are probably not collision-resistant enough
454
+ def long_hash_element # :nodoc:
455
+ "[#{@station.id},#{departure_time},#{arrival_time}]"
456
+ end
457
+
458
+ # =====
459
+ private
460
+ # =====
461
+ def get_full_details
462
+ if @arrival_time and @arrival_time != :none
463
+ stop = @service.stops.find {|stop|
464
+ stop.station.id == @station.id && stop.arrival_time == @arrival_time
465
+ }
466
+ @departure_time = stop.departure_time || :none
467
+ else
468
+ stop = @service.stops.find {|stop|
469
+ stop.station.id == @station.id && stop.departure_time == @departure_time
470
+ }
471
+ @arrival_time = stop.arrival_time || :none
472
+ end
473
+ @platform = stop.platform || :none
474
+ @arrival_time_from_origin = stop.arrival_time_from_origin || :none
475
+ @departure_time_from_origin = stop.departure_time_from_origin || :none
476
+ end
477
+ end
478
+ end
data/test/test_bahn.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "test/unit"
2
+ require "bahn"
3
+
4
+ class TestBahn < Test::Unit::TestCase
5
+ def test_sanity
6
+ flunk "write tests or I will kneecap you"
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bahn
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 2
10
+ version: 1.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Matt Westcott
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-23 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: hoe
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 47
30
+ segments:
31
+ - 2
32
+ - 8
33
+ - 0
34
+ version: 2.8.0
35
+ type: :development
36
+ version_requirements: *id001
37
+ description: |-
38
+ A library for accessing train information from Deutsche Bahn in an
39
+ object-oriented way
40
+ email:
41
+ - matt@west.co.tt
42
+ executables: []
43
+
44
+ extensions: []
45
+
46
+ extra_rdoc_files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.txt
50
+ files:
51
+ - History.txt
52
+ - Manifest.txt
53
+ - README.txt
54
+ - Rakefile
55
+ - lib/bahn.rb
56
+ - test/test_bahn.rb
57
+ has_rdoc: true
58
+ homepage:
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --main
64
+ - README.txt
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project: bahn
88
+ rubygems_version: 1.3.7
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: A library for accessing train information from Deutsche Bahn in an object-oriented way
92
+ test_files:
93
+ - test/test_bahn.rb