tube 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,7 @@ A simple MIT licensed Ruby library to access the status of the London Undergroun
4
4
 
5
5
  == Installation
6
6
 
7
- $ gem install tube --source http://gemcutter.org
7
+ $ gem install tube
8
8
 
9
9
  == Examples
10
10
  require 'tube/status'
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ require 'tube/status'
3
+ require 'terminal_color/ansi'
4
+
5
+ class String
6
+ include Terminal::Color::ANSI
7
+ end
8
+
9
+ LINE_COLORS = {
10
+ :bakerloo => [:bright_white, :yellow_bg],
11
+ :central => [:white, :bright_red_bg],
12
+ :circle => [:blue, :bright_yellow_bg],
13
+ :district => [:bright_white, :green_bg],
14
+ :hammersmithandcity => [:blue, :bright_magenta_bg],
15
+ :jubilee => [:bright_white, :bright_black_bg],
16
+ :metropolitan => [:bright_white, :magenta_bg],
17
+ :northern => [:bright_white, :black_bg],
18
+ :piccadilly => [:bright_white, :blue_bg],
19
+ :victoria => [:bright_white, :bright_blue_bg],
20
+ :waterlooandcity => [:blue, :bright_cyan_bg]
21
+ }
22
+ STATUS_COLOR = [:blue, :bright_white_bg]
23
+ PROBLEM_COLOR = STATUS_COLOR + [:negative]
24
+
25
+ status = Tube::Status.new
26
+
27
+ longest_name = status.lines.map {|l| l.name.length}.max
28
+ longest_status = status.lines.map {|l| l.status.length}.max
29
+ formatted_time = status.updated.strftime("%I:%M%p").downcase.sub(/^0/, "")
30
+
31
+ puts " Live travel news".style(:bold), " Last update: #{formatted_time}", ""
32
+ status.lines.each do |line|
33
+ print " "
34
+ printf(" %-#{longest_name}s ".color(*LINE_COLORS[line.id]), line.name)
35
+ status_color = if line.problem? then PROBLEM_COLOR else STATUS_COLOR end
36
+ printf(" %-#{longest_status}s \n".color(*status_color), line.status)
37
+ end
@@ -1,79 +1,72 @@
1
1
  require 'time'
2
2
  require 'date'
3
3
  require 'rubygems'
4
- require 'hpricot'
4
+ require 'nokogiri'
5
5
 
6
6
  module Tube # :nodoc:
7
7
  module StatusParser # :nodoc:
8
8
  extend self
9
9
 
10
10
  def parse( html_doc )
11
- html_doc.gsub!(" ", " ")
12
- service_board = Hpricot( html_doc ).at( "#service-board" )
11
+ html_doc.gsub!( / /, " " )
12
+ service_board = Nokogiri::HTML( html_doc ).at_css( "#service-board" )
13
13
 
14
- updated_element = service_board.previous_sibling.children.first
14
+ updated_element = service_board.previous_element.at_css( "h2" )
15
15
  updated = parse_updated( updated_element )
16
16
 
17
- lines = service_board.search( "dl#lines dt" ).map do |line_element|
18
- parse_line( line_element )
19
- end
17
+ line_elements = service_board.css( "ul#lines > li.ltn-line" )
18
+ lines = line_elements.map( &method( :parse_line ) )
20
19
 
21
- station_group_elements = service_board.search( "dl#stations dt" )
22
- station_groups = station_group_elements.map do |station_group_element|
23
- parse_station_group( station_group_element )
24
- end
20
+ station_group_elements = service_board.css( "ul#stations > li" )
21
+ station_groups = station_group_elements.map(&method(:parse_station_group))
25
22
 
26
23
  {:updated => updated, :lines => lines, :station_groups => station_groups}
27
24
  end
28
25
 
29
26
  def parse_updated( updated_element )
