File Coverage

blib/lib/Map/Tube/CLI.pm
Criterion Covered Total %
statement 50 177 28.2
branch 0 70 0.0
condition 0 15 0.0
subroutine 17 26 65.3
pod 1 2 50.0
total 68 290 23.4


line stmt bran cond sub pod time code
1             package Map::Tube::CLI;
2              
3             $Map::Tube::CLI::VERSION = '0.65';
4             $Map::Tube::CLI::AUTHORITY = 'cpan:MANWAR';
5              
6             =head1 NAME
7              
8             Map::Tube::CLI - Command Line Interface for Map::Tube::* map.
9              
10             =head1 VERSION
11              
12             Version 0.65
13              
14             =cut
15              
16 4     4   101260 use 5.006;
  4         36  
17 4     4   1915 use utf8::all;
  4         205538  
  4         24  
18 4     4   9011 use Data::Dumper;
  4         22541  
  4         258  
19 4     4   1939 use MIME::Base64;
  4         2497  
  4         247  
20 4     4   1995 use Map::Tube::Utils qw(is_valid_color);
  4         157985  
  4         295  
21 4     4   1996 use Map::Tube::Exception::MissingStationName;
  4         255852  
  4         154  
22 4     4   1938 use Map::Tube::Exception::InvalidStationName;
  4         11373  
  4         159  
23 4     4   2049 use Map::Tube::Exception::InvalidBackgroundColor;
  4         10786  
  4         159  
24 4     4   2266 use Map::Tube::Exception::InvalidLineName;
  4         10559  
  4         144  
25 4     4   2087 use Map::Tube::Exception::MissingSupportedMap;
  4         10560  
  4         149  
26 4     4   2043 use Map::Tube::Exception::FoundUnsupportedMap;
  4         10719  
  4         137  
27 4     4   1974 use Map::Tube::CLI::Option;
  4         50  
  4         243  
28             use Module::Pluggable
29 4         32 search_path => [ 'Map::Tube' ],
30             require => 1,
31             inner => 0,
32 4     4   2280 max_depth => 3;
  4         35165  
33              
34 4     4   2825 use Text::ASCIITable;
  4         28258  
  4         226  
35 4     4   33 use Moo;
  4         8  
  4         29  
36 4     4   1520 use namespace::autoclean;
  4         11  
  4         41  
37 4     4   314 use MooX::Options;
  4         11  
  4         35  
