bahn 1.0.2

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.
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