30
- time_text = updated_element.inner_text.match( /(\d?\d:\d\d)/ )[0]
27
+ time_text = updated_element.content.match( /(\d?\d:\d\d)/ )[0]
31
28
  time_zone = if is_bst? then "+0100" else "+0000" end
32
29
 
33
30
  Time.parse( "#{time_text} #{time_zone}" )
34
31
  end
35
32
 
36
33
  def parse_line( line_element )
37
- name = line_element.inner_text.strip
38
- html_class = line_element.attributes["class"]
39
- status = parse_status( line_element.next_sibling )
34
+ name = line_element.at_css( "h3.ltn-name" ).content
35
+ html_class = line_element["class"].split.first
36
+ status = parse_status( line_element.at_css( "div.status" ) )
40
37
 
41
38
  {:name => name, :html_class => html_class, :status => status}
42
39
  end
43
40
 
44
41
  def parse_status( status_element )
45
- header = status_element.at( "h3" )
42
+ header = status_element.at_css( "h4.ltn-title" )
46
43
 
47
44
  if header
48
- headline = header.inner_text.strip
49
- message = parse_status_message( status_element.search( "div.message p" ) )
45
+ headline = header.content.strip
46
+ message = parse_status_message( status_element.css("div.message > p") )
50
47
  else
51
- headline = status_element.inner_text.strip
48
+ headline = status_element.content.strip
52
49
  end
53
- problem = status_element.attributes["class"] == "problem"
50
+ problem = status_element["class"].split.include?( "problem" )
54
51
 
55
52
  {:headline => headline, :problem => problem, :message => message}
56
53
  end
57
54
 
58
55
  def parse_station_group( station_group_element )
59
- name = station_group_element.inner_text.strip
60
- stations = []
61
-
62
- station_element = station_group_element
63
- while station_element = station_element.next_sibling
64
- if station_element.to_html =~ /^<dd/
65
- stations.push( parse_station( station_element ) )
66
- elsif station_element.to_html =~ /^<dt/
67
- break
68
- end
56
+ name = station_group_element.at_css( "h3" ).content
57
+
58
+ station_elements = station_group_element.css( "ul > li.ltn-station" )
59
+
60
+ stations = station_elements.map do |station_element|
61
+ parse_station( station_element )
69
62
  end
70
63
 
71
64
  {:name => name, :stations => stations}
72
65
  end
73
66
 
74
67
  def parse_station( station_element )
75
- name = station_element.at( "h3" ).inner_text.strip
76
- message = parse_status_message( station_element.search("div.message p") )
68
+ name = station_element.at_css( "h4.ltn-name" ).content.strip
69
+ message = parse_status_message( station_element.css( "div.message > p" ) )
77
70
 
78
71
  {:name => name, :message => message}
79
72
  end
data/test/dummy.html CHANGED
@@ -14,56 +14,125 @@
14
14
  <div id="main-content">
15
15
  <h1>Live travel news</h1>
16
16
 
17
- <h2>Service update at 12:12 <a href="later.html" id="works_later" class="alert" name="works_later">View engineering works planned for <strong>later today</strong></a></h2>
17
+ <div class="hd-row">
18
+ <h2>
19
+ Service update at 12:12
20
+ <a href="later.html" id="works_later" class="alert">
21
+ View engineering works planned for <strong>later today</strong>
22
+ </a>
23
+ </h2>
24
+
25
+ <div id="closures-email">
26
+ <h3>
27
+ <a class="iframe closures-eml-lnk" href="/plannedworks">
28
+ Weekend closures email
29
+ </a>
30
+ </h3>
31
+ <p>Get advance warning of weekend closures</p>
32
+ </div>
33
+ </div>
18
34
  <div id="service-board">