38             with 'Map::Tube::CLI::Option';
39              
40             =head1 DESCRIPTION
41              
42             It provides simple command line interface to the package consuming L.
43             The distribution contains a script C, using package L.
44              
45             =head1 SYNOPSIS
46              
47             You can list all command line options by giving C<-h> flag.
48              
49             $ map-tube -h
50             USAGE: map-tube [-h] [long options...]
51              
52             --map=String Map name
53             --start=String Start station name
54             --end=String End station name
55             --preferred Show preferred route
56             --generate_map Generate map as image
57             --line=String Line name for map
58             --bgcolor=String Map background color
59             --line_mappings Generate line mappings
60             --line_notes Generate line notes
61              
62             --usage show a short help message
63             -h show a compact help message
64             --help show a long help message
65             --man show the manual
66              
67             =head1 COMMON USAGES
68              
69             =head2 Shortest Route
70              
71             You can also ask for shortest route in London Tube Map as below:
72              
73             $ map-tube --map London --start 'Baker Street' --end 'Wembley Park'
74              
75             Baker Street (Bakerloo, Circle, Hammersmith & City, Jubilee, Metropolitan), Finchley Road (Jubilee, Metropolitan), Wembley Park (Jubilee, Metropolitan)
76              
77             =head2 Preferred Shortest Route
78              
79             Now request for preferred route as below:
80              
81             $ map-tube --map London --start 'Baker Street' --end 'Euston Square' --preferred
82              
83             Baker Street (Circle, Hammersmith & City, Metropolitan), Great Portland Street (Circle, Hammersmith & City, Metropolitan), Euston Square (Circle, Hammersmith & City, Metropolitan)
84              
85             =head2 Generate Full Map
86              
87             To generate entire map, follow the command below:
88              
89             $ map-tube --map Delhi --generate_map
90              
91             In case you want different background color to the map then you can try below:
92              
93             $ map-tube --map Delhi --bgcolor gray --generate_map
94              
95             =head2 Generate Just a Line Map
96              
97             To generate just a particular line map, follow the command below:
98              
99             $ map-tube --map London --line Bakerloo --generate_map
100              
101             In case you want different background color to the map then you can try below:
102              
103             $ map-tube --map London --line DLR --bgcolor yellow --generate_map
104              
105             =head2 Generate Line Mappings
106              
107             $ map-tube --map London --line Bakerloo --line_mappings
108              
109             =head2 Generate Line Notes
110              
111             $ map-tube --map London --line Bakerloo --line_notes
112              
113             =head2 General Error
114              
115             If encountered invalid map or missing map i.e not installed, you get an error
116             message like below:
117              
118             $ map-tube --map xYz --start 'Baker Street' --end 'Euston Square'
119             ERROR: Unsupported Map [xYz].
120              
121             $ map-tube --map Kazan --start 'Baker Street' --end 'Euston Square'
122             ERROR: Missing Map [Kazan].
123              
124             =head1 SUPPORTED MAPS
125              
126             The command line parameter C can take one of the following map names. It is
127             case insensitive i.e. 'London' and 'lOndOn' are the same.
128              
129             You could use L to install the supported maps.Please make
130             sure you have the latest maps when you install.
131              
132             =over 4
133              
134             =item * L
135              
136             =item * L
137              
138             =item * L
139              
140             =item * L
141              
142             =item * L
143              
144             =item * L
145              
146             =item * L
147              
148             =item * L
149              
150             =item * L
151              
152             =item * L
153              
154             =item * L
155              
156             =item * L
157              
158             =item * L
159              
160             =item * L
161              
162             =item * L
163              
164             =item * L
165              
166             =item * L
167              
168             =item * L
169              
170             =item * L
171              
172             =item * L
173              
174             =item * L
175              
176             =item * L
177              
178             =item * L
179              
180             =item * L
181              
182             =item * L
183              
184             =item * L
185              
186             =item * L
187              
188             =item * L
189              
190             =item * L
191              
192             =item * L
193              
194             =item * L
195              
196             =item * L
197              
198             =item * L
199              
200             =item * L
201              
202             =item * L
203              
204             =item * L
205              
206             =item * L
207              
208             =item * L
209              
210             =item * L
211              
212             =back
213              
214             =cut
215              
216             sub BUILD {
217 0     0 0   my ($self) = @_;
218              
219 0           my $plugins = [ plugins ];
220 0           foreach my $plugin (@$plugins) {
221 0           my $key = _map_key($plugin);
222 0 0         if (defined $key) {
223 0           $self->{maps}->{uc($key)} = $plugin->new;
224             }
225             }
226              
227 0           $self->_validate_param;
228             }
229              
230             =head1 METHODS
231              
232             =head2 run()
233              
234             This is the only method provided by the package L. It does not
235             expect any parameter. Here is the code from the supplied C script.
236              
237             use strict; use warnings;
238             use Map::Tube::CLI;
239              
240             Map::Tube::CLI->new_with_options->run;
241              
242             =cut
243              
244             sub run {
245 0     0 1   my ($self) = @_;
246              
247 0           my $start = $self->start;
248 0           my $end = $self->end;
249 0           my $map = $self->map;
250 0           my $line = $self->line;
251 0           my $bgcolor = $self->bgcolor;
252 0           my $map_obj = $self->{maps}->{uc($map)};
253              
254 0 0 0       if ($self->preferred) {
    0          
    0          
255 0           print $map_obj->get_shortest_route($start, $end)->preferred, "\n";
256             }
257             elsif ($self->generate_map) {
258 0           my ($image_file, $image_data);
259              
260 0 0         if (defined $bgcolor) {
261 0           $map_obj->bgcolor($bgcolor);
262             }
263              
264 0 0         if (defined $line) {
265 0           $image_file = sprintf(">%s.png", $line);
266 0           $image_data = $map_obj->as_image($line);
267             }
268             else {
269 0           $image_file = sprintf(">%s.png", $map);
270 0           $image_data = $map_obj->as_image;
271             }
272              
273 0           open(my $IMAGE, $image_file);
274 0           binmode($IMAGE);
275 0           print $IMAGE decode_base64($image_data);
276 0           close($IMAGE);
277             }
278             elsif ($self->line_mappings || $self->line_notes) {
279 0           my ($line_map_table, $line_map_notes) = _prepare_mapping_notes($map_obj, $line);
280              
281 0 0         if ($self->line_mappings) {
282 0           print sprintf("\n=head1 DESCRIPTION\n\n%s Metro Map: %s Line.\n\n", $map, $line);
283 0           print $line_map_table;
284             }
285 0 0         if ($self->line_notes) {
286 0           print _line_notes($map_obj, $map, $line, $line_map_notes);
287             }
288             }
289             else {
290 0           print $map_obj->get_shortest_route($start, $end), "\n";
291             }
292             }
293              
294             #
295             #
296             # PRIVATE METHODS
297              
298             sub _prepare_mapping_notes {
299 0     0     my ($map, $line_name) = @_;
300              
301 0           my $map_table = Text::ASCIITable->new;
302 0           $map_table->setCols('Station Name','Connected To');
303              
304 0           my $stations = $map->get_stations($line_name);
305              
306 0           my @station_names = ();
307 0           foreach my $station (@$stations) {
308 0           push @station_names, $station->name;
309             }
310              
311 0           my $i = 0;
312 0           my $map_notes = {};
313 0           foreach (@station_names) {
314 0           my $a = $station_names[$i];
315 0           my $b = '';
316 0 0         if ($i == 0) {
    0          
317 0           $b = $station_names[$i+1];
318             }
319             elsif ($i == (@station_names-1)) {
320 0           $b = $station_names[$i-1];
321             }
322             else {
323 0           $b = sprintf("%s, %s", $station_names[$i-1], $station_names[$i+1]);
324             }
325              
326 0           $map_table->addRow($a, $b);
327              
328 0           _add_notes($map, $line_name, $map_notes, $a);
329              
330 0           $i++;
331             }
332              
333 0           return ($map_table, $map_notes);
334             }
335              
336             sub _line_notes {
337 0     0     my ($map, $map_name, $line_name, $line_map_notes) = @_;
338              
339 0           my $all_lines = $map->get_lines;
340 0           my $line_package = {};
341 0           foreach my $line (@$all_lines) {
342 0 0         next unless (scalar(@{$line->get_stations}));
  0            
343 0           my $_line_name = $line->name;
344 0 0         next if (uc($line_name) eq uc($_line_name));
345 0           $line_package->{$_line_name} = 1;
346             }
347              
348 0           my $notes = "\n";
349 0           $notes .= "=head1 NOTE\n\n";
350 0           $notes .= "=over 2\n";
351              
352 0           foreach my $station (sort keys %$line_map_notes) {
353 0           my $i = 1;
354 0           my $lines = $line_map_notes->{$station};
355 0           my $_notes .= sprintf("\n=item * The station \"%s\" is also part of\n", $station);
356 0           foreach my $line (@$lines) {
357 0 0         next unless (exists $line_package->{$line});
358 0 0         if ($i == 1) {
359 0           $_notes .= sprintf(" L<%s Line|Map::Tube::%s::Line::%s>\n", $line, $map_name, _guess_package_name($line));
360 0           $i++;
361             }
362             else {
363 0           $_notes .= sprintf(" | L<%s Line|Map::Tube::%s::Line::%s>\n", $line, $map_name, _guess_package_name($line));
364             }
365             }
366 0 0         if ($i > 1) {
367 0           $notes .= $_notes;
368             }
369             }
370              
371 0           $notes .= "\n=back\n";
372              
373 0           return $notes;
374             }
375              
376             sub _guess_package_name {
377 0     0     my ($name) = @_;
378              
379 0           my $_name;
380 0           foreach my $token (split /\s/,$name) {
381 0 0         next if ($token =~ /\&/);
382 0           $_name .= ucfirst(lc($token));
383             }
384              
385 0           return $_name;
386             }
387              
388             sub _add_notes {
389 0     0     my ($map_object, $line_name, $notes, $station_name) = @_;
390              
391 0           my $station_lines = $map_object->get_node_by_name($station_name)->line;
392 0           my $lines = [];
393 0           foreach my $line (@$station_lines) {
394 0           my $_line_name = $line->name;
395 0 0         next if ($_line_name eq $line_name);
396 0           push @$lines, $_line_name;
397             }
398 0 0         return unless (scalar(@$lines));
399              
400 0           $notes->{$station_name} = $lines;
401             }
402              
403             sub _map_key {
404 0     0     my ($name) = @_;
405 0 0         return unless defined $name;
406              
407 0           my $maps = _supported_maps();
408 0           foreach my $map (keys %$maps) {
409 0 0         return $map if ($maps->{$map} eq $name);
410             }
411              
412 0           return;
413             }
414              
415             sub _validate_param {
416 0     0     my ($self) = @_;
417              
418 0           my @caller = caller(0);
419 0 0         @caller = caller(2) if $caller[3] eq '(eval)';
420              
421 0           my $start = $self->start;
422 0           my $end = $self->end;
423 0           my $map = $self->map;
424 0           my $line = $self->line;
425 0           my $bgcolor = $self->bgcolor;
426              
427 0           my $supported_maps = _supported_maps();
428             Map::Tube::Exception::FoundUnsupportedMap->throw({
429             method => __PACKAGE__."::_validate_param",
430             message => "ERROR: Unsupported Map [$map].",
431             filename => $caller[1],
432             line_number => $caller[2] })
433 0 0         unless (exists $supported_maps->{uc($map)});
434              
435             Map::Tube::Exception::MissingSupportedMap->throw({
436             method => __PACKAGE__."::_validate_param",
437             message => "ERROR: Missing Map [$map].",
438             filename => $caller[1],
439             line_number => $caller[2] })
440 0 0         unless (exists $self->{maps}->{uc($map)});
441              
442 0 0         if ($self->generate_map) {
443 0 0 0       if (defined $bgcolor && !(is_valid_color($bgcolor))) {
444 0           Map::Tube::Exception::InvalidBackgroundColor->throw({
445             method => __PACKAGE__."::_validate_param",
446             message => "ERROR: Invalid background Color [$bgcolor].",
447             filename => $caller[1],
448             line_number => $caller[2] });
449             }
450              
451 0 0         if (defined $line) {
452             Map::Tube::Exception::InvalidLineName->throw({
453             method => __PACKAGE__."::_validate_param",
454             message => "ERROR: Invalid Line Name [$line].",
455             filename => $caller[1],
456             line_number => $caller[2] })
457 0 0         unless defined $self->{maps}->{uc($map)}->get_line_by_name($line);
458             }
459             }
460              
461 0 0 0       if ($self->line_mappings || $self->line_notes) {
462 0 0         Map::Tube::Exception::MissingLineName->throw({
463             method => __PACKAGE__."::_validate_param",
464             message => "ERROR: Missing Line Name.",
465             filename => $caller[1],
466             line_number => $caller[2] })
467             unless (defined $line);
468              
469             Map::Tube::Exception::InvalidLineName->throw({
470             method => __PACKAGE__."::_validate_param",
471             message => "ERROR: Invalid Line Name [$line].",
472             filename => $caller[1],
473             line_number => $caller[2] })
474 0 0         unless defined $self->{maps}->{uc($map)}->get_line_by_name($line);
475             }
476              
477 0 0 0       unless ($self->generate_map || $self->line_mappings || $self->line_notes) {
      0        
478 0 0         Map::Tube::Exception::MissingStationName->throw({
479             method => __PACKAGE__."::_validate_param",
480             message => "ERROR: Missing Station Name [start].",
481             filename => $caller[1],
482             line_number => $caller[2] })
483             unless defined $start;
484              
485 0 0         Map::Tube::Exception::MissingStationName->throw({
486             method => __PACKAGE__."::_validate_param",
487             message => "ERROR: Missing Station Name [end].",
488             filename => $caller[1],
489             line_number => $caller[2] })
490             unless defined $end;
491              
492             Map::Tube::Exception::InvalidStationName->throw({
493             method => __PACKAGE__."::_validate_param",
494             message => "ERROR: Invalid Station Name [$start].",
495             filename => $caller[1],
496             line_number => $caller[2] })
497 0 0         unless defined $self->{maps}->{uc($map)}->get_node_by_name($start);
498              
499             Map::Tube::Exception::InvalidStationName->throw({
500             method => __PACKAGE__."::_validate_param",
501             message => "ERROR: Invalid Station Name [$end].",
502             filename => $caller[1],
503             line_number => $caller[2] })
504 0 0         unless defined $self->{maps}->{uc($map)}->get_node_by_name($end);
505             }
506             }
507              
508             sub _supported_maps {
509              
510             return {
511 0     0     'ATHENS' => 'Map::Tube::Athens',
512             'BARCELONA' => 'Map::Tube::Barcelona',
513             'BEIJING' => 'Map::Tube::Beijing',
514             'BERLIN' => 'Map::Tube::Berlin',
515             'BUCHAREST' => 'Map::Tube::Bucharest',
516             'BUDAPEST' => 'Map::Tube::Budapest',
517             'COPENHAGEN' => 'Map::Tube::Copenhagen',
518             'DELHI' => 'Map::Tube::Delhi',
519             'DNIPROPETROVSK' => 'Map::Tube::Dnipropetrovsk',
520             'FRANKFURT' => 'Map::Tube::Frankfurt',
521             'GLASGOW' => 'Map::Tube::Glasgow',
522             'KAZAN' => 'Map::Tube::Kazan',
523             'KHARKIV' => 'Map::Tube::Kharkiv',
524             'KIEV' => 'Map::Tube::Kiev',
525             'KOELNBONN' => 'Map::Tube::KoelnBonn',
526             'KOLKATTA' => 'Map::Tube::Kolkatta',
527             'KUALALUMPUR' => 'Map::Tube::KualaLumpur',
528             'LONDON' => 'Map::Tube::London',
529             'LYON' => 'Map::Tube::Lyon',
530             'MADRID' => 'Map::Tube::Madrid',
531             'MALAGA' => 'Map::Tube::Malaga',
532             'MILAN' => 'Map::Tube::Milan',
533             'MINSK' => 'Map::Tube::Minsk',
534             'MOSCOW' => 'Map::Tube::Moscow',
535             'NUREMBERG' => 'Map::Tube::Nuremberg',
536             'NYC' => 'Map::Tube::NYC',
537             'NANJING' => 'Map::Tube::Nanjing',
538             'NIZHNYNOVGOROD' => 'Map::Tube::NizhnyNovgorod',
539             'NOVOSIBIRSK' => 'Map::Tube::Novosibirsk',
540             'PRAGUE' => 'Map::Tube::Prague',
541             'SAINTPETERSBURG' => 'Map::Tube::SaintPetersburg',
542             'SAMARA' => 'Map::Tube::Samara',
543             'SINGAPORE' => 'Map::Tube::Singapore',
544             'SOFIA' => 'Map::Tube::Sofia',
545             'TBILISI' => 'Map::Tube::Tbilisi',
546             'TOKYO' => 'Map::Tube::Tokyo',
547             'VIENNA' => 'Map::Tube::Vienna',
548             'WARSAW' => 'Map::Tube::Warsaw',
549             'YEKATERINBURG' => 'Map::Tube::Yekaterinburg',
550             };
551             }
552              
553             =head1 AUTHOR
554              
555             Mohammad S Anwar, C<< >>
556              
557             =head1 REPOSITORY
558              
559             L
560              
561             =head1 BUGS
562              
563             Please report any bugs or feature requests to C,
564             or through the web interface at L.
565             I will be notified and then you'll automatically be notified of progress on your
566             bug as I make changes.
567              
568             =head1 SUPPORT
569              
570             You can find documentation for this module with the perldoc command.
571              
572             perldoc Map::Tube::CLI
573              
574             You can also look for information at:
575              
576             =over 4
577              
578             =item * RT: CPAN's request tracker (report bugs here)
579              
580             L
581              
582             =item * AnnoCPAN: Annotated CPAN documentation
583              
584             L
585              
586             =item * CPAN Ratings
587              
588             L
589              
590             =item * Search CPAN
591              
592             L
593              
594             =back
595              
596             =head1 LICENSE AND COPYRIGHT
597              
598             Copyright (C) 2015 - 2019 Mohammad S Anwar.
599              
600             This program is free software; you can redistribute it and / or modify it under
601             the terms of the the Artistic License (2.0). You may obtain a copy of the full
602             license at:
603              
604             L
605              
606             Any use, modification, and distribution of the Standard or Modified Versions is
607             governed by this Artistic License.By using, modifying or distributing the Package,
608             you accept this license. Do not use, modify, or distribute the Package, if you do
609             not accept this license.
610              
611             If your Modified Version has been derived from a Modified Version made by someone
612             other than you,you are nevertheless required to ensure that your Modified Version
613             complies with the requirements of this license.
614              
615             This license does not grant you the right to use any trademark, service mark,
616             tradename, or logo of the Copyright Holder.
617              
618             This license includes the non-exclusive, worldwide, free-of-charge patent license
619             to make, have made, use, offer to sell, sell, import and otherwise transfer the
620             Package with respect to any patent claims licensable by the Copyright Holder that
621             are necessarily infringed by the Package. If you institute patent litigation
622             (including a cross-claim or counterclaim) against any party alleging that the
623             Package constitutes direct or contributory patent infringement,then this Artistic
624             License to you shall terminate on the date that such litigation is filed.
625              
626             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
627             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
628             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
629             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS
630             REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT,
631             INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
632             OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
633              
634             =cut
635              
636             1; # End of Map::Tube::CLI