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.67';
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.67
13              
14             =cut
15              
16 2     2   87114 use 5.006;
  2         12  
17 2     2   817 use utf8::all;
  2         86189  
  2         11  
18 2     2   3883 use Data::Dumper;
  2         9824  
  2         138  
19 2     2   947 use MIME::Base64;
  2         1176  
  2         111  
20 2     2   925 use Map::Tube::Utils qw(is_valid_color);
  2         72237  
  2         139  
21 2     2   993 use Map::Tube::Exception::MissingStationName;
  2         113002  
  2         76  
22 2     2   1019 use Map::Tube::Exception::InvalidStationName;
  2         4995  
  2         67  
23 2     2   983 use Map::Tube::Exception::InvalidBackgroundColor;
  2         4636  
  2         67  
24 2     2   995 use Map::Tube::Exception::InvalidLineName;
  2         4709  
  2         65  
25 2     2   949 use Map::Tube::Exception::MissingSupportedMap;
  2         4763  
  2         64  
26 2     2   982 use Map::Tube::Exception::FoundUnsupportedMap;
  2         4640  
  2         63  
27 2     2   956 use Map::Tube::CLI::Option;
  2         16  
  2         145  
28             use Module::Pluggable
29 2         20 search_path => [ 'Map::Tube' ],
30             require => 1,
31             inner => 0,
32 2     2   1228 max_depth => 3;
  2         15492  
33              
34 2     2   1373 use Text::ASCIITable;
  2         12095  
  2         109  
35 2     2   21 use Moo;
  2         4  
  2         19  
36 2     2   708 use namespace::autoclean;
  2         3  
  2         19  