19
- <dl id="lines">
20
- <dt class="central">Central</dt>
21
- <dd>Good service</dd>
22
- <dt class="district">District</dt>
23
- <dd class="problem">
24
- <h3>Part closure</h3>
25
- <div class="message">
26
- <p>Rail replacement bus</p>
27
- <p>Service A: details...</p>
28
- <p><a href="/transform">See how we are transforming the Tube</a></p>
29
- </div>
30
- </dd>
31
- <dt class="waterlooandcity">Waterloo &amp; City</dt>
32
- <dd class="problem">
33
- <h3>Planned closure</h3>
34
- <div class="message">
35
- <p>Closed Sunday.</p>
36
- <p><a href="/transform">See how we are transforming the Tube</a></p>
37
- </div>
38
- </dd>
39
- </dl>
40
- <dl id="stations">
41
- <dt>Closed stations</dt>
42
- <dd>
43
- <h3>Bank</h3>
44
- <div class="message">
45
- <p>Closed due to excessive noise.</p>
46
- <p><a href="/transform">See how we are transforming the Tube</a></p>
47
- </div>
48
- </dd>
49
- <dd>
50
- <h3>Holborn</h3>
51
- <div class="message">
52
- <p>Closed due to fire investigation.</p>
53
- <p>Go to Chancery Lane or Tottenham Court Road instead.</p>
54
- <p><a href="/transform">See how we are transforming the Tube</a></p>
35
+ <h2>Tube lines</h2>
36
+ <ul id="lines">
37
+ <li class="central ltn-line">
38
+ <h3 class="central ltn-name">Central</h3>
39
+ <div class="status">Good service</div>
40
+ </li>
41
+
42
+ <li class="district ltn-line">
43
+ <h3 class="district ltn-name">District</h3>
44
+ <div class="problem status">
45
+ <h4 class="ltn-title">Part closure</h4>
46
+ <div class="message">
47
+ <p>Rail replacement bus</p>
48
+ <p>Service A: details...</p>
49
+ <dl class="linklist">
50
+ <dd>
51
+ <a href="http://journeyplanner.tfl.gov.uk/">Replan your journey</a>
52
+ </dd>
53
+ </dl>
54
+ </div>
55
55
  </div>
56
- </dd>
56
+ </li>
57
57
 
58
- <dt>Station maintenance</dt>
59
- <dd>
60
- <h3>Elephant &amp; Castle</h3>
61
- <div class="message">
62
- <p>Reduced lift service.</p>
63
- <p><a href="/transform">See how we are transforming the Tube</a></p>
58
+ <li class="waterlooandcity ltn-line">
59
+ <h3 class="waterlooandcity ltn-name">Waterloo &amp; City</h3>
60
+ <div class="problem status">
61
+ <h4 class="ltn-title">Planned closure</h4>
62
+ <div class="message">
63
+ <p>Closed Sunday.</p>
64
+ <dl class="linklist">
65
+ <dd>
66
+ <a href="http://journeyplanner.tfl.gov.uk/">Replan your journey</a>
67
+ </dd>
68
+ </dl>
69
+ </div>
64
70
  </div>
