gasman-bahn 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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