Readline interface

Revision as of 22:14, 31 May 2012 by rosettacode>Gerard Schildberger (→‎{{header|REXX}}: simplified the unAbbrev subroutine. -- ~~~~)

Build a simple application with a readline interface.

Readline interface is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The interface should provide

  • a history
  • commandline editing
  • application specific commands

The application could be based on Simple database or something else.

If the language already provides a readline interface, then it should be demonstrated how build an application specific readline interface (one that only understands the applications commands) and also show how to customize the builtin readline to provide the above features and especially add application specific commands.

See also: Input loop

C

A program that does absolutely nothing. Type 'help' for help. <lang c>#include <readline/readline.h>

  1. include <readline/history.h>
  2. include <string.h>

int main() { char *s; using_history(); while (1) { s = readline("This be a prompt> ");

if (!s || !strcmp(s, "quit")) { puts("bye."); return 0; }

if (!strcmp(s, "help")) puts("commands: ls, cat, quit"); else if (!strcmp(s, "ls") || !strcmp(s, "cat")) { printf("command `%s' not implemented yet.\n", s); add_history(s); } else puts("Yes...?"); } }</lang>

Pike

watch.pike is the solution from Simple database#Pike. this solution demonstrates how to retrofit an application with a readline interface by inheriting the original and overriding specific functions.

<lang pike>#!/usr/bin/pike

inherit "watch.pike";

Stdio.Readline readln = Stdio.Readline();

void print_help() {

   write("The following commands are available: \n");
   write(sort(indices(functions))*", ");
   write("\n");

}

void watch_add() {

   ::watch_add(db);

}

void watch_list() {

   ::watch_list(db);

}

void do_exit() {

   exit(0);

}

mapping functions = ([ "add":watch_add,

                      "list":watch_list,
                      "load":watch_load,
                      "save":watch_save,
                      "help":print_help,
                      "quit":do_exit ]);

string prompt_read(string prompt) {

   return readln->read(prompt+": ");

}

void main() {

 Stdio.Readline.History readline_history = Stdio.Readline.History(512);
 readln->enable_history(readline_history);
 string prompt="> ";


 print_help();
 while(1)
 {
   string input=readln->read(prompt);
   if(!input)
     exit(0);
   if(input != "")
   {
      if (functions[input])
          functions[input]();
      else
      {
          write("unknown command\n");
          print_help();
      }
   }
 }

}</lang>

Sample session:

pike watch_rl.pike
The following commands are available:
add, help, list, load, quit, save
> load
> list
  Rosetta Code                            2 Simple Database                (27.10.2011.)
> add
Series: Rdm asks!
Title: Balanced ternary
Episode: 1
Date watched: 1.11.2011
Add new series? [y/n]: : y
> list
  Rdm asks!                               1 Balanced ternary               (1.11.2011.)
  Rosetta Code                            2 Simple Database                (27.10.2011.)
> hello
unknown command
The following commands are available: 
add, help, list, load, quit, save
> save
> quit

REXX

This REXX programs supports a REDO (re-do) with very limited editing to keep the program simple.
It has a history (which can be interrogated) and a few simple (DOS) commands.
The HELP (?), REDO, error checking, and abbreviations took up most of the program. <lang rexx>/*REXX program to implement a simple "readline" shell. */ trace off /*suppress echoing of non-zero RC*/ signal on syntax; signal on novalue /*handle REXX program errors. */ cmds1='? DIR CALendar HISTory Kedit LLL PROMPT Quit Rexx REDO Type' cmds2='?|Help DIR CALendar HISTory Kedit LLL PROMPT Quit Rexx REDO Type' cls='CLS' /*define the pgm to clear screen.*/ @hist.='*** command not defined. ***' /*initialize the history database*/ hist#=0 /*number of commands in history. */ prompt='Enter one of:' cmds1 /*the default PROMPT message. */ sw=linesize() /*some REXX don't have this BIF. */ call .CLS /*start with a clean slate. */ redoing=0

  do forever
  call prompter;  if xxx== then iterate
    select                            /*now then, let's rock & roll.   */
    when xxxF=='CAL'                               then call .cal
    when xxxF=='CLS'                               then call .cls
    when xxxF=='DIR'                               then call .dir
    when xxxF=='HISTORY'                           then call .history
    when xxxF=='HELP'                              then call .help
    when xxxF=='KEDIT'                             then call .kedit
    when xxxF=='LLL'                               then call .lll
    when xxxF=='PROMPT'                            then call .prompt
    when xxxF=='QUIT'                              then leave
    when xxxF=='REDO'                              then call .redo
    when xxxF=='REXX'                              then call .rexx
    when xxxF=='TYPE'                              then call .type
    otherwise say 'unknown command:' xxx yyy    /*oops-say.*/
    end   /*select*/
  end     /*forever*/