65
- </dd>
66
- </dl>
71
+ </li>
72
+ </ul>
73
+
74
+ <h2>Tube stations</h2>
75
+ <ul id="stations">
76
+ <li class="ltn-closed">
77
+ <h3>Closed stations</h3>
78
+ <ul>
79
+ <li class="ltn-station" id="stop-10000xx">
80
+ <h4 class="ltn-name">Bank</h4>
81
+ <div class="message">
82
+ <p>Closed due to excessive noise.</p>
83
+ <dl class="linklist">
84
+ <dd>
85
+ Replan your journey
86
+ <a href="journeyplanner">to</a>
87
+ /
88
+ <a href="http://journeyplanner">from</a>
89
+ this station
90
+ </dd>
91
+ </dl>
92
+ </div>
93
+ </li>
94
+
95
+ <li class="ltn-station" id="stop-10000xx">
96
+ <h4 class="ltn-name">Holborn</h4>
97
+ <div class="message">
98
+ <p>Closed due to fire investigation.</p>
99
+ <p>Go to Chancery Lane or Tottenham Court Road instead.</p>
100
+ <dl class="linklist">
101
+ <dd>
102
+ Replan your journey
103
+ <a href="journeyplanner">to</a>
104
+ /
105
+ <a href="http://journeyplanner">from</a>
106
+ this station
107
+ </dd>
108
+ </dl>
109
+ </div>
110
+ </li>
111
+ </ul>
112
+ </li>
113
+
114
+ <li>
115
+ <h3>Station maintenance</h3>
116
+ <p class="ltn-more-info">(lifts, escalators etc.)</p>
117
+ <ul>
118
+ <li class="ltn-station" id="stop-10000xx">
119
+ <h4 class="ltn-name">Elephant &amp; Castle</h4>
120
+ <div class="message">
121
+ <p>Reduced lift service.</p>
122
+ <dl class="linklist">
123
+ <dd>
124
+ Replan your journey
125
+ <a href="journeyplanner">to</a>
126
+ /
127
+ <a href="http://journeyplanner">from</a>
128
+ this station
129
+ </dd>
130
+ </dl>
131
+ </div>
132
+ </li>
133
+ </ul>
134
+ </li>
135
+ </ul>
67
136
  </div>
68
137
  </div>
69
138
  </div>
@@ -4,24 +4,24 @@ require "#{File.dirname( __FILE__ )}/../lib/tube/status"
4
4
 
5
5
  class TestStatusParser < Test::Unit::TestCase
6
6
  def test_parse_updated
7
- document = Hpricot("<h2>Service update at 18:26</h2>")
8
- element = document.at("h2")
7
+ document = Nokogiri::HTML("<h2>Service update at 18:26</h2>")
8
+ element = document.at_css("h2")
9
9
  result = Tube::StatusParser.parse_updated(element)
10
10
 
11
11
  assert_equal(Time.parse("6:26pm"), result)
12
12
  end
13
13
 
14
14
  def test_parse_updated_with_anchor_in_element
15
- document = Hpricot(%Q{<h2>Service update at 12:12 <a href="/later.html">View engineering works planned for later today</a></h2>})
16
- element = document.at("h2")
15
+ document = Nokogiri::HTML(%Q{<h2>Service update at 12:12 <a href="/later.html">View engineering works planned for later today</a></h2>})
16
+ element = document.at_css("h2")
17
17
  result = Tube::StatusParser.parse_updated(element)
18
18
 
19
19
  assert_equal(Time.parse("12:12pm"), result)
20
20
  end
21
21
 
22
22
  def test_parse_line
23
- document = Hpricot(%Q{<dt class="central">Central</dt> <dd>Good service</dd>})
24
- element = document.at("dt")
23
+ document = Nokogiri::HTML(%Q{<li class="central ltn-line"><h3 class="central ltn-name">Central</h3><div class="status">Good service</div></li>})
24
+ element = document.at_css("li")
25
25
  result = Tube::StatusParser.parse_line(element)
26
26
 
27
27
  assert_equal("Central", result[:name])
@@ -30,8 +30,8 @@ class TestStatusParser < Test::Unit::TestCase
30
30
  end
31
31
 
32
32
  def test_parse_line_with_complex_name
33
- document = Hpricot(%Q{<dt class="waterlooandcity">Waterloo &amp; City</dt> <dd></dd>})
34
- element = document.at("dt")
33
+ document = Nokogiri::HTML(%Q{<li class="waterlooandcity ltn-line"><h3 class="waterlooandcity ltn-name">Waterloo &amp; City</h3><div class="status">Good service</div></li>})
34
+ element = document.at_css("li")
35
35
  result = Tube::StatusParser.parse_line(element)
36
36
 
37
37
  assert_equal("Waterloo & City", result[:name])
@@ -39,32 +39,32 @@ class TestStatusParser < Test::Unit::TestCase
39
39
  end
40
40
 
41
41
  def test_parse_status
