national-rail 0.1.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.
- data/lib/national-rail.rb +3 -0
- data/lib/national-rail/index.html +13305 -0
- data/lib/national-rail/journey_planner.rb +157 -0
- data/lib/national-rail/live_departure_boards.rb +107 -0
- data/lib/national-rail/station_list.rb +32 -0
- metadata +107 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'hpricot'
|
|
3
|
+
require 'active_support'
|
|
4
|
+
|
|
5
|
+
module NationalRail
|
|
6
|
+
|
|
7
|
+
class JourneyPlanner
|
|
8
|
+
|
|
9
|
+
class HpricotParser < Mechanize::Page
|
|
10
|
+
attr_reader :doc
|
|
11
|
+
def initialize(uri = nil, response = nil, body = nil, code = nil)
|
|
12
|
+
@doc = Hpricot(body)
|
|
13
|
+
super(uri, response, body, code)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class TimeParser
|
|
18
|
+
def initialize(date)
|
|
19
|
+
@date = date
|
|
20
|
+
@last_time = nil
|
|
21
|
+
end
|
|
22
|
+
def time(hours_and_minutes)
|
|
23
|
+
time = parse(hours_and_minutes)
|
|
24
|
+
if @last_time && (time < @last_time)
|
|
25
|
+
@date += 1
|
|
26
|
+
time = parse(hours_and_minutes)
|
|
27
|
+
end
|
|
28
|
+
@last_time = time
|
|
29
|
+
time
|
|
30
|
+
end
|
|
31
|
+
def parse(hours_and_minutes)
|
|
32
|
+
Time.zone.parse("#{hours_and_minutes} #{@date}")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class SummaryRow
|
|
37
|
+
|
|
38
|
+
attr_reader :departure_time, :number_of_changes
|
|
39
|
+
|
|
40
|
+
def initialize(agent, date, departure_time, number_of_changes, link)
|
|
41
|
+
@agent, @date = agent, date
|
|
42
|
+
@departure_time, @number_of_changes, @link = departure_time, number_of_changes, link
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def details
|
|
46
|
+
@agent.transact do
|
|
47
|
+
parse_details(@link.click, @date)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_details(page, date)
|
|
52
|
+
details = {}
|
|
53
|
+
description = (page.doc/"table#journeyLegDetails tbody tr.lastRow td[@colspan=6] div").first.inner_text.gsub(/\s+/, " ").strip
|
|
54
|
+
origins, destinations = (/from (.*) to (.*)/.match(description)[1,2]).map{ |s| s.split(",").map(&:strip) }
|
|
55
|
+
details[:origins], details[:destinations] = origins, destinations
|
|
56
|
+
parser = TimeParser.new(date)
|
|
57
|
+
|
|
58
|
+
origin_code = (page.doc/"td.origin abbr").inner_html.strip
|
|
59
|
+
departs_at = (page.doc/"td.leaving").inner_html.strip
|
|
60
|
+
details[:initial_stop] = {
|
|
61
|
+
:station_code => origin_code,
|
|
62
|
+
:departs_at => parser.time(departs_at)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
details[:stops] = []
|
|
66
|
+
(page.doc/".callingpoints table > tbody > tr").each do |tr|
|
|
67
|
+
if (tr/".calling-points").length > 0
|
|
68
|
+
station_code = (tr/".calling-points > a > abbr").inner_html.strip
|
|
69
|
+
arrives_at = (tr/".arrives").inner_html.strip
|
|
70
|
+
departs_at = (tr/".departs").inner_html.strip
|
|
71
|
+
departs_at = arrives_at if arrives_at.present? && departs_at.blank?
|
|
72
|
+
arrives_at = departs_at if arrives_at.blank? && departs_at.present?
|
|
73
|
+
details[:stops] << {
|
|
74
|
+
:station_code => station_code,
|
|
75
|
+
:arrives_at => parser.time(arrives_at),
|
|
76
|
+
:departs_at => parser.time(departs_at)
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
destination_code = (page.doc/"td.destination abbr").inner_html.strip
|
|
82
|
+
arrives_at = (page.doc/"td.arriving").inner_html.strip
|
|
83
|
+
details[:final_stop] = {
|
|
84
|
+
:station_code => destination_code,
|
|
85
|
+
:arrives_at => parser.time(arrives_at)
|
|
86
|
+
}
|
|
87
|
+
details
|
|
88
|
+
rescue => e
|
|
89
|
+
filename = File.join(File.dirname(__FILE__), "..", "..", "details-error.html")
|
|
90
|
+
File.open(filename, "w") { |f| f.write(page.parser.to_html) }
|
|
91
|
+
raise e
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def initialize
|
|
96
|
+
Time.zone ||= 'London'
|
|
97
|
+
@agent = Mechanize.new
|
|
98
|
+
@agent.pluggable_parser.html = HpricotParser
|
|
99
|
+
@agent.user_agent_alias = "Mac FireFox"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def plan(options = {})
|
|
103
|
+
summary_rows = []
|
|
104
|
+
@agent.get("http://www.nationalrail.co.uk/") do |home_page|
|
|
105
|
+
times_page = home_page.form_with(:action => "http://ojp.nationalrail.co.uk/en/s/planjourney/plan") do |form|
|
|
106
|
+
form["jpState"] = "single"
|
|
107
|
+
form["commandName"] = "journeyPlannerCommand"
|
|
108
|
+
form["from.searchTerm"] = options[:from]
|
|
109
|
+
form["to.searchTerm"] = options[:to]
|
|
110
|
+
form["timeOfOutwardJourney.arrivalOrDeparture"] = "DEPART"
|
|
111
|
+
form["timeOfOutwardJourney.monthDay"] = options[:time].strftime("%d/%m/%Y")
|
|
112
|
+
form["timeOfOutwardJourney.hour"] = options[:time].strftime("%H")
|
|
113
|
+
form["timeOfOutwardJourney.minute"] = options[:time].strftime("%M")
|
|
114
|
+
# form["timeOfReturnJourney.arrivalOrDeparture"] = "DEPART"
|
|
115
|
+
# form["timeOfReturnJourney.monthDay"] = "Today"
|
|
116
|
+
# form["timeOfReturnJourney.hour"] = "15"
|
|
117
|
+
# form["timeOfReturnJourney.minute"] = "0"
|
|
118
|
+
form["viaMode"] = "VIA"
|
|
119
|
+
form["via.searchTerm"] = ""
|
|
120
|
+
form["offSetOption"] = "0"
|
|
121
|
+
form["_reduceTransfers"] = "on"
|
|
122
|
+
form["operatorMode"] = "SHOW"
|
|
123
|
+
form["operator.code"] = ""
|
|
124
|
+
form["_lookForSleeper"] = "on"
|
|
125
|
+
form["_directTrains"] = "on"
|
|
126
|
+
form["_includeOvertakenTrains"] = "on"
|
|
127
|
+
end.click_button
|
|
128
|
+
|
|
129
|
+
if (times_page.doc/"error-message").any?
|
|
130
|
+
raise (times_page.doc/"error-message").first.inner_text.gsub(/\s+/, " ").strip
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
date = Date.parse((times_page.doc/".journey-details span:nth-child(0)").first.inner_text.gsub(/\s+/, " ").gsub(/\+ 1 day/, '').strip)
|
|
134
|
+
|
|
135
|
+
(times_page.doc/"table#outboundJourneyTable > tbody > tr:not(.status):not(.changes)").each do |tr|
|
|
136
|
+
|
|
137
|
+
if (tr.attributes["class"] == "day-heading")
|
|
138
|
+
date = Date.parse((tr/"th > p > span").first.inner_text.strip)
|
|
139
|
+
next
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
departure_time = TimeParser.new(date).time((tr/"td.leaving").inner_text.strip)
|
|
143
|
+
number_of_changes = (tr/"td:nth-child(6)").inner_text.strip
|
|
144
|
+
anchor = (tr/"a[@id^=journeyOption]").first
|
|
145
|
+
link = times_page.links.detect { |l| l.attributes["id"] == anchor["id"] }
|
|
146
|
+
|
|
147
|
+
summary_rows << SummaryRow.new(@agent, date, departure_time, number_of_changes, link)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
summary_rows
|
|
151
|
+
rescue => e
|
|
152
|
+
filename = File.join(File.dirname(__FILE__), "..", "..", "summary-error.html")
|
|
153
|
+
File.open(filename, "w") { |f| f.write(@agent.current_page.parser.to_html) }
|
|
154
|
+
raise e
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'hpricot'
|
|
3
|
+
|
|
4
|
+
module NationalRail
|
|
5
|
+
|
|
6
|
+
class LiveDepartureBoards
|
|
7
|
+
|
|
8
|
+
class HpricotParser < Mechanize::Page
|
|
9
|
+
attr_reader :doc
|
|
10
|
+
def initialize(uri = nil, response = nil, body = nil, code = nil)
|
|
11
|
+
@doc = Hpricot(body)
|
|
12
|
+
super(uri, response, body, code)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class SummaryRow
|
|
17
|
+
attr_reader :attributes
|
|
18
|
+
def initialize(agent, attributes)
|
|
19
|
+
@agent, @attributes = agent, attributes
|
|
20
|
+
end
|
|
21
|
+
def [](key)
|
|
22
|
+
@attributes[key]
|
|
23
|
+
end
|
|
24
|
+
def details
|
|
25
|
+
page = nil
|
|
26
|
+
@agent.transact do
|
|
27
|
+
page = @attributes[:details][:link].click
|
|
28
|
+
station = nil
|
|
29
|
+
(page.doc/"table.arrivaltable tbody tr").map do |tr|
|
|
30
|
+
tds = (tr/"td")
|
|
31
|
+
tds.unshift(station) if tds.length < 4
|
|
32
|
+
station = tds[0]
|
|
33
|
+
station_url = (station/"a").first.attributes["href"]
|
|
34
|
+
station_code = /station_query\=(.*)$/.match(station_url)[1]
|
|
35
|
+
deparr = (tds[1]/".deparr").remove
|
|
36
|
+
direction = (deparr.inner_text.strip == "Dep") ? "departure" : "arrival"
|
|
37
|
+
{
|
|
38
|
+
:station => {
|
|
39
|
+
:name => (station/"a").inner_text.strip,
|
|
40
|
+
:url => station_url,
|
|
41
|
+
:code => station_code
|
|
42
|
+
},
|
|
43
|
+
:timetabled => {
|
|
44
|
+
:time => tds[1].inner_text.strip,
|
|
45
|
+
:direction => direction
|
|
46
|
+
},
|
|
47
|
+
:expected => {
|
|
48
|
+
:time => tds[2].inner_text.strip
|
|
49
|
+
},
|
|
50
|
+
:actual => {
|
|
51
|
+
:time => tds[3].inner_text.strip
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
rescue => e
|
|
57
|
+
File.open("error.html", "w") { |f| f.write(page.parser.to_html) } if page
|
|
58
|
+
raise e
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def initialize
|
|
63
|
+
@agent = Mechanize.new
|
|
64
|
+
@agent.pluggable_parser.html = HpricotParser
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def summary(options = {})
|
|
68
|
+
summary_url = "http://realtime.nationalrail.co.uk/ldb/sumdep.aspx"
|
|
69
|
+
page = @agent.get(summary_url, "T" => options[:from], "S" => options[:to])
|
|
70
|
+
(page.doc/"table.arrivaltable tbody tr").map do |tr|
|
|
71
|
+
destination = (tr/"td:nth-child(1)")
|
|
72
|
+
destination_url = (destination/"a").first.attributes["href"]
|
|
73
|
+
destination_code = /station_query\=(.*)$/.match(destination_url)[1]
|
|
74
|
+
operator = (tr/"td:nth-child(5)")
|
|
75
|
+
operator_url = (operator/"a").first.attributes["href"]
|
|
76
|
+
operator_code = /atocCode\=(.*)$/.match(operator_url)[1]
|
|
77
|
+
details = (tr/"td:nth-child(6)")
|
|
78
|
+
details_url = (details/"a").first.attributes["href"]
|
|
79
|
+
details_link = page.links.detect { |l| l.attributes["href"] == details_url }
|
|
80
|
+
SummaryRow.new(@agent,
|
|
81
|
+
:destination => {
|
|
82
|
+
:name => destination.inner_text,
|
|
83
|
+
:url => destination_url,
|
|
84
|
+
:code => destination_code
|
|
85
|
+
},
|
|
86
|
+
:platform => (tr/"td:nth-child(2)").inner_text,
|
|
87
|
+
:timetabled_at => (tr/"td:nth-child(3)").inner_text,
|
|
88
|
+
:expected_at => (tr/"td:nth-child(4)").inner_text,
|
|
89
|
+
:operator => {
|
|
90
|
+
:name => operator.inner_text,
|
|
91
|
+
:url => operator_url,
|
|
92
|
+
:code => operator_code
|
|
93
|
+
},
|
|
94
|
+
:details => {
|
|
95
|
+
:url => details_url,
|
|
96
|
+
:link => details_link
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
rescue => e
|
|
101
|
+
File.open("error.html", "w") { |f| f.write(@agent.current_page.parser.to_html) }
|
|
102
|
+
raise e
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'hpricot'
|
|
3
|
+
|
|
4
|
+
module NationalRail
|
|
5
|
+
|
|
6
|
+
class StationList
|
|
7
|
+
|
|
8
|
+
class HpricotParser < Mechanize::Page
|
|
9
|
+
attr_reader :doc
|
|
10
|
+
def initialize(uri = nil, response = nil, body = nil, code = nil)
|
|
11
|
+
@doc = Hpricot(body)
|
|
12
|
+
super(uri, response, body, code)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@agent = Mechanize.new
|
|
18
|
+
@agent.pluggable_parser.html = HpricotParser
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def each
|
|
22
|
+
@agent.get("http://www.nationalrail.co.uk/stations/codes/") do |stations_page|
|
|
23
|
+
(stations_page.doc/"table:nth-child(0) tbody tr").each do |tr|
|
|
24
|
+
name, code = (tr/"td").map { |td| td.inner_text }
|
|
25
|
+
yield(name, code) unless name == "DUMMY TEST"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: national-rail
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 1
|
|
8
|
+
- 0
|
|
9
|
+
version: 0.1.0
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- James Mead
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2010-05-15 00:00:00 +01:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: mechanize
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ~>
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
segments:
|
|
28
|
+
- 1
|
|
29
|
+
- 0
|
|
30
|
+
- 0
|
|
31
|
+
version: 1.0.0
|
|
32
|
+
type: :runtime
|
|
33
|
+
version_requirements: *id001
|
|
34
|
+
- !ruby/object:Gem::Dependency
|
|
35
|
+
name: hpricot
|
|
36
|
+
prerelease: false
|
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ~>
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
segments:
|
|
42
|
+
- 0
|
|
43
|
+
- 8
|
|
44
|
+
- 2
|
|
45
|
+
version: 0.8.2
|
|
46
|
+
type: :runtime
|
|
47
|
+
version_requirements: *id002
|
|
48
|
+
- !ruby/object:Gem::Dependency
|
|
49
|
+
name: activesupport
|
|
50
|
+
prerelease: false
|
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ~>
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
segments:
|
|
56
|
+
- 2
|
|
57
|
+
- 3
|
|
58
|
+
- 5
|
|
59
|
+
version: 2.3.5
|
|
60
|
+
type: :runtime
|
|
61
|
+
version_requirements: *id003
|
|
62
|
+
description:
|
|
63
|
+
email: james@floehopper.org
|
|
64
|
+
executables: []
|
|
65
|
+
|
|
66
|
+
extensions: []
|
|
67
|
+
|
|
68
|
+
extra_rdoc_files: []
|
|
69
|
+
|
|
70
|
+
files:
|
|
71
|
+
- lib/national-rail/index.html
|
|
72
|
+
- lib/national-rail/journey_planner.rb
|
|
73
|
+
- lib/national-rail/live_departure_boards.rb
|
|
74
|
+
- lib/national-rail/station_list.rb
|
|
75
|
+
- lib/national-rail.rb
|
|
76
|
+
has_rdoc: true
|
|
77
|
+
homepage:
|
|
78
|
+
licenses: []
|
|
79
|
+
|
|
80
|
+
post_install_message:
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
segments:
|
|
90
|
+
- 0
|
|
91
|
+
version: "0"
|
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
segments:
|
|
97
|
+
- 0
|
|
98
|
+
version: "0"
|
|
99
|
+
requirements: []
|
|
100
|
+
|
|
101
|
+
rubyforge_project:
|
|
102
|
+
rubygems_version: 1.3.6
|
|
103
|
+
signing_key:
|
|
104
|
+
specification_version: 3
|
|
105
|
+
summary: A Ruby API for the National Rail website
|
|
106
|
+
test_files: []
|
|
107
|
+
|