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