42
- document = Hpricot("<dd>Good service</dd>")
43
- element = document.at("dd")
42
+ document = Nokogiri::HTML(%Q{<div class="status">Good service</div>})
43
+ element = document.at_css("div")
44
44
  result = Tube::StatusParser.parse_status(element)
45
45
 
46
46
  assert_equal("Good service", result[:headline])
47
47
  end
48
48
 
49
49
  def test_parse_status_with_problem
50
- document = Hpricot(%Q{<dd class="problem">Part suspended</dd>})
51
- element = document.at("dd")
50
+ document = Nokogiri::HTML(%Q{<div class="problem status">Part suspended</div>})
51
+ element = document.at_css("div")
52
52
  result = Tube::StatusParser.parse_status(element)
53
53
 
54
54
  assert_equal(true, result[:problem])
55
55
  end
56
56
 
57
57
  def test_parse_status_with_header
58
- document = Hpricot(%Q{<dd><h3>Part suspended</h3></dd>})
59
- element = document.at("dd")
58
+ document = Nokogiri::HTML(%Q{<div class="status"><h4 class="ltn-title">Part suspended</h4></div>})
59
+ element = document.at_css("div")
60
60
  result = Tube::StatusParser.parse_status(element)
61
61
 
62
62
  assert_equal("Part suspended", result[:headline])
63
63
  end
64
64
 
65
65
  def test_parse_status_with_message
66
- document = Hpricot(%Q{<dd class="problem"><h3>Part closure</h3><div class="message"><p>engineering works, etc...</p></div></dd>})
67
- element = document.at("dd")
66
+ document = Nokogiri::HTML(%Q{<div class="problem status"><h4 class="ltn-title">Part closure</h4><div class="message"><p>engineering works, etc...</p></div></div>})
67
+ element = document.at_css("div.status")
68
68
  result = Tube::StatusParser.parse_status(element)
69
69
 
70
70
  assert_equal("Part closure", result[:headline])
@@ -73,34 +73,34 @@ class TestStatusParser < Test::Unit::TestCase
73
73
  end
74
74
 
75
75
  def test_parse_status_message
76
- document = Hpricot(%Q{<div><p>engineering works, etc...</p></div>})
77
- elements = document.search("div p")
76
+ document = Nokogiri::HTML(%Q{<div><p>engineering works, etc...</p></div>})
77
+ elements = document.css("div p")
78
78
  result = Tube::StatusParser.parse_status_message(elements)
79
79
 
80
80
  assert_equal("engineering works, etc...", result)
81
81
  end
82
82
 
83
83
  def test_parse_status_message_with_multi_paragraph_message
84
- document = Hpricot(%Q{<div><p>Rail replacement bus</p><p>Service A: details...</p></div>})
85
- elements = document.search("div p")
84
+ document = Nokogiri::HTML(%Q{<div><p>Rail replacement bus</p><p>Service A: details...</p></div>})
85
+ elements = document.css("div p")
86
86
  result = Tube::StatusParser.parse_status_message(elements)
87
87
 
88
88
  assert_equal("Rail replacement bus\nService A: details...", result)
89
89
  end
90
90
 
91
91
  def test_parse_status_message_removes_anchor_from_message
92
- document = Hpricot(%Q{<div><p>Closed Sunday.</p><p><a href="/transform">See how we are transforming the Tube</a></p></div>})
93
- elements = document.search("div p")
92
+ document = Nokogiri::HTML(%Q{<div><p>Closed Sunday.</p><p><a href="/projectsandschemes"><p>Click for further information.</p></a></p></div>})
93
+ elements = document.css("div > p")
94
94
  result = Tube::StatusParser.parse_status_message(elements)
95
95
 
96
96
  assert_equal("Closed Sunday.", result)
97
97
  end
98
98
 
99
99
  def test_parse_station_group
