File Coverage

blib/lib/JSAN/Shell.pm
Criterion Covered Total %
statement 37 39 94.8
branch n/a
condition n/a
subroutine 13 13 100.0
pod n/a
total 50 52 96.1


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