say 'Quitting...' /*say goodbye, 'cause it's polite*/ exit /*stick a fork in it, we're done.*/ /*───────────────────────────────error handling subroutines and others.─*/ er: say; say arg(1); say; return err: say; say; say center(' error! ',max(40,linesize()%2),"*"); say

              do j=1 for arg(); say arg(j); say; end; say; exit 13

novalue: syntax: call err 'REXX program' condition('C') "error",,

            condition('D'),'REXX source statement (line' sigl"):",,
            sourceline(sigl)

/*──────────────────────────────────.CAL subroutine─────────────────────*/ .cal: 'CAL' yyy; return /*──────────────────────────────────.CLS subroutine─────────────────────*/ .cls: cls; return /*──────────────────────────────────.DIR subroutine─────────────────────*/ .dir: 'DIR' yyy; return /*──────────────────────────────────.HELP subroutine────────────────────*/ .help: say center(strip(xxx yyy),sw-1,'-'); cmds_=cmds2 cmdsH='CLS DIR TYPE' help. = ' No help is available for the' yyy "command." help.cal = 'shows a calendar for the current month or specified month.' help.kedit = 'KEDITs the file specified.' help.lll = 'shows a formatted listing of files in the current directory.' help.prompt= 'sets the PROMPT message to the specified text.' help.q = 'exits this program.' help.redo = 're-does the a command # specified (or the last command).' help.rexx = 'executes the REXX program specified.' yyyF=unabbrev(yyy) if yyy== then do j=1 while cmds_\==

               parse var cmds_ x cmds_
               say left(,sw%2) changestr('|',x,"  |  ")
               end
          else select
               when wordpos(yyyF,cmdsH)\==0 then yyyF '/?'
               otherwise cmd?=yyyF
               if left(help.yyyF,1)\==' ' then say yyyF ' ' help.yyyF
                                          else day help.yyyF
               end

return /*──────────────────────────────────.HISTORY subroutine─────────────────*/ .history: say center('history',sw-1,'-'); w=length(hist#)

            do j=1 for hist#
            say right(j,w) '===>' @hist.j
            end

return /*──────────────────────────────────.KEDIT subroutine───────────────────*/ .kedit: 'KEDIT' yyy; return /*──────────────────────────────────.LLL subroutine─────────────────────*/ .lll: 'LLL' yyy; return /*──────────────────────────────────PROMPTER subroutine─────────────────*/ prompter: if redoing then do /*special case for naked REDO */

                         redoing=0;       z=hist#-1
                         parse var @hist.z xxx yyy
                         end
                    else do
                         if prompt\== then do;   say;  say prompt;  end
                         parse pull xxx yyy
                         end

xxxU=xxx; upper xxxU; if xxx== then return yyyU=yyy; upper yyyU; yyyU=strip(yyyU) hist#=hist#+1; /*bump the history counter. */ @hist.hist#=strip(xxx yyy) /*assign to history. */ xxxF=unAbbrev(xxx) /*expand the abbreviation (maybe)*/ return /*──────────────────────────────────.PROMPT subroutine──────────────────*/ .prompt: if yyyU\== then prompt=yyy; return /*──────────────────────────────────.redo subroutine────────────────────*/ .redo:

 select
 when yyyU== then redoing=1         /*assume they want the last cmd. */
 when words(yyy)\==1     then call er 'too many args specified for' xxx
 when \datatype(yyy,'W') then call er "2nd arg isn't numeric for" xxx
 otherwise nop
 end

if redoing then return /*handle with kid gloves. */ yyy=yyy/1 /*normalize it: +7 7. 1e1 007 7.0*/ say 'Re-doing:' @hist.yyy @hist.yyy return /*──────────────────────────────────.REXX subroutine────────────────────*/ .rexx: 'REXX' yyy; return /*──────────────────────────────────.TYPE subroutine────────────────────*/ .type: 'TYPE' yyy; return /*──────────────────────────────────UNABBREV subroutine─────────────────*/ unabbrev: procedure; arg ccc

  select
  when abbrev('CALENDAR',ccc,3)                 then return 'CAL'
  when abbrev('CLEARSCREEN',ccc,5)              then return 'CLS'
  when abbrev('HISTORY',ccc,4)                  then return 'HISTORY'
  when abbrev('HELP',ccc,1) |,
         ccc=='?'                               then return 'HELP'
  when abbrev('KEDIT',ccc,1)                    then return 'KEDIT'
  when abbrev('QUIT',ccc,1)                     then return 'QUIT'
  when abbrev('REXX',ccc,1)                     then return 'REXX'
  when abbrev('TYPE',ccc,1)                     then return 'TYPE' 
  otherwise nop
  end

return ccc</lang>