#!/usr/local/bin/kermit

; r e m o t e a c c e s s
; 
; How to conduct a dialog with a user when Kermit does not have a controlling
; terminal; for example, when Kermit has accepted an incoming Internet ("set
; host *") or modem ("answer") connection, or when a Kermit script is started
; under inetd.  This script shows how to handle echoing and rudimentary
; keyboard editing:
;
; . Prints a prompt and waits for a line of text.
; . Ordinary characters echo.
; . Rubout, Delete, or Backspace removes the rightmost character from the line.
; . Ctrl-U removes the current line, if any.
; . Ctrl-R repaints the current line.
; . The prompt is protected.
;
; To make it more interesting, lines are interpreted as commands, validated,
; and executed.
;
; Note that when printing lines to the user's screen, both carriage (\13)
; and linefeed (\10) are included for when there is no terminal driver
; to supply them.  In case there IS a terminal driver, the SET INPUT ECHO OFF
; command allows the script to work there too.
;
; F. da Cruz, Columbia University, January 2003

; First make a command keyword table and then sort it for \tablelook().
;
declare \&k[] = exit:5 quit:5 help:2 ?:2 echo:1 send:3 list:4 directory:4
sort &k

; Make an array of help strings keyed on keyword value.
;
dcl \&h[5]
.\&h[1] = ECHO [text] echos its arguments.
.\&h[2] = HELP prints this message; Synonym: "?".
.\&h[3] = SEND filename [as-name] sends the given file under the given name.
.\&h[4] = LIST [filespec] lists files; Synonym: DIRECTORY.
.\&h[5] = EXIT exits; Synonym: QUIT.

; Define command service routines

define DOEXIT {                        ; Service routine for EXIT command
    output "\13\10Bye.\13\10"
    sleep 1
    exit
}

define DOSEND {                        ; Service routine for SEND command
    if not defined \%1 {
        output "Filename required\13\10"
        end 1
    } else if not exist \%1 {
        output "File not found: "\%1"\13\10"
        end 1
    }
    send \%1 \%2
}

define DOLIST {                        ; Service routine for LIST command
    local \&d[] \%i \%f \%m
    directory /array:&d \%1            ; Get filenames into array &d[]
    output "\13\10"
    if not \fdim(&d) {
        output "(No files match)\13\10"
    } else {
        .\%m := 0                      ; Quickly get length of longest name
        for \%i 1 \fdim(&d) 1 {
            .\%m := \fmax(\%m,\flen(\&d[\%i]))
        }
        incr \%m
        for \%i 1 \fdim(&d) 1 {        ; Print listing
            .\%f := \&d[\%i]
            if not readable \%f continue
            output "  \frpad(\%f,\%m) \flpad(\fsize(\%f),9)  \fdate(\%f)\13\10"
        }
    }
    output "\13\10"
}

define DOHELP {                        ; Service routine for HELP command
    local \%i
    output "\13\10"
    output "Commands are:\13\10\13\10"
    for \%i 1 \fdim(&h) 1 {
        output "  \&h[\%i]\13\10"
    }
    output "\13\{10}Commands may be abbreviated.\13\10"
    output "\13\10"
}

; Command parser and dispatcher

define DOCOMMAND {
    if not def \%1 return              ; Ignore blank lines
    void \fsplit(\%1,&a,\32)           ; Split line into words
    .\%k := \ftablelook(\&a[1],&k)     ; Look up first word
    switch \%k {                       ; Handle lookup error
      :-1
        output "Command not found: "\&a[1]"\13\10"
        output "Type HELP for help\13\10"
        continue
      :-2
        output "Ambiguous: "\&a[1]"\13\10"
        output "Type HELP for help\13\10"
        continue
    }
    switch \fword(\&k[\%k],2,:) {      ; Dispatch on keyword value
      :1                               ; ECHO prints its arguments
        output "\fjoin(&a[2:])\13\10"
        break
      :2                               ; HELP prints help text
        dohelp
        break
      :3                               ; SEND sends a file
        dosend \&a[2] \&a[3]           ; with an optional as-name
        output "\13\10"
        break
      :4                               ; LIST prints a file list
        dolist \&a[2]
        break
      :5                               ; QUIT and EXIT quit
        doexit
    }
}

set case off                           ; Commands are case-independent
set input echo off                     ; In case there is a controlling tty
set root .                             ; Restrict users to this directory tree

define prompt "Command: "              ; Define prompt
output "\13\10"                        ; Start on a new line
output "Type HELP for help.\13\10"     ; Greet

; Loop to read lines and pass them to the command interpreter.
; This is where we handle echoing and editing.

while true {                           ; Outer loop for lines
    undef buf                          ; Line buffer
    .\%n = 0                           ; Line length
    output "\m(prompt)"                ; Prompt
    set flag off                       ; Inner loop control
    while not flag {                   ; Inner loop for characters
        input -1                       ; Get a character
        if fail stop 1 INPUT Error     ; Make sure we did
        .\%c := \v(inchar)             ; This is the character
        .\%x := \fcode(\%c)            ; This is its code
        switch \%x {                   ; switch on its code
          :3                           ; Ctrl-C
            doexit                     ; Exit immediately
          :13                          ; Carriage Return
          :10                          ; or Line Feed
          :12                          ; or Form Feed
            output "\13\10"            ; Echo CRLF
            set flag on                ; Set inner-loop exit flag
            docommand "\m(buf)"        ; Process the user's command
            continue
          :8                           ; Backspace
          :127                         ; or Rubout
            if not def buf {           ; Nothing to delete
                output "\7"            ; just beep
                continue
            }
            output "\8 \8"             ; Erase char from user's screen
            decr \%n                   ; And from line buffer
            .buf := \s(buf[1:\%n])
            continue
          :18                          ; Ctrl-R
            output "\13\10"            ; Refresh line
            output "\m(prompt)\m(buf)"
            continue            
          :21                          ; Ctrl-U
            for \%i 1 \%n 1 {          ; Erase line from screen
                output "\8 \8"
            }
            .\%n = 0
            undef buf
            continue
          :9                           ; Ctrl-I (Tab)
            .\%c := \32                ; Convert to space
            .\%x := 32                 ; and fall thru.
          :default                     ; Anything else
            if > \%x 31 {              ; If char is printable
                output \%c             ; echo it
                .buf := \m(buf)\%c     ; add it to line buffer
                incr \%n               ; and count it
            } else output "\7"         ; Beep for nonprintables
        }          
    }
}
