gasman-bahn 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2009-05-17
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,6 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/bahn.rb
6
+ test/test_bahn.rb
@@ -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.
@@ -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
@@ -0,0 +1,465 @@
1
+ require 'rubygems'
2
+ require 'open-uri'
3
+ require 'json'
4
+ require 'iconv'
5
+ require 'hpricot'
6
+ require 'cgi'
7
+
8
+ module Bahn
9
+ VERSION = '1.0.0'
10
+
11
+ # Represents a time of day on a 24-hour clock, without implying any particular date.
12
+ class ClockTime < Time
13
+ # Create a new ClockTime object for the time specified by hours and mins
14
+ def self.clock(hours, mins)
15
+ self.utc(1970, 1, 1, hours, mins, 0)
16
+ end
17
+
18
+ # Parses a string of the form "hh:mm" and converts it to a ClockTime object
19
+ def self.parse(str)
20
+ if str =~ /(\d+):(\d+)/ then self.utc(1970, 1, 1, $1, $2, 0) end
21
+ end
22
+
23
+ def inspect # :nodoc:
24
+ to_s
25
+ end
26
+
27
+ def to_s # :nodoc:
28
+ sprintf("%02d:%02d", hour, min)
29
+ end
30
+ end
31
+
32
+ class Station
33
+ # Finds and returns one or many Station objects. Invoked through one of the following forms:
34
+ #
35
+ # * <tt>Bahn::Station.find(id)</tt> - returns the Station with the given ID
36
+ # * <tt>Bahn::Station.find(:first, :name => name)</tt> - returns the first station matching name
37
+ # * <tt>Bahn::Station.find(:all, :name => name)</tt> - returns an array of all stations matching name
38
+ def self.find(id_or_type, opts = {})
39
+ case id_or_type
40
+ when :first
41
+ query = autocomplete_query(opts[:name])
42
+ query.size ? self.new(:autocomplete_result => query.first) : nil
43
+ when :all
44
+ query = autocomplete_query(opts[:name])
45
+ query.collect {|result| self.new(:autocomplete_result => result)}
46
+ else # assume a numeric ID
47
+ self.new(:id => id_or_type)
48
+ end
49
+ end
50
+
51
+ def initialize(opts) # :nodoc:
52
+ if opts[:autocomplete_result]
53
+ populate_from_autocomplete_result(opts[:autocomplete_result])
54
+ else
55
+ @id = opts[:id].to_i
56
+ @name = opts[:name]
57
+ @x_coord = opts[:x_coord]
58
+ @y_coord = opts[:y_coord]
59
+ end
60
+ end
61
+
62
+ # The numeric ID of this station.
63
+ attr_reader :id
64
+
65
+ # Returns the station name.
66
+ def name
67
+ fetch_autocomplete_result if @name.nil?
68
+ @name
69
+ end
70
+
71
+ # Returns the X coordinate of the station; appears to be a WGS84 longitude in units of 10^-6 degrees.
72
+ def x_coord
73
+ fetch_autocomplete_result if @x_coord.nil?
74
+ @x_coord
75
+ end
76
+
77
+ # Returns the Y coordinate of the station; appears to be a WGS84 latitude in units of 10^-6 degrees.
78
+ def y_coord
79
+ fetch_autocomplete_result if @y_coord.nil?
80
+ @y_coord
81
+ end
82
+
83
+ # Returns an Enumerable listing of Stop objects for all services departing from this station.
84
+ # The list can be filtered by transport type through the following options:
85
+ # * <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
86
+ # * <tt>:exclude</tt> - Specifies one or more of the above transport types to be excluded from the results.
87
+ def departures(opts = {})
88
+ DepartureBoard.new(self, opts)
89
+ end
90
+
91
+ # =====
92
+ private
93
+ # =====
94
+ def self.autocomplete_query(term)
95
+ uri = "http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=1&REQ0JourneyStopsS0G=#{CGI.escape(term.to_s)}"
96
+ io = open(uri)
97
+ response_text = Iconv.iconv('utf-8', io.charset, io.read).first
98
+ response_json = JSON.parse(response_text.scan(/\{.*\}/).first)
99
+ response_json['suggestions']
100
+ end
101
+
102
+ def fetch_autocomplete_result
103
+ populate_from_autocomplete_result(Bahn::Station.autocomplete_query(@id).first)
104
+ end
105
+
106
+ def populate_from_autocomplete_result(autocomplete_data)
107
+ @id = autocomplete_data['id'].scan(/\@L=(\d+)\@/).first.first.to_i
108
+ @name = autocomplete_data['id'].scan(/\@O=([^\@]*)\@/).first.first
109
+ @x_coord = autocomplete_data['xcoord']
110
+ @y_coord = autocomplete_data['ycoord']
111
+ end
112
+ end
113
+
114
+ class DepartureBoard
115
+ TRANSPORT_TYPES = {
116
+ :ice =>0x100,
117
+ :ic_ec => 0x80,
118
+ :ir => 0x40,
119
+ :regional => 0x20,
120
+ :urban => 0x10,
121
+ :bus => 0x08,
122
+ :boat => 0x04,
123
+ :subway => 0x02,
124
+ :tram => 0x01
125
+ }
126
+ include Enumerable
127
+ def initialize(station, opts)
128
+ @station = station
129
+
130
+ transport_types = Array(opts[:include] || TRANSPORT_TYPES.keys) - Array(opts[:exclude])
131
+ filter_num = transport_types.inject(0) {|sum, type| sum + (TRANSPORT_TYPES[type] || 0) }
132
+ filter = sprintf("%09b", filter_num)
133
+ @url_prefix = "http://reiseauskunft.bahn.de/bin/bhftafel.exe/en?input=#{station.id}&productsFilter=#{filter}&start=1&boardType=dep&dateBegin=14.12.08&dateEnd=12.12.09&time=00:00"
134
+ @departure_pages = []
135
+ end
136
+
137
+ def each
138
+ 0.upto(23) do |hour|
139
+ doc = departure_page(hour)
140
+ # find all tr children of table.result which contain a td.train
141
+ # and do not have class 'browse'
142
+ departure_docs = doc / 'table.result tr[td.train]:not(.browse)'
143
+ departure_docs.each do |departure_doc|
144
+ service_link = departure_doc % 'td.train:nth-child(1) a'
145
+ destination_link = departure_doc % 'td.route span.bold a'
146
+ (destination_id, destination_time_string), = destination_link['onclick'].scan(/^sHC\(this, '', '(\d+)','([^']*)'\)/)
147
+ destination_time = ClockTime.parse(destination_time_string)
148
+
149
+ intermediate_stops = (departure_doc % 'td.route').children.find_all{|node| node.text?}.join(' ')
150
+ departure_time = ClockTime.parse((departure_doc % 'td.time').inner_text)
151
+ last_time = departure_time
152
+ days_passed = 0
153
+
154
+ intermediate_stops.scan(/\d+:\d+/) do |time_string|
155
+ time = ClockTime.parse(time_string)
156
+ days_passed += 1 if time < last_time
157
+ last_time = time
158
+ end
159
+ inferred_time_to_destination = days_passed * 24*60*60 + (destination_time - departure_time)
160
+
161
+ platform_td = departure_doc % 'td.platform'
162
+ if platform_td.nil?
163
+ platform = :none
164
+ else
165
+ platform = platform_td.inner_text.strip
166
+ end
167
+
168
+ yield Stop.new(
169
+ :station => @station,
170
+ :service => Service.new(
171
+ :path => service_link['href'].sub(/\?.*/, ''),
172
+ :name => service_link.inner_text.strip.gsub(/\s+/, ' '),
173
+ :destination_info => {
174
+ :station => Station.new(
175
+ :id => destination_id,
176
+ :name => destination_link.inner_text.strip
177
+ ),
178
+ :arrival_time => destination_time
179
+ }
180
+ ),
181
+ :departure_time => departure_time,
182
+ :platform => (platform == '' ? :none : platform),
183
+ :inferred_time_to_destination => inferred_time_to_destination
184
+ )
185
+ end
186
+ end
187
+ end
188
+
189
+ # =====
190
+ private
191
+ # =====
192
+ def departure_page(hour)
193
+ @departure_pages[hour] ||= Hpricot(open("#{@url_prefix}%2B#{hour * 60}"))
194
+ end
195
+ end
196
+
197
+ # Represents a scheduled train (or other transportation) service. This is not specific to
198
+ # a particular day's train, but encompasses all trains taking this route at this
199
+ # time of day. When services are subject to daily or seasonal variations, each such variation
200
+ # is considered a Service of its own, even if they share the same service name.
201
+ class Service
202
+ def initialize(opts) # :nodoc:
203
+ @path = opts[:path]
204
+ @name = opts[:name]
205
+ if opts[:origin_info]
206
+ @origin = Stop.new(opts[:origin_info].merge(
207
+ :service => self,
208
+ :arrival_time => :none,
209
+ :arrival_time_from_origin => :none,
210
+ :arrival_time_to_destination => :none,
211
+ :departure_time_from_origin => 0
212
+ ))
213
+ end
214
+ if opts[:destination_info]
215
+ @destination = Stop.new(opts[:destination_info].merge(
216
+ :service => self,
217
+ :arrival_time_to_destination => 0,
218
+ :departure_time => :none,
219
+ :departure_time_from_origin => :none,
220
+ :departure_time_to_destination => :none
221
+ ))
222
+ end
223
+ end
224
+
225
+ # The designated name for this service, if any - e.g. "ICE 692".
226
+ attr_reader :name
227
+
228
+ # Returns an array of Stop objects for all the stops that this service makes.
229
+ def stops
230
+ if !@stops
231
+ stop_docs = traininfo_response_doc / "table.result tr[td.station]"
232
+ last_time = nil
233
+ days_passed = 0
234
+
235
+ origin_departure_time = nil
236
+
237
+ @stops = stop_docs.collect {|stop_doc|
238
+ station_link = stop_doc % 'td.station a'
239
+
240
+ arrival_time = ClockTime.parse((stop_doc % 'td.arrival').inner_text)
241
+ departure_time = ClockTime.parse((stop_doc % 'td.departure').inner_text)
242
+ origin_departure_time ||= departure_time
243
+
244
+ if arrival_time.nil?
245
+ arrival_time_from_origin = :none
246
+ else
247
+ days_passed += 1 if (!last_time.nil? && arrival_time < last_time)
248
+ arrival_time_from_origin = days_passed * 24*60*60 + (arrival_time - origin_departure_time)
249
+ last_time = arrival_time
250
+ end
251
+
252
+ if departure_time.nil?
253
+ departure_time_from_origin = :none
254
+ else
255
+ days_passed += 1 if (!last_time.nil? && departure_time < last_time)
256
+ departure_time_from_origin = days_passed * 24*60*60 + (departure_time - origin_departure_time)
257
+ last_time = departure_time
258
+ end
259
+
260
+ platform = (stop_doc % 'td.platform').inner_text.strip
261
+
262
+ Stop.new(
263
+ :station => Station.new(
264
+ :id => station_link['href'].scan(/&input=[^&]*%23(\d+)&/).first.first,
265
+ :name => station_link.inner_text
266
+ ),
267
+ :service => self,
268
+ :arrival_time => arrival_time || :none,
269
+ :departure_time => departure_time || :none,
270
+ :platform => (platform == '' ? :none : platform),
271
+ :arrival_time_from_origin => arrival_time_from_origin,
272
+ :departure_time_from_origin => departure_time_from_origin
273
+ )
274
+ }
275
+ end
276
+ @stops
277
+ end
278
+
279
+ # Returns the Stop object for the departure station of this service.
280
+ def origin
281
+ @origin ||= stops.first
282
+ end
283
+
284
+ # Returns the Stop object for the destination station of this service.
285
+ def destination
286
+ @destination ||= stops.last
287
+ end
288
+
289
+ def inspect # :nodoc:
290
+ "#<#{self.class} @name=#{@name.inspect} @origin=#{@origin.inspect} @destination=#{@destination.inspect}>"
291
+ end
292
+
293
+ # Returns a hash code which will match for any two Service objects that stop at the same list of
294
+ # stations at the same times. (This is the nearest thing we have to a unique ID, as Deutsche Bahn
295
+ # do not expose unique IDs for services)
296
+ def hash
297
+ stops.collect{|stop| stop.subhash}.hash
298
+ end
299
+
300
+ # Returns an array of strings indicating the features of this train, as listed as 'Comments:' on
301
+ # the Deutsche Bahn website; e.g. "Subject to compulsory reservation", "Sleeping-car". There
302
+ # doesn't seem to be a consistent format for these.
303
+ def features
304
+ if @features.nil?
305
+ tr = (traininfo_response_doc / 'table.remarks tr').find {|tr| (tr % 'th').inner_text == 'Comments:'}
306
+ if tr
307
+ @features = (tr / 'td strong').collect {|elem| elem.inner_text}.uniq
308
+ else
309
+ @features = []
310
+ end
311
+ end
312
+ @features
313
+ end
314
+
315
+ # =====
316
+ private
317
+ # =====
318
+ def traininfo_response_doc
319
+ @traininfo_response_doc ||= Hpricot(open("http://reiseauskunft.bahn.de#{@path}"))
320
+ end
321
+ end
322
+
323
+ # Represents a stop made at a Station by a Service.
324
+ class Stop
325
+ def initialize(opts) # :nodoc:
326
+ # for the following fields, use :none to mean none supplied (as opposed to not fetched yet):
327
+ # @arrival_time, @departure_time, @platform, @arrival_time_from_origin, @departure_time_from_origin
328
+
329
+ @station = opts[:station] # required
330
+ @service = opts[:service] # required
331
+ @arrival_time = opts[:arrival_time]
332
+ @departure_time = opts[:departure_time] # arrival_time or departure_time is required
333
+ @platform = opts[:platform]
334
+ @arrival_time_from_origin = opts[:arrival_time_from_origin]
335
+ @departure_time_from_origin = opts[:departure_time_from_origin]
336
+
337
+ @inferred_time_to_destination = opts[:inferred_time_to_destination]
338
+
339
+ # these can be calculated from arrival_time_from_origin and departure_time_from_origin
340
+ # (but require service.origin, and therefore service.stops, to have been looked up)
341
+ @arrival_time_to_destination = opts[:arrival_time_to_destination]
342
+ @departure_time_to_destination = opts[:departure_time_to_destination]
343
+ end
344
+
345
+ # The Station where this stop occurs.
346
+ attr_reader :station
347
+ # The Service making this stop.
348
+ attr_reader :service
349
+
350
+ # The name of the station for this stop (provided as a shortcut for stop.station.name).
351
+ def name
352
+ @station.name
353
+ end
354
+
355
+ # The Stop object for the departure station (provided as a shortcut for stop.service.origin).
356
+ def origin
357
+ @service.origin
358
+ end
359
+
360
+ # The Stop object for the destination station (provided as a shortcut for stop.service.destination).
361
+ def destination
362
+ @service.destination
363
+ end
364
+
365
+ # Returns the platform number or name for this stop, or nil if not specified.
366
+ def platform
367
+ get_full_details if @platform.nil?
368
+ @platform == :none ? nil : @platform
369
+ end
370
+
371
+ # Returns the ClockTime at which the service arrives at this station, or nil if not specified
372
+ # (e.g. because this is the origin station for the service, or is for boarding only).
373
+ def arrival_time
374
+ get_full_details if @arrival_time.nil?
375
+ @arrival_time == :none ? nil : @arrival_time
376
+ end
377
+
378
+ # Returns the ClockTime at which the service leaves at this station, or nil if not specified
379
+ # (e.g. because this is the destination station for the service, or is for alighting only).
380
+ def departure_time
381
+ get_full_details if @departure_time.nil?
382
+ @departure_time == :none ? nil : @departure_time
383
+ end
384
+
385
+ # Returns the time between departing the origin station and arriving at this station,
386
+ # as a number of seconds, or nil if arrival time is not specified.
387
+ def arrival_time_from_origin
388
+ get_full_details if @arrival_time_from_origin.nil?
389
+ @arrival_time_from_origin == :none ? nil : @arrival_time_from_origin
390
+ end
391
+
392
+ # Returns the time between departing the origin station and departing from this station,
393
+ # as a number of seconds, or nil if departure time is not specified.
394
+ def departure_time_from_origin
395
+ get_full_details if @departure_time_from_origin.nil?
396
+ @departure_time_from_origin == :none ? nil : @departure_time_from_origin
397
+ end
398
+
399
+ # Returns the time between arriving at this station and arriving at the destination station,
400
+ # as a number of seconds, or nil if arrival time is not specified.
401
+ def arrival_time_to_destination
402
+ if @arrival_time_to_destination.nil?
403
+ if arrival_time_from_origin == :none
404
+ @arrival_time_to_destination == :none
405
+ else
406
+ @arrival_time_to_destination = @service.destination.arrival_time_from_origin - arrival_time_from_origin
407
+ end
408
+ end
409
+ @arrival_time_to_destination
410
+ end
411
+
412
+ # Returns the time between departing from this station and arriving at the destination station,
413
+ # as a number of seconds, or nil if departure time is not specified.
414
+ def departure_time_to_destination
415
+ if @departure_time_to_destination.nil?
416
+ if departure_time_from_origin == :none
417
+ @departure_time_to_destination == :none
418
+ else
419
+ @departure_time_to_destination = @service.destination.arrival_time_from_origin - departure_time_from_origin
420
+ end
421
+ end
422
+ @departure_time_to_destination
423
+ end
424
+
425
+ # Returns calculated time to destination while minimising additional hits to the Deutsche Bahn website:
426
+ # * if the service timetable has already been fetched, use that;
427
+ # * if nothing relevant has been fetched yet, fetch the service timetable and use that;
428
+ # * if only the station timetable has been fetched, use that. This may result in an inaccurate count -
429
+ # see BUGS in README.txt.
430
+ def inferred_time_to_destination
431
+ @inferred_time_to_destination ||= departure_time_to_destination || arrival_time_to_destination
432
+ end
433
+
434
+ def inspect # :nodoc:
435
+ "#<#{self.class} @time=#{(@departure_time.nil? || @departure_time == :none ? @arrival_time : @departure_time).inspect} @station=#{@station.name.inspect} @destination=#{service.destination.station.name.inspect}>"
436
+ end
437
+
438
+ # Code identifying this stop, which can form part of the hash for the Service.
439
+ # Not quite suitable as a hash for this stop in its own right, as different trains
440
+ # at the same station at the same time will have the same hash...
441
+ def subhash # :nodoc:
442
+ [@station.id, departure_time, arrival_time].hash
443
+ end
444
+
445
+ # =====
446
+ private
447
+ # =====
448
+ def get_full_details
449
+ if @arrival_time and @arrival_time != :none
450
+ stop = @service.stops.find {|stop|
451
+ stop.station.id == @station.id && stop.arrival_time == @arrival_time
452
+ }
453
+ @departure_time = stop.departure_time || :none
454
+ else
455
+ stop = @service.stops.find {|stop|
456
+ stop.station.id == @station.id && stop.departure_time == @departure_time
457
+ }
458
+ @arrival_time = stop.arrival_time || :none
459
+ end
460
+ @platform = stop.platform || :none
461
+ @arrival_time_from_origin = stop.arrival_time_from_origin || :none
462
+ @departure_time_from_origin = stop.departure_time_from_origin || :none
463
+ end
464
+ end
465
+ end
@@ -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,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gasman-bahn
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Westcott
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-17 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.12.2
24
+ version:
25
+ description: A library for accessing train information from Deutsche Bahn in an object-oriented way
26
+ email:
27
+ - matt@west.co.tt
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - History.txt
34
+ - Manifest.txt
35
+ - README.txt
36
+ files:
37
+ - History.txt
38
+ - Manifest.txt
39
+ - README.txt
40
+ - Rakefile
41
+ - lib/bahn.rb
42
+ - test/test_bahn.rb
43
+ has_rdoc: true
44
+ homepage:
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --main
48
+ - README.txt
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project: bahn
66
+ rubygems_version: 1.2.0
67
+ signing_key:
68
+ specification_version: 2
69
+ summary: A library for accessing train information from Deutsche Bahn in an object-oriented way
70
+ test_files:
71
+ - test/test_bahn.rb