File Coverage

lib/WWW/LiveDepartureBoards.pm
Criterion Covered Total %
statement 63 85 74.1
branch 7 14 50.0
condition n/a
subroutine 13 16 81.2
pod 4 4 100.0
total 87 119 73.1


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             WWW:LiveDepartureBoards - presents an OO interface to the National Rail Live Departure Boards (LDB's)
6             Website (http://www.livedepartureboards.co.uk).
7              
8             =head1 DESCRIPTION
9              
10             Queries and then screenscrapes the LDB's website, making a guess as to what day
11             the given arrival or departure time is on and constructing a DateTime object as
12             part of the details returned. Can also filter by the stations you are interested
13             in.
14              
15             =head1 METHODS
16              
17             =cut
18              
19              
20             package WWW::LiveDepartureBoards;
21              
22 1     1   27314 use strict;
  1         2  
  1         39  
23 1     1   6 use warnings;
  1         2  
  1         28  
24              
25 1     1   1560 use LWP;
  1         79353  
  1         38  
26 1     1   1699 use DateTime;
  1         270374  
  1         82  
27              
28             our $VERSION = '0.03';
29              
30             use constant {
31 1         1154 BASE_DEPARTURE_URL => 'http://www.livedepartureboards.co.uk/ldb/sumdep.aspx?T=',
32             BASE_ARRIVAL_URL => 'http://www.livedepartureboards.co.uk/ldb/sumarr.aspx?T='
33 1     1   15 };
  1         2  
34              
35             # pinched from AlarmClock::Plan, which in turn was pictured from alarmclock/gac
36             sub _split_hours_and_minutes {
37 0     0   0 my $time = shift;
38 0         0 return split(/:/,$time);
39             }
40              
41             # pinched from AlarmClock::Plan, which in turn was pictured from alarmclock/gac
42             sub _convert_hours_and_minutes_to_future_datetime {
43              
44 0     0   0 my $hour = shift;
45 0         0 my $minute = shift;
46              
47 0         0 my $tz = shift;
48              
49 0 0       0 $tz = 'Europe/London' unless defined $tz;
50              
51 0         0 my $now = DateTime->now( time_zone => $tz );
52 0         0 my $datetime = DateTime->new(
53             year => $now->year(),
54             month => $now->month(),
55             day => $now->day(),
56             hour => $hour,
57             minute => $minute,
58             time_zone => $tz,
59             );
60              
61              
62 0         0 my $duration = $datetime - $now; # this makes absolutely no sense
63 0 0       0 if ($duration->is_negative()) {
64 0         0 $datetime->add_duration(DateTime::Duration->new( days => 1 ));
65             }
66              
67 0         0 return $datetime;
68             }
69              
70             sub _content_to_details {
71 4     4   11 my $self = shift;
72 4         9 my $content = shift;
73 4         11 my $other_station_name = shift;
74              
75 4         14 $self->{last_content} = $content;
76              
77 4         6 my @details;
78              
79 4         1471 while ($content =~ m/
80             \
81             \.*?\>(.*?)\<.*?
82             \(.*?)\<.*?
83             \(.*?)\<.*?
84             /sixg) {
85            
86 0         0 my $details = {};
87 0         0 $details->{$other_station_name} = $1;
88 0         0 $details->{time} = $2;
89 0         0 $details->{status} = $3;
90 0         0 $details->{datetime} = _convert_hours_and_minutes_to_future_datetime(_split_hours_and_minutes($details->{time}));
91              
92 0         0 push(@details,$details);
93             }
94              
95 4         22 return @details;
96             }
97              
98             sub _get_content {
99 4     4   8 my $url = shift;
100              
101 4         38 my $ua = LWP::UserAgent->new();
102 4         313724 $ua->agent('Mozilla/5.0');
103              
104 4         343 my $request = HTTP::Request->new(GET => $url);
105              
106 4         26378 my $response = $ua->request($request);
107 4         5050971 my $content = $response->content;
108              
109 4 50       467 die "Error at LDB" if ($content =~ m/We were unable to service your request/);
110              
111 4         529 return $content;
112             }
113              
114              
115             sub _get_lookup_hash {
116 1     1   5 my @elements = @_;
117 1         3 my $lookup_hash = {};
118 1         9 $lookup_hash->{$_} = 1 for (@elements);
119 1         5 return $lookup_hash;
120             }
121              
122             sub _lookup_either {
123 2     2   3 my $self = shift;
124              
125 2         5 my $filter_list = shift;
126 2         5 my $base_url = shift;
127 2         4 my $other_station_name = shift;
128              
129 2         17 my @details = $self->_content_to_details(_get_content($base_url.$self->{station_code}),$other_station_name);
130              
131             #warn scalar(@details).' details got before filtration';
132              
133 2 100       9 if (defined($filter_list)) {
134             #warn 'doing filtration';
135 1         9 my $lookup_hash=_get_lookup_hash(@$filter_list);
136 1         4 @details = grep { exists($lookup_hash->{$_->{destination}}) } @details;
  0         0  
137             }
138              
139             #warn scalar(@details).' details after filtration';
140              
141 2         14 return @details;
142             }
143              
144             sub _lookup_destination {
145 2     2   4 my $self = shift;
146 2         5 my $destination = shift;
147 2         4 my $base_url = shift;
148            
149 2         16 my @details = $self->_content_to_details(_get_content($base_url.$self->{station_code}.'&S='.$destination),$destination);
150              
151 2         17 return @details;
152             }
153              
154             =head2 new({station_code => 'XXX'})
155              
156             Takes a 3 letter station code such as PNE (Penge East) and returns the
157             corresponding object. You can find out what your local station's code
158             is by visiting the website mentioned above.
159              
160             =cut
161              
162             sub new {
163 3     3 1 5644 my $class = shift;
164              
165 3         9 my $self = shift;
166              
167 3         9 for (qw(station_code)) {
168 3 50       17 die "Mandatory parameter '$_' doesn't exist" unless exists($self->{$_});
169             }
170              
171 3         14 return bless $self, $class;
172             }
173              
174             =head2 arrivals(['Station Name'])
175              
176             Returns an array of hashes with arrival details as follows,
177              
178             origin - the origin of the train
179             time - time in the form of 'hh:mm'
180             datetime - a DateTime object that has been tied to the best guess of
181             what day the train arrives/departs on
182             status - the status of the train
183              
184             Also a reference to a list can be supplied that will act as a filter.
185              
186             =cut
187              
188             sub arrivals {
189 0     0 1 0 my $self = shift;
190              
191 0         0 my $filter_list = shift;
192              
193 0         0 return $self->_lookup_either($filter_list,BASE_ARRIVAL_URL,'origin');
194             }
195              
196             =head2 departures(['Station Name'])
197              
198             Returns an array of hashes with departure details as follows,
199              
200             destination - the origin of the train
201             time - time in the form of 'hh:mm'
202             datetime - a DateTime object that has been tied to the best guess of
203             what day the train arrives/departs on
204             status - the status of the train
205              
206             Also a reference to a list can be supplied that will act as a filter.
207              
208             =cut
209              
210             sub departures {
211 2     2 1 1131 my $self = shift;
212              
213 2         4 my $filter_list = shift;
214              
215 2         10 return $self->_lookup_either($filter_list,BASE_DEPARTURE_URL,'destination');
216             }
217              
218             =head2 destination({station_code => 'XXX'})
219              
220             Returns an array of hashes with departure details as follows,
221              
222             station_code - the final destination name of the train
223             time - time in the form of 'hh:mm'
224             datetime - a DateTime object that has been tied to the best guess of
225             what day the train arrives/departs on
226             status - the status of the train
227              
228             =cut
229              
230             sub destination {
231 2     2 1 33 my $self = shift;
232 2         4 my $destination = shift;
233              
234 2 100       11 if (ref $destination eq 'HASH') {
235 1         9 $destination = $destination->{station_code};
236             }
237 2         7 $destination = uc($destination);
238              
239 2 50       7 die "You MUST provide a destination station.\n" unless $destination;
240              
241 2         10 return $self->_lookup_destination($destination, BASE_DEPARTURE_URL, 'destination');
242             }
243              
244             =head1 AUTHOR
245              
246             Greg McCarroll
247             Adam Trickett
248              
249             =head1 COPYRIGHT
250              
251             Copyright 2005-2007 Greg McCarroll. All Rights Reserved.
252              
253             This program is free software; you can redistribute it
254             and/or modify it under the same terms as Perl itself.
255              
256             =head1 SEE ALSO
257              
258             L
259              
260             =cut
261              
262             1;
263