37 2     2   139 use MooX::Options;
  2         4  
  2         15  
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             =item * L
213              
214             =back
215              
216             =cut
217              
218             sub BUILD {
219 0     0 0   my ($self) = @_;
220              
221 0           my $plugins = [ plugins ];
222 0           foreach my $plugin (@$plugins) {
223 0           my $key = _map_key($plugin);
224 0 0         if (defined $key) {
225 0           $self->{maps}->{uc($key)} = $plugin->new;
226             }
227             }
228              
229 0           $self->_validate_param;
230             }
231              
232             =head1 METHODS
233              
234             =head2 run()
235              
236             This is the only method provided by the package L. It does not
237             expect any parameter. Here is the code from the supplied C script.
238              
239             use strict; use warnings;
240             use Map::Tube::CLI;
241              
242             Map::Tube::CLI->new_with_options->run;
243              
244             =cut
245              
246             sub run {
247 0     0 1   my ($self) = @_;
248              
249 0           my $start = $self->start;
250 0           my $end = $self->end;
251 0           my $map = $self->map;
252 0           my $line = $self->line;
253 0           my $bgcolor = $self->bgcolor;
254 0           my $map_obj = $self->{maps}->{uc($map)};
255              
256 0 0 0       if ($self->preferred) {
    0          
    0          
257 0           print $map_obj->get_shortest_route($start, $end)->preferred, "\n";
258             }
259             elsif ($self->generate_map) {
260 0           my ($image_file, $image_data);
261              
262 0 0         if (defined $bgcolor) {
263 0           $map_obj->bgcolor($bgcolor);
264             }
265              
266 0 0         if (defined $line) {
267 0           $image_file = sprintf(">%s.png", $line);
268 0           $image_data = $map_obj->as_image($line);
269             }
270             else {
271 0           $image_file = sprintf(">%s.png", $map);
272 0           $image_data = $map_obj->as_image;
273             }
274              
275 0           open(my $IMAGE, $image_file);
276 0           binmode($IMAGE);
277 0           print $IMAGE decode_base64($image_data);
278 0           close($IMAGE);
279             }
280             elsif ($self->line_mappings || $self->line_notes) {
281 0           my ($line_map_table, $line_map_notes) = _prepare_mapping_notes($map_obj, $line);
282              
283 0 0         if ($self->line_mappings) {
284 0           print sprintf("\n=head1 DESCRIPTION\n\n%s Metro Map: %s Line.\n\n", $map, $line);
285 0           print $line_map_table;
286             }
287 0 0         if ($self->line_notes) {
288 0           print _line_notes($map_obj, $map, $line, $line_map_notes);
289             }
290             }
291             else {
292 0           print $map_obj->get_shortest_route($start, $end), "\n";
293             }
294             }
295              
296             #
297             #
298             # PRIVATE METHODS
299              
300             sub _prepare_mapping_notes {
301 0     0     my ($map, $line_name) = @_;
302              
303 0           my $map_table = Text::ASCIITable->new;
304 0           $map_table->setCols('Station Name','Connected To');
305              
306 0           my $stations = $map->get_stations($line_name);
307              
308 0           my @station_names = ();
309 0           foreach my $station (@$stations) {
310 0           push @station_names, $station->name;
311             }
312              
313 0           my $i = 0;
314 0           my $map_notes = {};
315 0           foreach (@station_names) {
316 0           my $a = $station_names[$i];
317 0           my $b = '';
318 0 0         if ($i == 0) {
    0          
319 0           $b = $station_names[$i+1];
320             }
321             elsif ($i == (@station_names-1)) {
322 0           $b = $station_names[$i-1];
323             }
324             else {
325 0           $b = sprintf("%s, %s", $station_names[$i-1], $station_names[$i+1]);
326             }
327              
328 0           $map_table->addRow($a, $b);
329              
330 0           _add_notes($map, $line_name, $map_notes, $a);
331              
332 0           $i++;
333             }
334              
335 0           return ($map_table, $map_notes);
336             }
337              
338             sub _line_notes {
339 0     0     my ($map, $map_name, $line_name, $line_map_notes) = @_;
340              
341 0           my $all_lines = $map->get_lines;
342 0           my $line_package = {};
343 0           foreach my $line (@$all_lines) {
344 0 0         next unless (scalar(@{$line->get_stations}));
  0            
345 0           my $_line_name = $line->name;
346 0 0         next if (uc($line_name) eq uc($_line_name));
347 0           $line_package->{$_line_name} = 1;
348             }
349              
350 0           my $notes = "\n";
351 0           $notes .= "=head1 NOTE\n\n";
352 0           $notes .= "=over 2\n";
353              
354 0           foreach my $station (sort keys %$line_map_notes) {
355 0           my $i = 1;
356 0           my $lines = $line_map_notes->{$station};
357 0           my $_notes .= sprintf("\n=item * The station \"%s\" is also part of\n", $station);
358 0           foreach my $line (@$lines) {
359 0 0         next unless (exists $line_package->{$line});
360 0 0         if ($i == 1) {
361 0           $_notes .= sprintf(" L<%s Line|Map::Tube::%s::Line::%s>\n", $line, $map_name, _guess_package_name($line));
362 0           $i++;
363             }
364             else {
365 0           $_notes .= sprintf(" | L<%s Line|Map::Tube::%s::Line::%s>\n", $line, $map_name, _guess_package_name($line));
366             }
367             }
368 0 0         if ($i > 1) {
369 0           $notes .= $_notes;
370             }
371             }
372              
373 0           $notes .= "\n=back\n";
374              
375 0           return $notes;
376             }
377              
378             sub _guess_package_name {
379 0     0     my ($name) = @_;
380              
381 0           my $_name;
382 0           foreach my $token (split /\s/,$name) {
383 0 0         next if ($token =~ /\&/);
384 0           $_name .= ucfirst(lc($token));
385             }
386              
387 0           return $_name;
388             }
389              
390             sub _add_notes {
391 0     0     my ($map_object, $line_name, $notes, $station_name) = @_;
392              
393 0           my $station_lines = $map_object->get_node_by_name($station_name)->line;
394 0           my $lines = [];
395 0           foreach my $line (@$station_lines) {
396 0           my $_line_name = $line->name;
397 0 0         next if ($_line_name eq $line_name);
398 0           push @$lines, $_line_name;
399             }
400 0 0         return unless (scalar(@$lines));
401              
402 0           $notes->{$station_name} = $lines;
403             }
404              
405             sub _map_key {
406 0     0     my ($name) = @_;
407 0 0         return unless defined $name;
408              
409 0           my $maps = _supported_maps();
410 0           foreach my $map (keys %$maps) {
411 0 0         return $map if ($maps->{$map} eq $name);
412             }
413              
414 0           return;
415             }
416              
417             sub _validate_param {
418 0     0     my ($self) = @_;
419              
420 0           my @caller = caller(0);
421 0 0         @caller = caller(2) if $caller[3] eq '(eval)';
422              
423 0           my $start = $self->start;
424 0           my $end = $self->end;
425 0           my $map = $self->map;
426 0           my $line = $self->line;
427 0           my $bgcolor = $self->bgcolor;
428              
429 0           my $supported_maps = _supported_maps();
430             Map::Tube::Exception::FoundUnsupportedMap->throw({
431             method => __PACKAGE__."::_validate_param",
432             message => "ERROR: Unsupported Map [$map].",
433             filename => $caller[1],
434             line_number => $caller[2] })
435 0 0         unless (exists $supported_maps->{uc($map)});
436              
437             Map::Tube::Exception::MissingSupportedMap->throw({
438             method => __PACKAGE__."::_validate_param",
439             message => "ERROR: Missing Map [$map].",
440             filename => $caller[1],
441             line_number => $caller[2] })
442 0 0         unless (exists $self->{maps}->{uc($map)});
443              
444 0 0         if ($self->generate_map) {
445 0 0 0       if (defined $bgcolor && !(is_valid_color($bgcolor))) {
446 0           Map::Tube::Exception::InvalidBackgroundColor->throw({
447             method => __PACKAGE__."::_validate_param",
448             message => "ERROR: Invalid background Color [$bgcolor].",
449             filename => $caller[1],
450             line_number => $caller[2] });
451             }
452              
453 0 0         if (defined $line) {
454             Map::Tube::Exception::InvalidLineName->throw({
455             method => __PACKAGE__."::_validate_param",
456             message => "ERROR: Invalid Line Name [$line].",
457             filename => $caller[1],
458             line_number => $caller[2] })
459 0 0         unless defined $self->{maps}->{uc($map)}->get_line_by_name($line);
460             }
461             }
462              
463 0 0 0       if ($self->line_mappings || $self->line_notes) {
464 0 0         Map::Tube::Exception::MissingLineName->throw({
465             method => __PACKAGE__."::_validate_param",
466             message => "ERROR: Missing Line Name.",
467             filename => $caller[1],
468             line_number => $caller[2] })
469             unless (defined $line);
470              
471             Map::Tube::Exception::InvalidLineName->throw({
472             method => __PACKAGE__."::_validate_param",
473             message => "ERROR: Invalid Line Name [$line].",
474             filename => $caller[1],
475             line_number => $caller[2] })
476 0 0         unless defined $self->{maps}->{uc($map)}->get_line_by_name($line);
477             }
478              
479 0 0 0       unless ($self->generate_map || $self->line_mappings || $self->line_notes) {
      0        
480 0 0         Map::Tube::Exception::MissingStationName->throw({
481             method => __PACKAGE__."::_validate_param",
482             message => "ERROR: Missing Station Name [start].",
483             filename => $caller[1],
484             line_number => $caller[2] })
485             unless defined $start;
486              
487 0 0         Map::Tube::Exception::MissingStationName->throw({
488             method => __PACKAGE__."::_validate_param",
489             message => "ERROR: Missing Station Name [end].",
490             filename => $caller[1],
491             line_number => $caller[2] })
492             unless defined $end;
493              
494             Map::Tube::Exception::InvalidStationName->throw({
495             method => __PACKAGE__."::_validate_param",
496             message => "ERROR: Invalid Station Name [$start].",
497             filename => $caller[1],
498             line_number => $caller[2] })
499 0 0         unless defined $self->{maps}->{uc($map)}->get_node_by_name($start);
500              
501             Map::Tube::Exception::InvalidStationName->throw({
502             method => __PACKAGE__."::_validate_param",
503             message => "ERROR: Invalid Station Name [$end].",
504             filename => $caller[1],
505             line_number => $caller[2] })
506 0 0         unless defined $self->{maps}->{uc($map)}->get_node_by_name($end);
507             }
508             }
509              
510             sub _supported_maps {
511              
512             return {
513 0     0     'ATHENS' => 'Map::Tube::Athens',
514             'BARCELONA' => 'Map::Tube::Barcelona',
515             'BEIJING' => 'Map::Tube::Beijing',
516             'BERLIN' => 'Map::Tube::Berlin',
517             'BUCHAREST' => 'Map::Tube::Bucharest',
518             'BUDAPEST' => 'Map::Tube::Budapest',
519             'COPENHAGEN' => 'Map::Tube::Copenhagen',
520             'DELHI' => 'Map::Tube::Delhi',
521             'DNIPROPETROVSK' => 'Map::Tube::Dnipropetrovsk',
522             'FRANKFURT' => 'Map::Tube::Frankfurt',
523             'GLASGOW' => 'Map::Tube::Glasgow',
524             'HONGKONG' => 'Map::Tube::Hongkong',
525             'KAZAN' => 'Map::Tube::Kazan',
526             'KHARKIV' => 'Map::Tube::Kharkiv',
527             'KIEV' => 'Map::Tube::Kiev',
528             'KOELNBONN' => 'Map::Tube::KoelnBonn',
529             'KOLKATTA' => 'Map::Tube::Kolkatta',
530             'KUALALUMPUR' => 'Map::Tube::KualaLumpur',
531             'LONDON' => 'Map::Tube::London',
532             'LYON' => 'Map::Tube::Lyon',
533             'MADRID' => 'Map::Tube::Madrid',
534             'MALAGA' => 'Map::Tube::Malaga',
535             'MILAN' => 'Map::Tube::Milan',
536             'MINSK' => 'Map::Tube::Minsk',
537             'MOSCOW' => 'Map::Tube::Moscow',
538             'NUREMBERG' => 'Map::Tube::Nuremberg',
539             'NYC' => 'Map::Tube::NYC',
540             'NANJING' => 'Map::Tube::Nanjing',
541             'NIZHNYNOVGOROD' => 'Map::Tube::NizhnyNovgorod',
542             'NOVOSIBIRSK' => 'Map::Tube::Novosibirsk',
543             'PRAGUE' => 'Map::Tube::Prague',
544             'SAINTPETERSBURG' => 'Map::Tube::SaintPetersburg',
545             'SAMARA' => 'Map::Tube::Samara',
546             'SINGAPORE' => 'Map::Tube::Singapore',
547             'SOFIA' => 'Map::Tube::Sofia',
548             'TBILISI' => 'Map::Tube::Tbilisi',
549             'TOKYO' => 'Map::Tube::Tokyo',
550             'VIENNA' => 'Map::Tube::Vienna',
551             'WARSAW' => 'Map::Tube::Warsaw',
552             'YEKATERINBURG' => 'Map::Tube::Yekaterinburg',
553             };
554             }
555              
556             =head1 AUTHOR
557              
558             Mohammad S Anwar, C<< >>
559              
560             =head1 REPOSITORY
561              
562             L
563              
564             =head1 BUGS
565              
566             Please report any bugs or feature requests to C,
567             or through the web interface at L.
568             I will be notified and then you'll automatically be notified of progress on your
569             bug as I make changes.
570              
571             =head1 SUPPORT
572              
573             You can find documentation for this module with the perldoc command.
574              
575             perldoc Map::Tube::CLI
576              
577             You can also look for information at:
578              
579             =over 4
580              
581             =item * BUG report
582              
583             L
584              
585             =item * AnnoCPAN: Annotated CPAN documentation
586              
587             L
588              
589             =item * CPAN Ratings
590              
591             L
592              
593             =item * Search MetaCPAN
594              
595             L
596              
597             =back
598              
599             =head1 LICENSE AND COPYRIGHT
600              
601             Copyright (C) 2015 - 2022 Mohammad S Anwar.
602              
603             This program is free software; you can redistribute it and / or modify it under
604             the terms of the the Artistic License (2.0). You may obtain a copy of the full
605             license at:
606              
607             L
608              
609             Any use, modification, and distribution of the Standard or Modified Versions is
610             governed by this Artistic License.By using, modifying or distributing the Package,
611             you accept this license. Do not use, modify, or distribute the Package, if you do
612             not accept this license.
613              
614             If your Modified Version has been derived from a Modified Version made by someone
615             other than you,you are nevertheless required to ensure that your Modified Version
616             complies with the requirements of this license.
617              
618             This license does not grant you the right to use any trademark, service mark,
619             tradename, or logo of the Copyright Holder.
620              
621             This license includes the non-exclusive, worldwide, free-of-charge patent license
622             to make, have made, use, offer to sell, sell, import and otherwise transfer the
623             Package with respect to any patent claims licensable by the Copyright Holder that
624             are necessarily infringed by the Package. If you institute patent litigation
625             (including a cross-claim or counterclaim) against any party alleging that the
626             Package constitutes direct or contributory patent infringement,then this Artistic
627             License to you shall terminate on the date that such litigation is filed.
628              
629             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
630             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
631             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
632             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS
633             REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT,
634             INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
635             OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
636              
637             =cut
638              
639             1; # End of Map::Tube::CLI