Welcome to the first (?) Sunday Perl Challenge Trip! The challenge will last one week and then points will be calculated. I dont hope someone is mad enough to accept the challange, but I think is more fun this way, instead of posting my code alone. But who knows..
We need to implement nested autocompletion while reading user input: the command A lead to options 1 2 3 and command B to 4 5 etc..
Simple? No: a dread :) Let specify it better. Given the following data:
# dummy items to play with my @animals = (qw( cow camel dog cat )); my @foods = (qw( fish pasture meat )); my @places = (qw( wood sea desert )); # commands my $commands = { select => { completion_list => \@animals, commands => { give => { completion_list => \@foods, }, take_to => { completion_list => \@places, }, }, }, kill => { completion_list => \@animals, } };
the first choices are only select and kill and if you choose select (entering it or using selTAB to autocomplete it) then autocomplete should permit only one item from @animals Once you entered (directly or autocompleting it) an animal then only the two commands give and take_to should be available. And so on.
I used hardcoded keywords commands and completion_list to drive my solution: feel free to use them or not or to change them.
Press TAB for autocompletion or available options autocompletion nested>("kill", "select") + # 'TAB' on empty input shows available commands autocompletion nested>select c("camel", "cat", "cow") + # 'cTAB' shows animals but not 'dog' autocompletion nested>select ca("camel", "cat") + # 'caTAB' shows two animals starting with 'ca' autocompletion nested>select camel ("give", "take_to") + # I press 'mTAB' thae autocomplete in 'camel' + # 'TAB' again for next level commands: give and take_to autocompletion nested>select camel take_to ("desert", "sea", "wood") + # 'tTAB' and 'TAB' again autocompletion nested>select camel take_to desert + # 'desTAB' leads to 'desert' + # ENTER 1 autocompletion nested> + # ENTER 2 exits the user input cycle CHOOSEN: select camel take_to desert + # finally we print the resulting path
My code is ugly enough, but it works. It is full of comments to help you understing what's going on. Some unused code is commented to show possible improvemnts. It has some debug options: 0 (default), 1, 2 that ruins a bit the output but are useful to see the flow.
Avoid to comment my code before the challange ends. Here we are:
#!perl use strict; use warnings; use Data::Diver qw( Dive ); use Data::Dump; # https://www.perlmonks.org/?node_id=1108329 BEGIN { # you can force Term::ReadLine to load the ::Perl # by setting the ENV PERL_RL variable # but you have to do it in a begin block # before the use statement # try to comment next line: probably ::Perl will be loaded # anyway if it is installed # try to set to a non available sub module # see details: https://metacpan.org/pod/Term::ReadLine#ENVIRON +MENT $ENV{PERL_RL}="Perl"; # on win32 systems it ENV TERM is 'dumb' # autocompletion is not possible. # try to comment next line on win32 and watch how does not wor +k # see also http://bvr.github.io/2010/11/term-readline/ $ENV{TERM} = 'not dumb' if $^O eq 'MSWin32'; } use Term::ReadLine; my $term=Term::ReadLine->new("test"); my $debug = 0; # 0 1 2 # Term::ReadLine is a wrapper module: it initilizes an Attribs hasref # internally used to fill in the right configuration. # You can use this hashref to set autompletion manually. As i did here +. # # YOU MUST NOT USE readline::rl_basic_commands DIRECTLY! # # you cannot print anything from hashref returned by $term->Attribs #foreach my $k (keys $term->Attribs){print "$k ${$term->Attribs}{$k}\n +"} # dummy items to play with my @animals = (qw( cow camel dog cat )); my @foods = (qw( fish pasture meat )); my @places = (qw( wood sea desert)); # commands my $commands = { select => { completion_list => \@animals, commands => { give => { completion_list => \@foods, }, take_to => { completion_list => \@places, }, }, }, kill => { completion_list => \@animals, } }; # starting from top level of $commands my $hr = $commands; # first time initialization of autocompletion define_completion_func($hr); print "The Term::ReadLine effective module loaded is: ",$term->ReadLin +e,"\n" if $debug; print "Press TAB for autocompletion or available options\n"; my @choosen; my $prompt = "autocompletion nested>"; while ( defined ( $_ = $term->readline( $prompt ) ) ) { s/\s+//g; if (/^$/){ print "CHOOSEN: ", (join ' ', @choosen),"\n"; last; } } sub define_completion_func { my $cur_hr = shift; # completion_list OR keys of the current HASH my @cur_list = $cur_hr->{completion_list} ? @{$cur_hr->{completion +_list}} : keys %$cur_hr; $term->Attribs->{completion_function} = sub { my ($text, $line, $start) = @_; if ( $debug ){ print "\n\tDEBUG: text, line, start = [$tex +t] [$line] [$start]\n";} # populate results (0 1 or more) my @res = grep { /^$text/i } sort @cur_list ; ##################################### # one result AND defined in the %HASH if ( 1 == scalar @res and defined Dive($cur_hr, $res[0]) ) +{ # change the current pointer to the HASH to what enter +ed $hr = $cur_hr->{ $res[0] }; if ( $debug > 1 ){ print "CHANGING \$hr to: "; dd $hr; + } # save the result push @choosen, $res[0]; # redefine autocmpletion define_completion_func($hr); } ######################################################## # one result ( but NOT defined this result in the HASH ) elsif( 1 == scalar @res ){ # change the current pointer to the HASH to one level +above.. $hr = Dive( $cur_hr, @choosen[0..$#choosen-1] ); # ..to commands $hr = $hr->{commands}; if ( $debug > 1 ){ print "BACK to commands. \$hr: "; d +d $hr; } # save the result push @choosen, $res[0]; # redefine autocmpletion define_completion_func($hr); } ##################################################### # more then one result: print options still available elsif ( scalar @res > 1){ # show available options dd @res; # mimicry of the prompt and the line at this moment print $prompt,(join ' ',@choosen), (@choosen ? " " : " +"), $text; # nothing to return return; } ############# # TAB pressed elsif ( $text eq '' ){ # show available options dd @res; # mimicry of the prompt and the line at this moment print $prompt,(join ' ',@choosen), @choosen ? " " : "" +; # nothing to return return; } # elsif ( 0 == @cur_list ){ # how to tell the while loop to terminate?? # } # RELAXED ?? # extra stuff can be provided ? filling @choosen_relaxed? else{ die "Unknown happened.." } # BACKSPACE ?? modify the line? no... # my $check_line = join ' ',@choosen; # unless ( $check_line =~ /$line/ ){ # print "BACKSPACE!\n"; # dd @choosen; # } if ( $debug > 1 ){ print "RETURNING "; dd @res; } # return results return @res; }; }
Have fun!
L*
|
|---|