File Coverage

blib/lib/JSAN/Shell.pm
Criterion Covered Total %
statement 61 296 20.6
branch 6 92 6.5
condition 2 12 16.6
subroutine 15 64 23.4
pod 0 48 0.0
total 84 512 16.4


line stmt bran cond sub pod time code
1             package JSAN::Shell;
2              
3             =pod
4              
5             =head1 NAME
6              
7             JSAN::Shell - JavaScript Archive Network Client Shell
8              
9             =head1 DESCRIPTION
10              
11             C provides command handling and dispatch for the L
12             user application. It interprates these commands and provides the
13             appropriate instructions to the L and L
14             APIs to download and install JSAN modules.
15              
16             =head2 Why Do A New Shell?
17              
18             The JavaScript Archive Network, like its predecessor CPAN, is a large
19             system with quite a number of different parts.
20              
21             In an effort to have a usable repository up, running and usable as
22             quickly as possible, some systems (such as the original JSAN shell)
23             were built with the understanding that they would be replaced by lighter,
24             more scalable and more comprehensive (but much slower to write)
25             replacements eventually.
26              
27             C represents the rewrite of the end-user JSAN shell
28             component, with L providing the seperate and more
29             general programmatic client interface.
30              
31             =head1 METHODS
32              
33             =cut
34              
35 2     2   15586 use 5.006;
  2         5  
36 2     2   7 use strict;
  2         2  
  2         28  
37 2     2   6 use warnings;
  2         8  
  2         55  
38 2     2   875 use Params::Util qw{ _IDENTIFIER _INSTANCE };
  2         5736  
  2         110  
39 2     2   924 use Term::ReadLine ();
  2         7349  
  2         41  
40 2     2   810 use File::HomeDir ();
  2         8458  
  2         36  
41 2     2   902 use File::ShareDir ();
  2         9752  
  2         35  
42 2     2   862 use File::UserConfig ();
  2         11124  
  2         37  
43 2     2   865 use Mirror::JSON ();
  2         122864  
  2         42  
44 2     2   808 use LWP::Online ();
  2         1707  
  2         30  
45 2     2   775 use JSAN::Index ();
  2         58609  
  2         39  
46 2     2   769 use JSAN::Client ();
  2         18441  
  2         93  
47              
48             our $VERSION = '2.00_07';
49             $VERSION = eval $VERSION;
50              
51             # Locate the starting mirror.json
52 2         9 use constant MIRROR_INIT => File::ShareDir::dist_dir(
53             'JSAN-Shell'
54 2     2   14 );
  2         2  