100
- document = Hpricot(%Q{<dt>Closed stations</dt>
101
- <dd><h3>Bank</h3><div class="message"><p>Closed due to excessive noise.</p></div></dd>
102
- <dd><h3>Holborn</h3><div class="message"><p>Closed due to fire investigation.</p></div></dd>})
103
- element = document.at("dt")
100
+ document = Nokogiri::HTML(%Q{<li class="ltn-closed"><h3>Closed stations</h3><ul>
101
+ <li class="ltn-station" id="stop-1000013"><h4 class="ltn-name">Bank</h4><div class="message"><p>Closed due to excessive noise.</p></div></li>
102
+ <li class="ltn-station" id="stop-10000xx"><h4 class="ltn-name">Holborn</h4><div class="message"><p>Closed due to fire investigation.</p></div></li></ul></li>})
103
+ element = document.at_css("li.ltn-closed")
104
104
  result = Tube::StatusParser.parse_station_group(element)
105
105
 
106
106
  assert_equal("Closed stations", result[:name])
@@ -109,8 +109,8 @@ class TestStatusParser < Test::Unit::TestCase
109
109
  end
110
110
 
111
111
  def test_parse_station
112
- document = Hpricot(%Q{<dd><h3>Bank</h3><div class="message"><p>Closed due to excessive noise.</p></div></dd>})
113
- element = document.at("dd")
112
+ document = Nokogiri::HTML(%Q{<li class="ltn-station" id="stop-1000013"><h4 class="ltn-name">Bank</h4><div class="message"><p>Closed due to excessive noise.</p></div></li>})
113
+ element = document.at_css("li")
114
114
  result = Tube::StatusParser.parse_station(element)
115
115
 
116
116
  assert_equal("Bank", result[:name])
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tube
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 3
9
+ version: 0.2.3
5
10
  platform: ruby
6
11
  authors:
7
12
  - Matthew Sadler
@@ -9,19 +14,23 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-03-09 00:00:00 +00:00
17
+ date: 2010-04-14 00:00:00 +01:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
- name: hpricot
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
21
+ name: nokogiri
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
23
- version: 0.8.1
24
- version:
27
+ segments:
28
+ - 1
29
+ - 4
30
+ - 1
31
+ version: 1.4.1
32
+ type: :runtime
33
+ version_requirements: *id001
25
34
  description: A simple Ruby library to access the status of the London Underground network.
26
35
  email: mat@sourcetagsandcodes.com
27
36
  executables: []
@@ -29,7 +38,7 @@ executables: []
29
38
  extensions: []
30
39
 
31
40
  extra_rdoc_files:
32
- - README.txt
41
+ - README.rdoc
33
42
  files:
34
43
  - lib/tube/line.rb
35
44
  - lib/tube/station.rb
@@ -37,7 +46,8 @@ files:
37
46
  - lib/tube/status_parser.rb
38
47
  - test/dummy.html
39
48
  - test/status_parser_test.rb
40
- - README.txt
49
+ - examples/service_board.rb
50
+ - README.rdoc
41
51
  has_rdoc: true
42
52
  homepage: http://github.com/matsadler/tube
43
53
  licenses: []
@@ -45,25 +55,27 @@ licenses: []
45
55
  post_install_message:
46
56
  rdoc_options:
47
57
  - --main
48
- - README.txt
58
+ - README.rdoc
49
59
  require_paths:
50
60
  - lib
51
61
  required_ruby_version: !ruby/object:Gem::Requirement
52
62
  requirements:
53
63
  - - ">="
54
64
  - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
55
67
  version: "0"
56
- version:
57
68
  required_rubygems_version: !ruby/object:Gem::Requirement
58
69
  requirements:
59
70
  - - ">="
60
71
  - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
61
74
  version: "0"
62
- version:
63
75
  requirements: []
64
76
 
65
77
  rubyforge_project:
66
- rubygems_version: 1.3.5
78
+ rubygems_version: 1.3.6
67
79
  signing_key:
68
80
  specification_version: 3
69
81
  summary: Access the status of the London Underground network.