http://qs1969.pair.com?node_id=53067

Here's what I've learned since posting snippet browser. There are numerous improvements based on bbfu's comments, in that thread, but lots of things done for which I hardly know the consequences. Criticism (as well as generosity) would be appreciated. (Trying the READMORE tag - here goes...nothing?)

It's more like a full program now - and I'm pleased to say, it took less time to write this, than it did to write the original version (thanks to obsessively reading all of you ).

N.B. again, that this probably only runs on Linux. Also, as in the previous version, as written it requires a $SANDBOX environment variable, to designate a subdirectory under $HOME.

Thank you for indulging a beginner using you all as a sounding board.

    New stuff revised Sat Jan 20 at 04:16
  1. cleaned up a few things following tilly's advice below (for which I'm very grateful).
  2. Improved subroutines for color.
  3. Change menu colors.
  4. You can now specify the editor, and a subroutine called which looks it up in your $PATH. Defaults to "less" if you enter something bogus, by calling which recursively.
  5. Has a menued console interface - with color!
  6. The menu is hideable.
  7. Arbitrarily select snippets by designating an array index.
  8. Change the Sandbox directory interactively (using a hidden option: type "SANDBOX".)
  9. Display a snippet to the screen or
  10. execute the snippet from the menu.
#!/usr/bin/perl -w use strict; my $EDITOR = 'elvis'; my $index = @ARGV ? shift(@ARGV) :0; my $clear = 1; my $hide_help = 0; my $homedir = $ENV{HOME}; my $sandbox = $ENV{SANDBOX}; my $STUDYPATH = "$homedir/$sandbox"; $EDITOR = which($EDITOR); my $color = colorize('GREEN'); my $reset = colorize('reset'); while (1){ my @files = glob "$STUDYPATH/*"; system ('clear') unless $clear == 0; $clear++; $index =~ s/[A-Za-z]/0/g; $index = 0 if $index < (0-$#files)-1 || $index > $#files; printf ( "%50s <- (%5s)\n", $files[$index], $index); print " ",$color,"[C]",$reset,"hange editor. Currently $EDITOR ",$color,"[E]",$reset,"dit to open the snippet in an editor ",$color,"[G]",$reset,"oto to select file index: -", ($#files+1)," + to $#files ",$color,"[H]",$reset,"elp to (un)hide this menu ",$color,"[N]",$reset,"ext to skip this snippet ",$color,"[R]",$reset,"edo to revisit the previous snippet co",$color,"[L]",$reset,"or to change the menu item color ",$color,"[Q]",$reset,"UIT to exit ",$color,"[T]",$reset,"ext to change the menu text color e",$color,"[X]",$reset,"ecute Any other key +",$color," <ENTER> ",$reset,"to print out the snipp +et -> " unless $hide_help==1; my $input = <>; if ($input =~ /^N/i){ ++$index; next; }elsif($input =~ /^G/i){ print "enter a number from -",$#files+1," to $#files -> "; $index = <>; chomp $index; }elsif($input =~ /^H/i){ $hide_help = $hide_help?0:1; }elsif($input =~ /^C/i){ $EDITOR = <>; chomp $EDITOR; $EDITOR = which ($EDITOR); # redo STUDY; }elsif($input =~ /^R/i){ --$index; }elsif($input =~ /^L/i){ print "change menu item color to: "; my $newcolor = <>; chomp $newcolor; $color = colorize($newcolor); }elsif($input =~ /^T/i){ print "change menu text color to: "; my $textcolor = <>; chomp $textcolor; $reset = colorize($textcolor); }elsif($input =~ /^(SANDBOX)/i){ print "$homedir/"; my $sandbox = <>; chomp $sandbox; $STUDYPATH = change_snippet_dir($sandbox); }elsif($input =~ /^E/i){ system ($EDITOR,$files[$index]); }elsif($input =~ /^X/i){ do $files[$index]; print "\n"; $clear = 0; }elsif($input =~ /^Q/i){ last; }else{ $clear=0; open (SCRIPT,"< $files[$index]") or warn "can't open file: $!"; print while <SCRIPT>; } } sub which{ my $editor = shift; foreach (my @PATH = split(":",$ENV{PATH})) { if (-x "$_/$editor") { return "$_/$editor"; last; } } which ('less') if not -x "$editor"; } sub colorize{ my $a = shift; my $newcolor; eval "use Term::ANSIColor 'color'"; return if $@; for (my @valid_colors = split /,/ => qq( blue bold,yellow bold,reset, green bold,red bold,white, bold blue,bold yellow,bold white, bold green,bold red, red,green,yellow,blue)) { s/^\s+//s; if ( $a =~ /($_)/i) { return $newcolor = color($_); } } } sub change_snippet_dir{ my $sandbox = shift; my $STUDYPATH = "$homedir/$sandbox"; -d $STUDYPATH ? return $STUDYPATH: return "$ENV{HOME}/$ENV{SANDBOX}"; }