55              
56              
57              
58              
59              
60             #####################################################################
61             # Constructor
62              
63             sub new {
64 1     1 0 8 my $class = shift;
65 1         3 my %params = @_;
66              
67             # Find a terminal to use
68 1   33     23 my $term = _INSTANCE($params{term}, 'Term::Readline') # Use an explicitly passed terminal...
69             || $Term::ReadLine::Perl::term # ... or an existing terminal...
70             || Term::ReadLine->new('JSAN Shell'); # ... or create a new one.
71              
72             # Create the actual object
73 1         34319 my $self = bless {
74             prompt => 'jsan> ',
75             term => $term,
76             client => undef, # Create this later
77             configdir => File::UserConfig->configdir,
78             config => undef,
79             }, $class;
80              
81             # Are we online?
82 1 50       1358 unless ( $self->{config}->{offline} ) {
83 1         7 $self->_print("Checking for Internet access...");
84 1 50       7 unless ( LWP::Online::online('http') ) {
85 0         0 $self->{config}->{offline} = 1;
86             }
87             }
88              
89             # Shortcut if offline
90 1 50       240588 if ( $self->{config}->{offline} ) {
91 0         0 $self->_print("No direct access, offline mode enabled.");
92 0         0 return $self;
93             }
94              
95             # Locate the best mirror
96 1 50       4 unless ( $self->{config}->{mirror} ) {
97 1         49 $self->_print("Locating closest JSAN mirror...");
98 1         21 my $mirror_yaml = Mirror::JSON->read( MIRROR_INIT );
99 1         15557 my @mirrors = $mirror_yaml->mirrors;
100 1         34 my $mirror = $mirrors[ int rand scalar @mirrors ];
101 1         11 $self->{config}->{mirror} = $mirror;
102             }
103              
104             # Initialize the Index layer
105 1 50 33     12 if (JSAN::Index->self && $self->{config}->{mirror}) {
    50          
106 0         0 Carp::croak("Cannot re-initialize JSAN::Index with different mirror url");
107             } elsif (not JSAN::Index->self) {
108             JSAN::Index->init(
109             verbose => $self->{config}->{verbose},
110             mirror_remote => $self->{config}->{mirror},
111 1         19 );
112             }
113              
114 1         1101075 $self;
115             }
116              
117             sub term {
118 0     0 0 0 $_[0]->{term};
119             }
120              
121             sub prompt {
122 0     0 0 0 $_[0]->{prompt};
123             }
124              
125             # Get or create the JSAN::Client object for the shell
126             sub client {
127 0     0 0 0 my $self = shift;
128             $self->{client} or
129 0 0       0 $self->{client} = JSAN::Client->new( %{$self->{config}} );
  0         0  
130             }
131              
132             sub prefix {
133 0     0 0 0 $_[0]->{config}->{prefix};
134             }
135              
136             sub mirror {
137 0     0 0 0 $_[0]->{config}->{mirror};
138             }
139              
140             sub verbose {
141 0     0 0 0 my $self = shift;
142 0         0 my $config = $self->{config};
143            
144 0 0       0 return $config->{verbose} unless @_;
145            
146 0         0 $config->{verbose} = shift;
147 0         0 $self->client->verbose($config->{verbose});
148             }
149              
150             sub offline {
151 0     0 0 0 $_[0]->{config}->{offline};
152             }
153              
154              
155              
156              
157              
158             #####################################################################
159             # JSAN::Shell Main Methods
160              
161             sub run {
162 0     0 0 0 local $| = 1;
163 0         0 my $self = shift;
164 0         0 $self->execute('help motd');
165 0         0 while ( defined(my $cmd_line = $self->term->readline($self->prompt)) ) {
166 0         0 $cmd_line = $self->_clean($cmd_line);
167 0 0       0 next unless length($cmd_line);
168 0         0 eval { $self->execute($cmd_line) };
  0         0  
169 0 0       0 if ( $@ ) {
170 0         0 warn "$@\n";
171             } else {
172 0         0 $self->term->addhistory($cmd_line);
173             }
174             }
175             }
176              
177             # Execute a single command
178             sub execute {
179 0     0 0 0 my $self = shift;
180 0         0 my $line = shift;
181 0         0 my %options = (
182             force => 0,
183             );
184              
185             # Split and find the command
186 0         0 my @words = split /[ ]/, $line;
187 0         0 my $word = shift(@words);
188 0         0 my $cmd = $self->resolve_command($word);
189 0 0       0 unless ( $cmd ) {
190 0         0 return $self->_show("Unknown command '$word'. Type 'help' for a list of commands");
191             }
192              
193             # Is the command implemented
194 0         0 my $method = "command_$cmd";
195 0 0       0 unless ( $self->can($method) ) {
196 0         0 return $self->_show("The command '$cmd' is not currently implemented");
197             }
198              
199             # Hand off to the specific command
200 0         0 $options{params} = \@words;
201 0         0 $self->$method( %options );
202             }
203              
204              
205              
206              
207              
208             #####################################################################
209             # General Commands
210              
211             sub command_quit {
212 0     0 0 0 my $self = shift;
213 0         0 $self->_show('K TNX BYE!!!');
214 0         0 exit(0);
215             }
216              
217             sub command_help {
218 0     0 0 0 my $self = shift;
219 0         0 my %args = @_;
220 0         0 my @params = @{$args{params}};
  0         0  
221              
222             # Get the command to show help for
223 0   0     0 my $command = $params[0] || 'commands';
224 0         0 my $method = "help_$command";
225              
226 0 0       0 return $self->can($method)
227             ? $self->_show($self->$method())
228             : $self->_show("No help page for command '$command'");
229             }
230              
231              
232              
233              
234              
235             #####################################################################
236             # Investigation
237              
238 0     0 0 0 sub help_a { shift->help_author }
239 0     0 0 0 sub help_author { <<'END_HELP' }
240             jsan> author adamk
241            
242             Author ID = adamk
243             Name: Adam Kennedy
244             Email: jsan@ali.as
245             Website: http://ali.as/
246              
247             The "author" command is used to to locate an author and information
248             about them.
249              
250             It takes a single argument which should be the full JSAN identifier
251             for the author.
252             END_HELP
253              
254             sub command_author {
255 0     0 0 0 my $self = shift;
256 0         0 my %args = @_;
257 0         0 my @params = @{$args{params}};
  0         0  
258 0 0       0 my $name = lc _IDENTIFIER($params[0])
259             or return $self->_show("Not a valid author identifier");
260              
261             # Find the author
262 0         0 my $author = JSAN::Index::Author->retrieve( login => $name );
263 0 0       0 unless ( $author ) {
264 0         0 return $self->_show("Could not find the author '$name'");
265             }
266              
267 0         0 $self->show_author( $author );
268             }
269              
270 0     0 0 0 sub help_d { shift->help_dist }
271 0     0 0 0 sub help_dist { <<'END_HELP' }
272             jsan> dist JSAN
273            
274             Distribution = JSAN
275             Latest Release = /dist/c/cw/cwest/JSAN-0.10.tar.gz
276             Version: 0.10
277             Created: Tue Jul 26 17:26:35 2005
278             Author: cwest
279             Name: Casey West
280             Email: casey@geeknest.com
281             Website:
282             Library: JSAN 0.10
283            
284             The "dist" command is used to fetch information about a Distribution,
285             including the current release package, the author, and what Libraries
286             are contained in it.
287            
288             The dist command takes a single argument which should be the full name
289             of the distribution.
290            
291             In the JSAN, a Distribution represents an overall product/release-series.
292             Each distribution will have one or more Release package, which are the
293             actual archive files in the repository, and each distribution contains
294             one more more Library, which are the actual classes and APIs.
295            
296             For various reasons, it is occasionally necesary for a Library to move
297             from one Distribution to another. For this reason, most of the time
298             operations (such as installation) are done at the Library level, and the
299             JSAN client will automatically determine which Distribution (and thus
300             which Release package) to install.
301            
302             However, for cases when you do need information about the actual
303             Distribution, this command is made available.
304             END_HELP
305              
306             sub command_dist {
307 0     0 0 0 my $self = shift;
308 0         0 my %args = @_;
309 0         0 my @params = @{$args{params}};
  0         0  
310 0         0 my $name = $params[0];
311              
312             # Find the author
313 0         0 my $dist = JSAN::Index::Distribution->retrieve( name => $name );
314 0 0       0 unless ( $dist ) {
315 0         0 return $self->_show("Could not find the distribution '$name'");
316             }
317              
318 0         0 $self->show_dist( $dist );
319             }
320              
321 0     0 0 0 sub help_l { shift->help_library(@_) }
322 0     0 0 0 sub help_library { <<'END_HELP' }
323             jsan> library Test.Simple
324            
325             Library = Test.Simple
326             Version: 0.20
327             In Distribution = Test.Simple
328             Latest Release = /dist/t/th/theory/Test.Simple-0.20.tar.gz
329             Version: 0.20
330             Created: Thu Aug 18 04:09:19 2005
331             Author: theory
332             Name: David Wheeler
333             Email: david@justatheory.com
334             Website: http://www.justatheory.com/
335             Library: Test.Builder 0.20
336             Library: Test.Harness 0.20
337             Library: Test.Harness.Browser 0.20
338             Library: Test.Harness.Director 0.20
339             Library: Test.More 0.20
340             Library: Test.Simple 0.20
341              
342             The "library" command is used to fetch information about a Library,
343             including the distribution that contains it, the current release, and
344             any other libraries that will be installed at the same time as the one
345             you are searching for.
346            
347             The library command takes a single argument which should be the full name
348             of the library/class.
349            
350             In the JSAN, a Library represents a single JavaScript prototype/class,
351             and most actions are Library-oriented.
352            
353             For various reasons, it is occasionally necesary for a Library to move
354             from one Distribution to another. For this reason, most of the time
355             operations (such as installation) are done at the Library level, and the
356             JSAN client will automatically determine which Distribution (and thus
357             which Release package) to install.
358             END_HELP
359              
360             sub command_library {
361 0     0 0 0 my $self = shift;
362 0         0 my %args = @_;
363 0         0 my @params = @{$args{params}};
  0         0  
364 0         0 my $name = $params[0];
365              
366             # Find the library
367 0         0 my $library = JSAN::Index::Library->retrieve( name => $name );
368 0 0       0 unless ( $library ) {
369 0         0 return $self->_show("Could not find the library '$name'");
370             }
371              
372 0         0 $self->show_library( $library );
373             }
374              
375 0     0 0 0 sub help_f { shift->help_find(@_) }
376 0     0 0 0 sub help_find { <<'END_HELP' }
377             jsan> f Display.Hide
378              
379             Library: Display.Hide
380             Distribution: Display.Hide
381             Release: /dist/r/ro/rooneg/Display.Hide-0.02.tar.gz
382             Release: /dist/r/ro/rooneg/Display.Hide-0.01.tar.gz
383              
384             Found 4 matching objects in the index
385              
386             The "find" command is an aggregate of the four other search commands
387             ("author", "dist", and "library").
388            
389             If a single index entry is found, the details of the result will be listed
390             in complete detail as per each specific search.
391            
392             If multiple index entries are found, a summary line of all the index
393             entries will be listed.
394             END_HELP
395              
396             sub command_find {
397 0     0 0 0 my $self = shift;
398 0         0 my %args = @_;
399 0         0 my @params = @{$args{params}};
  0         0  
400 0         0 my $name = $params[0];
401 0         0 my $search = "%$name%";
402              
403             # Do the search
404 0         0 my @objects = ();
405 0         0 push @objects, sort JSAN::Index::Author->search_like(
406             login => $search,
407             );
408 0         0 push @objects, sort JSAN::Index::Library->search_like(
409             name => $search,
410             );
411 0         0 push @objects, sort JSAN::Index::Distribution->search_like(
412             name => $search,
413             );
414 0         0 push @objects, sort JSAN::Index::Release->search_like(
415             source => $search,
416             );
417              
418             # Did we find anything?
419 0 0       0 unless ( @objects ) {
420 0         0 return $self->_show( "No objects found of any type like '$name'" );
421             }
422              
423             # If we only found one thing, go directly to it
424 0 0       0 if ( @objects == 1 ) {
425 0 0       0 if ( $objects[0]->isa('JSAN::Index::Author') ) {
    0          
    0          
426 0         0 return $self->show_author( $objects[0] );
427             } elsif ( $objects[0]->isa('JSAN::Index::Distribution') ) {
428 0         0 return $self->show_dist( $objects[0] );
429             } elsif ( $objects[0]->isa('JSAN::Index::Release') ) {
430 0         0 return $self->show_release( $objects[0] );
431             } else {
432 0         0 return $self->show_library( $objects[0] );
433             }
434             }
435              
436             # Show all of the objects
437 0         0 $self->show_list( @objects );
438             }
439              
440 0     0 0 0 sub help_c { shift->help_config(@_) }
441 0     0 0 0 sub help_config { <<'END_HELP' }
442             jsan> config
443              
444             jsan configuration
445             ------------------
446             verbose: disabled
447             offline: disabled
448             mirror: http://master.openjsan.org
449             prefix: (none)
450              
451             The "config" command lists the current configuration settings for the
452             client session.
453             END_HELP
454              
455             sub command_config {
456 0     0 0 0 shift()->show_config;
457             }
458              
459 0     0 0 0 sub help_s { shift->help_config(@_) }
460 0     0 0 0 sub help_set { <<'END_HELP' }
461             jsan> set verbose 1
462              
463             Verbose mode is enabled.
464              
465             The "set" command is used to change the current configuration. It takes
466             the name of a configuration setting, and a value.
467            
468             Changes to configuration currently only apply for the duration of the
469             current session (this will be corrected in future).
470             END_HELP
471              
472             sub command_set {
473 0     0 0 0 my $self = shift;
474 0         0 my %args = @_;
475 0         0 my @params = @{$args{params}};
  0         0  
476 0         0 my $name = shift(@params);
477 0 0       0 unless ( @params ) {
478 0         0 return $self->_show("Did not provide a value to set the option to");
479             }
480              
481             # Handle the valid options
482 0 0       0 if ( _IDENTIFIER($name) ) {
483 0 0       0 return $self->command_set_verbose( @params ) if $name eq 'verbose';
484 0 0       0 return $self->command_set_offline( @params ) if $name eq 'offline';
485 0 0       0 return $self->command_set_mirror( @params ) if $name eq 'mirror';
486 0 0       0 return $self->command_set_prefix( @params ) if $name eq 'prefix';
487             }
488              
489 0         0 $self->_show("Invalid or unknown configuration option '$params[0]'");
490             }
491              
492             sub command_set_verbose {
493 0     0 0 0 my $self = shift;
494 0         0 my $value = shift;
495 0 0       0 if ( $value =~ /^(?:y|yes|t|true|1|on)$/i ) {
    0          
496 0         0 $self->verbose(1);
497 0         0 $self->_show("Verbose mode is enabled.");
498             } elsif ( $value =~ /^(?:n|no|f|false|0|off)$/i ) {
499 0         0 $self->verbose('');
500 0         0 $self->_show("Verbose mode is disabled.");
501             } else {
502 0         0 $self->_show("Unknown verbose mode '$value'. Try 'on' or 'off'");
503             }
504             }
505              
506             sub command_set_offline {
507 0     0 0 0 my $self = shift;
508 0         0 my $value = shift;
509 0 0       0 if ( $value =~ /^(?:y|yes|t|true|1|on)$/i ) {
    0          
510 0         0 $self->{config}->{offline} = 1;
511 0         0 $self->_show("Offline mode is enabled.");
512             } elsif ( $value =~ /^(?:n|no|f|false|0|off)$/i ) {
513 0         0 $self->{config}->{offline} = '';
514 0         0 $self->_show("Offline mode is disabled.");
515             } else {
516 0         0 $self->_show("Unknown offline mode '$value'. Try 'on' or 'off'");
517             }
518             }
519              
520             sub command_set_mirror {
521 0     0 0 0 my $self = shift;
522 0         0 my $value = shift;
523              
524             ### FIXME - Once JSAN::URI works, add validation here
525              
526             # Change the mirror and reset JSAN::Transport
527 0         0 $self->{config}->{mirror} = $value;
528              
529 0         0 $self->_show("mirror changed to '$value'");
530             }
531              
532             sub command_set_prefix {
533 0     0 0 0 my $self = shift;
534              
535             # Check the prefix directory
536 0         0 my $value = glob shift;
537 0 0       0 unless ( -d $value ) {
538 0         0 return $self->_show("The directory '$value' does not exist.");
539             }
540 0 0       0 unless ( -w $value ) {
541 0         0 return $self->_show("You do not have write permissions to '$value'.");
542             }
543              
544             # Change the prefix and flush the client
545 0         0 $self->{config}->{prefix} = $value;
546 0         0 $self->{client} = undef;
547              
548 0         0 $self->_show("prefix changed to '$value'");
549             }
550              
551 0     0 0 0 sub help_p { shift->help_pull(@_) }
552 0     0 0 0 sub help_pull { <<'END_HELP' }
553              
554             END_HELP
555              
556             sub command_pull {
557 0     0 0 0 my $self = shift;
558 0         0 my %args = @_;
559 0         0 my @params = @{$args{params}};
  0         0  
560 0         0 my $name = shift(@params);
561              
562             # Find the library they are refering to
563 0         0 my $library = JSAN::Index::Library->retrieve( name => $name );
564 0 0       0 unless ( $library ) {
565 0         0 return $self->_show("Could not find the library '$name'");
566             }
567              
568             # Mirror the file to local disk
569 0         0 my $path = $library->release->mirror;
570 0         0 $self->_show("Library '$name' downloaded in release file '$path'");
571             }
572              
573             sub command_install {
574 0     0 0 0 my $self = shift;
575 0         0 my %args = @_;
576 0         0 my @params = @{$args{params}};
  0         0  
577 0         0 my $name = shift(@params);
578              
579             # Find the library they are refering to
580 0         0 my $library = JSAN::Index::Library->retrieve( name => $name );
581 0 0       0 unless ( $library ) {
582 0         0 return $self->_show("Could not find the library '$name'");
583             }
584              
585             # Do we have a prefix to install to
586 0 0       0 unless ( $self->prefix ) {
587 0         0 return $self->_show("No install prefix set. Try 'set prefix /install/path'");
588             }
589              
590             # Get the client object and install the package (and it's dependencies)
591 0         0 $self->client->install_library($name);
592             }
593              
594             sub show_author {
595 0     0 0 0 my $self = shift;
596 0         0 my $author = shift;
597 0         0 $self->_show(
598             "Author ID = " . $author->login,
599             " Name: " . $author->name,
600             " Email: " . $author->email,
601             " Website: " . $author->url,
602             );
603             }
604              
605             sub show_dist {
606 0     0 0 0 my $self = shift;
607 0         0 my $dist = shift;
608 0         0 my $release = $dist->latest_release;
609 0         0 my $author = $release->author;
610              
611             # Get the list of libraries in this release.
612             # This only works because we are using the latest release.
613             my @libraries =
614 0         0 sort { $a->name cmp $b->name }
  0         0  
615             JSAN::Index::Library->search( release => $release->id );
616              
617             # Find the max library name length and create the formatting string
618 0         0 my $max = 0;
619 0         0 foreach ( @libraries ) {
620 0 0       0 next if length($_->name) <= $max;
621 0         0 $max = length($_->name);
622             }
623 0         0 my $string = " Library: %-${max}s %s";
624              
625             $self->_show(
626             "Distribution = " . $dist->name,
627             "Latest Release = " . $release->source,
628             " Version: " . $release->version,
629             " Created: " . $release->created_string,
630             " Author: " . $author->login,
631             " Name: " . $author->name,
632             " Email: " . $author->email,
633             " Website: " . $author->url,
634             map {
635 0         0 sprintf( $string, $_->name, $_->version )
  0         0  
636             } @libraries
637             );
638             }
639              
640             sub show_release {
641 0     0 0 0 my $self = shift;
642 0         0 my $release = shift;
643 0         0 my $dist = $release->distribution;
644 0         0 my $author = $release->author;
645              
646 0 0       0 $self->_show(
647             "Release = " . $release->source,
648             " Distribution: " . $dist->name,
649             " Version: " . $release->version,
650             " Created: " . $release->created_string,
651             " Latest Release: " . ($release->latest ? 'Yes' : 'No'),
652             " Author: " . $author->login,
653             " Name: " . $author->name,
654             " Email: " . $author->email,
655             " Website: " . $author->url,
656             );
657             }
658              
659             sub show_library {
660 0     0 0 0 my $self = shift;
661 0         0 my $library = shift;
662 0         0 my $release = $library->release;
663 0         0 my $dist = $release->distribution;
664 0         0 my $author = $release->author;
665              
666             # Get the list of libraries in this release.
667             # This only works because we are using the latest release.
668             my @libraries = sort {
669 0         0 $a->name cmp $b->name
  0         0  
670             } JSAN::Index::Library->search(
671             release => $release->id,
672             );
673              
674             # Find the max library name length and create the formatting string
675 0         0 my $max = 0;
676 0         0 foreach ( @libraries ) {
677 0 0       0 next if length($_->name) <= $max;
678 0         0 $max = length($_->name);
679             }
680 0         0 my $string = " Library: %-${max}s %s";
681              
682             $self->_show(
683             "Library = " . $library->name,
684             " Version: " . $library->version,
685             "In Distribution = " . $dist->name,
686             "Latest Release = " . $release->source,
687             " Version: " . $release->version,
688             " Created: " . $release->created_string,
689             " Author: " . $author->login,
690             " Name: " . $author->name,
691             " Email: " . $author->email,
692             " Website: " . $author->url,
693             map {
694 0         0 sprintf( $string, $_->name, $_->version )
  0         0  
695             } @libraries,
696             );
697             }
698              
699             sub show_list {
700 0     0 0 0 my $self = shift;
701              
702             # Show each one
703 0         0 my @output = ();
704 0         0 foreach my $object ( @_ ) {
705 0 0       0 if ( $object->isa('JSAN::Index::Author') ) {
    0          
    0          
    0          
706 0         0 push @output, sprintf(
707             " Author: %-10s (\"%s\" <%s>)",
708             $object->login,
709             $object->name,
710             $object->email,
711             );
712              
713             } elsif ( $object->isa('JSAN::Index::Distribution') ) {
714 0         0 push @output, sprintf(
715             " Distribution: %s",
716             $object->name,
717             );
718              
719             } elsif ( $object->isa('JSAN::Index::Release') ) {
720 0         0 push @output, sprintf(
721             " Release: %s",
722             $object->source,
723             );
724              
725             } elsif ( $object->isa('JSAN::Index::Library') ) {
726 0         0 push @output, sprintf(
727             " Library: %s",
728             $object->name,
729             );
730             }
731             }
732              
733             # Summary
734 0         0 push @output, "";
735 0         0 push @output, " Found "
736             . scalar(@_)
737             . " matching objects in the index";
738              
739 0         0 $self->_show( @output );
740             }
741            
742             sub show_config {
743 0     0 0 0 my $self = shift;
744 0 0 0     0 $self->_show(
    0 0        
745             " jsan configuration",
746             " ------------------",
747             " verbose: " . ($self->verbose ? 'enabled' : 'disabled'),
748             " offline: " . ($self->offline ? 'enabled' : 'disabled'),
749             " mirror: " . ($self->mirror || '(none)'),
750             " prefix: " . ($self->prefix || '(none)'),
751             );
752             }
753              
754              
755              
756              
757              
758             #####################################################################
759             # Localisation and Content
760              
761             # For a given string, find the command for it
762             my %COMMANDS = (
763             'q' => 'quit',
764             'quit' => 'quit',
765             'exit' => 'quit',
766             'h' => 'help',
767             'help' => 'help',
768             '?' => 'help',
769             'a' => 'author',
770             'author' => 'author',
771             'd' => 'dist',
772             'dist' => 'dist',
773             'distribution' => 'dist',
774             'l' => 'library',
775             'lib' => 'library',
776             'library' => 'library',
777             'f' => 'find',
778             'find' => 'find',
779             'c' => 'config',
780             'conf' => 'config',
781             'config' => 'config',
782             's' => 'set',
783             'set' => 'set',
784             'p' => 'pull',
785             'pull' => 'pull',
786             'i' => 'install',
787             'install' => 'install',
788             'r' => 'readme',
789             'readme' => 'readme',
790             );
791              
792             sub resolve_command {
793 0     0 0 0 $COMMANDS{$_[1]};
794             }
795              
796              
797              
798              
799              
800 0     0 0 0 sub help_usage { <<"END_HELP" }
801             Usage: cpan [-OPTIONS [-MORE_OPTIONS]] [--] [PROGRAM_COMMAND ...]
802              
803             For more details run
804             perldoc -F /usr/bin/jsan
805             END_HELP
806              
807              
808              
809              
810              
811 0     0 0 0 sub help_motd { <<"END_HELP" }
812             jsan shell -- JSAN repository explorer and package installer (v$VERSION)
813             -- Copyright 2005 - 2009 Adam Kennedy.
814             -- Type 'help' for a summary of available commands.
815             END_HELP
816              
817              
818              
819              
820              
821 0     0 0 0 sub help_commands { <<"END_HELP" }
822             ------------------------------------------------------------
823             | Display Information |
824             | ------------------------------------------------------------ |
825             | command | argument | description |
826             | ------------------------------------------------------------ |
827             | a,author | WORD | about an author |
828             | d,dist | WORD | about a distribution |
829             | l,library | WORD | about a library |
830             | f,find | SUBSTRING | all matches from above |
831             | ------------------------------------------------------------ |
832             | Download, Test, Install... |
833             | ------------------------------------------------------------ |
834             | p,pull | WORD | download from the mirror |
835             | i,install | WORD | install (implies get) |
836             | r,readme | WORD | display the README file |
837             | ------------------------------------------------------------ |
838             | Other |
839             | ------------------------------------------------------------ |
840             | h,help,? | | display this menu |
841             | h,help,? | COMMAND | command details |
842             | c,config | | show all config options |
843             | s,set | OPTION, VALUE | set a config option |
844             | q,quit,exit | | quit the jsan shell |
845             ------------------------------------------------------------
846             END_HELP
847              
848              
849              
850              
851              
852             #####################################################################
853             # Support Methods
854              
855             # Clean a single command
856             sub _clean {
857 0     0   0 my ($self, $line) = @_;
858 0         0 $line =~ s/\s+/ /s;
859 0         0 $line =~ s/^\s+//s;
860 0         0 $line =~ s/\s+$//s;
861 0         0 $line;
862             }
863              
864             # Print a single line to screen
865             sub _print {
866 2     2   4 my $self = shift;
867 2         8 while ( @_ ) {
868 2         4 my $line = shift;
869 2         8 chomp($line);
870 2         531 print STDOUT "$line\n";
871             }
872 2         5 1;
873             }
874              
875             # Print something with a leading and trailing blank line
876             sub _show {
877 0     0     my $self = shift;
878 0           $self->_print( '', @_, '' );
879             }
880              
881             1;
882              
883             =pod
884              
885             =head1 AUTHORS
886              
887             Adam Kennedy Eadam@ali.asE
888              
889             =head1 SEE ALSO
890              
891             L, L, L
892              
893             =head1 COPYRIGHT
894              
895             Copyright 2005 - 2009 Adam Kennedy.
896              
897             This module is free software; you can redistribute it and/or modify it
898             under the same terms as Perl itself.
899              
900             =cut