File Coverage

blib/lib/Map/Tube/CLI.pm
Criterion Covered Total %
statement 50 193 25.9
branch 0 80 0.0
condition 0 21 0.0
subroutine 17 26 65.3
pod 1 2 50.0
total 68 322 21.1


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