Replies are listed 'Best First'.
Re (tilly) 1: newer snippet browser
by tilly (Archbishop) on Jan 20, 2001 at 19:08 UTC
    This is a nice idea. However upon request, here are some detailed suggestions.

    First of all I would suggest that you use Config to find the fallback:

    use strict; use Config; use vars qw(%Config); print "My default pager is $Config{pager}\n";
    That will make your code substantially more customizable. And if someone doesn't have less on their system (such do exist) they won't go directly into hard recursion.

    In the same vein I suggest having the editor be not just hard-coded but specifiable with an environment variable and possibly also with a command-line option. I usually use Getopt::Std for the latter. I would do the same with the color.

    Speaking of the color, it is good that you handle the case where you cannot load colors, but I would suggest a brief message. Also I am wondering at your attempt to dictate valid colors. Why not just throw the color and then trap errors? Like this:

    sub colorize { # Autoload here eval {require Term::ANSIColor}; if ($@ =~ /locate Term::ANSIColor/) { print "Cannot locate Term::ANSIColor.\n", "Color support disabled\n"; return; } elsif ($@) { die $@; } my $color = shift; my $escape_code = eval {color($color)}; if ($@) { print "Invalid color '$color'\n"; return color('reset'); } else { return $escape_code; } }
    Next, why is your change_snippet_dir function inside your loop? Putting it there doesn't do anything useful.

    Another detail. Should the sandbox be a directory with spaces in the path name, your glob is going to run into problems with shell semantics. I would suggest going with opendir and readdir - less danger of breakage.

    Now a bigger change. Your main loop is very simple in concept. However you now have a manual syncronization job between printing your menu and writing your loop. This is going to be a problem. Instead I would suggest that you take a page off of Code that writes code and write the stuff currently in the body of your loop as structured text. Extract that into the real loop, then eval it. I would strongly recommend that if you go this way you test your eval for $@, and if there are errors dump the full text of the code you created.

    Alternately you can write the information for the menu and the handlers in two hashes, then run an automatic check at load that the two data structures are in sync. Then you could, say, dispatch to anonymous subroutines like this:

    my ($case, $data) =(split /\s/, $input, 2; $case = lc($case); if (exists $handler{$case}) { $handler{$case}->($data); } else { do_default($input); }
    There are also ways to combine the two approaches. One of the nicest is to use the fact that nested Perl data structures already look a lot like a structured data format. Take a look at the output of Data::Dumper to see what I mean. (I prefer $Data::Dumper::Indent set to 1, YMMV.) So you can have something like this:
    @cases = [ # etc { case => 'l', desc => 'co_or to change the menu item color', func => sub { print "change menu item color to: "; my $newcolor=<>; chomp $newcolor; $color=colorize($newcolor); }, }, # etc };
    Think you can reconstruct your loop from that? :-)

    Any way you cut it, a long-term maintainance problem is going to be keeping the description in the menu in sync with what the loop does. If your program will (and you know it will) remain small, then solving that is unneeded work. But if there is a good chance it will grow or change over time, restructuring so that the information that belongs together will stay together is worthwhile.

    Also a point that is too often forgotten is that while the work may not have been needed, being agressive about doing this kind of thing on small projects stretches you so that you have the skills when you need it later...

(tye)Re: newer snippet browser
by tye (Sage) on Jan 20, 2001 at 22:54 UTC

    Here is what I came up with after a quick run through at your request:

    #!/usr/bin/perl -w =head Changes Be more consistant with your spacing. I don't see the use for which() since you are just duplicating code th +at the kernel will do but less portably (under Win32, you need to spl +it on ';', not on ':'). Used a more "standard" method for picking an editor. Since you only have one enclosing loop, you don't need the STUDY: labe +l, but I don't mind you keeping it since it makes it easy to find tha +t loop. On non-Unix systems, $ENV{HOME} may not be set. Declaring subs in the middle of code just makes them hard to find. Added a default for $ENV{SANDBOX} s/[A-Za-z]/0/g more efficient as tr/A-Za-z/0/ Allow easy highlight with a color other than 'GREEN' Replace repeated highlighting code with a subroutine Treat $hide_help and $clear as Booleans instead of integers Your first "next" doesn't do anything (I just commented it out) Your first "redo" doesn't do anything (commented out) Don't have a [my] variable with the same name as a global, STUDYPATH close SCRIPT when you are done with it =cut use strict; use Term::ANSIColor 'color'; my $EDITOR = $ENV{VISUAL} || $ENV{EDITOR} || $ENV{EDIT} or $^O =~ /^MSWin/ ? 'notepad' : 'vi'; my $index = @ARGV ? shift(@ARGV) :0; my $clear = 1; my $hide_help = 0; my $highlight = $ENV{STUDYCOLOR} || 'GREEN'; my $homedir = $ENV{HOME} || '.'; my $sandbox = $ENV{SANDBOX} || 'sandbox'; my $STUDYPATH = "$homedir/$sandbox"; sub change_snippet_dir{ my $sandbox= shift; my $path= "$homedir/$sandbox"; if( -d $path ) { $STUDYPATH= $path; } } sub highlight { return color($highlight), @_, color('reset'); } STUDY: while (1) { my @files= glob "$STUDYPATH/*"; if( $clear ) { system ('clear'); } else { $clear++; } $index =~ tr/A-Za-z/0/; $index= 0 if $index < (0-$#files)-1 || $index > $#files; printf( "%50s <- (%5s)\n", $files[$index], $index ); print " ",highlight("[C]"),"HANGE editor. Currently $EDITOR ",highlight("[E]"),"DIT to open the snippet in an editor ",highlight("[G]"),"oTo to select file index: -", ($#files+1)," to + $#files ",highlight("[H]"),"ELP to (un)hide this menu ",highlight("[N]"),"EXT to skip this snippet ",highlight("[R]"),"EDO to revisit the previous snippet ",highlight("[Q]"),"UIT to exit e",highlight("[X]"),"ecute Any other key +",highlight(" [ENTER] "),"to print out the snippet -> " unless $hide_help; my $input = <>; if ($input =~ /^N/i){ ++$index; # next; This doesn't do anything }elsif($input =~ /^G/i){ print "enter a number from -",$#files+1," to $#files -> "; $index = <>; chomp $index; }elsif($input =~ /^H/i){ $hide_help= ! $hide_help; }elsif($input =~ /^C/i){ $EDITOR = <>; chomp $EDITOR; # redo STUDY; }elsif($input =~ /^R/i){ --$index; }elsif($input =~ /^(SANDBOX)/i){ print "$homedir/"; my $sandbox=<>; chomp $sandbox; change_snippet_dir($sandbox); }elsif($input =~ /^E/i){ system( $EDITOR, $files[$index] ); }elsif($input =~ /^X/i){ do $files[$index]; print "\n"; $clear= 0; }elsif($input =~ /^Q/i){ last; }else{ $clear= 0; open (SCRIPT,"< $files[$index]") or warn "can't open file: $!"; print while <SCRIPT>; close SCRIPT; } }
            - tye (but my friends call me "Tye")

      This is just terrific stuff, tye. Like tilly's criticisms, I understand what you wrote, and I can agree with everything you said. I'm grateful that you both took so much time to make detailed comments. I'll try to make good use of your advice, not just in improving this simple program but in thinking more carefully about portability, maintainability, and choosing the appropriate data structures.

      Hopefully the light will come up, little by little, and you'll see some smarter programs from me over time!
      mkmcconn