PetaMem has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

the folowing code snippet is used in one of my applications
and - of course - it works, but - of course - could be better.
The main problem I have with this, is complete lack of editing
functionality not even mentioning gimmicks like expansion or
reverse-search etc. (all nice thingies I´m used from the bash):

CMD: while($exit eq FALSE) { # We´re as long in the lo +op until no exitflag print "> "; $line = <STDIN>; # read input from stdin chop $line; goto CMD if(!$line); # chop CR and if input wa +s empty.. REDO ($key,@param) = split ' ', $line; # split key and parameter +s $exit = &cmd_select($key,@param); }
cmd_select is just a huge if ... elsif function.

Now if I happen (and that happens often) to type in something
wrong, I have noch chance of editing it. Instead of Backspace
theres a ^H, instead of "Cursor-Up" theres some ^[OA and so
on.

How - o how my brothers can I get a decent CLI?
Obligatory question: Is there a module? :-)

Ciao

Replies are listed 'Best First'.
Re: crafting a decent CLI
by lshatzer (Friar) on Jul 12, 2001 at 01:42 UTC
Re: crafting a decent CLI
by Abigail (Deacon) on Jul 12, 2001 at 02:24 UTC
    Your question was already answered, but I would like to suggest an alternative way to do your main loop:
    { local $_; print "> "; chomp ($_ = <STDIN>); redo unless /\S/; next if "FALSE" eq cmd_select split ' '; }
    (If you don't like a bare block, you could put a while (1) in front of it and change the next if to a last unless).

    My version of the loop doesn't use goto to break out of the loop and re-enter it, it doesn't use an explicite variable $exit, which is compared to the true value "FALSE", it uses chomp in favour of chop and it will try to execute the command 0 which your version skips, but it won't try to execute a line consisting of a space like yours. I also have eliminated to need for the variables $key and @param. I would prefer cmd_select to return a true/false value instead of "FALSE" and something else.

    -- Abigail

      My version of the loop doesn't use goto

      No, it uses redo and next, which can be worse than goto. q-: (Except perhaps from the perspective of the optimizer which doesn't bother to try to figure out goto most of the time.)

      But what I'm really puzzled about is this new "naked block with lots of redo, next, and last" that I've been seeing lately. This example doesn't require anything so powerful and easy-to-abuse as that. Why not some simple loops like:

      local $_; do { do { print "> "; chomp( $_= <STDIN> ); } until( /\S/ ); } until( 'FALSE' eq cmd_select( split ' ' ) );
      or
      local $_; do { print "> "; chomp( $_= <STDIN> ); } until( /\S/ && 'FALSE' eq cmd_select( split ' ' ) );
      or even have your code handle EOF on STDIN:
      local $_; do { do { print "> "; defined( $_= <STDIN> ) or die "EOF on STDIN"; chomp( $_ ); } until( /\S/ ); } until( 'FALSE' eq cmd_select( split ' ' ) );
      (if you need to clean up on EOF, then throw this in a sub and return instead of die)

      That way your code actually looks like "loop reading commands until you get a non-empty one and loop doing that until a 'quit' is requested". Not, "here is a block that I may or may not be using as a loop (please parse the rest of the block if you want to know), read a command, goto the top of the nearest surrounding loop (or naked block but not a conditional or do block) if the command is empty, otherwise go to the continue block (if there is one) or the top of the nearest surrounding loopish block, if a 'quit' was not requested". (:

      I know that something like the following is a fairly common task:

      lots of work to get next thing; # possibly giving up for any of several reasons goto TOP if thing isn't interesting; if( thing is invalid ) { instruct; goto TOP; } do lots of stuff with thing; goto TOP if not last thing; if( no valid things found ) { complain; return; } finish up; return result;
      and that you can code that with a naked block and a few redos and/or nexts (which do the exact same thing in a naked block so mix them around to keep people guessing) and perhaps a last near the bottom. I mean you can do nearly anything with those almost like you can do anything with goto.

      But I'd much rather stick to abusing the "early return" which I don't find hard to parse at all:

      sub get_next_interesting_thing { do { return if ! get_next_thing; } until( interesting_thing ); return thing; } sub get_next_valid_thing { while( get_next_interesting_thing ) { return thing if valid_thing; instruct; } return; } if( ! get_next_valid_thing ) { complain; return; } do { do lots of stuff with thing; } while( get_next_valid_thing ); finish up; return result;
      (or if keeping track of having seen at least one valid thing is less work than retyping "get_next_valid_thing", then switch that bottom stuff around to look more like the original)

              - tye (but my friends call me "Tye")
        That way your code actually looks like "loop reading commands until you get a non-empty one and loop doing that until a 'quit' is requested". Not, "here is a block that I may or may not be using as a loop (please parse the rest of the block if you want to know), read a command, goto the top of the nearest surrounding loop (or naked block but not a conditional or do block) if the command is empty, otherwise go to the continue block (if there is one) or the top of the nearest surrounding loopish block, if a 'quit' was not requested". (:
        And for that, you use a "here's a do, please parse till the end to see whether I want to include a file, I want to turn a block into an expression or I want to use a looping construct"?

        At least with a bare block you know you have a looping construct. With do you can have 3 totally different things. On top of that, several of your examples nest dos. I can understand that some people find bare blocks confusing - I indicated you could use a while(1) in front of it - but I fail to see that nested do constructs are easier than a bare block.

        I would think that the major "problem" with my bare block is that the guard is at the end - but your do { } until suffers from the same problem.

        Here's how I read my block:

        • print prompt.
        • read line and chomp it.
        • try again if there are no non-blanks.
        • execute command and repeat if we don't quit.
        More or less the same as you would explain things in English. And isn't that what's a major design principle Larry uses for Perl? (Note that my next should have been a redo).

        -- Abigail

Re: crafting a decent CLI
by the_slycer (Chaplain) on Jul 12, 2001 at 01:41 UTC
    You might want to take a look at the code for PerlShell to see their implementation. I have not used it for some time, so I can't speak to features or stability, but this may help in your quest.
Re: crafting a decent CLI
by synapse0 (Pilgrim) on Jul 12, 2001 at 02:11 UTC
    just a note of info.. there are several keys that are mapped to do a delete.. when you see the ^H show up, it just means that backspace isn't mapped...
    Common character destruct keys are: backspace, del, ctrl-H, ctrl-D
    common movent keys are: ctrl-b, ctrl-f, ctrl-n, ctrl-p
    Modules such as Term::ReadLine should map the keys like you want..
    -Syn0