[= AutoGen5 Template -*- Mode: C -*-

h
c

=]
[= INVOKE preamble \=]
[= CASE (suffix)    =][=#

PURPOSE:
   This template will produce a header file and one or two source files.
   You supply a list of commands as values for "cmd" and it will produce
   an enumeration of these commands and code that will match strings against
   these command names.  The input command name need only be as long as
   needed for a unique partial match.  For example, if there is only one
   command that starts with "x", then the single letter "x" is sufficient
   for a unique match.

YOU SUPPLY:
   an AutoGen definitions file.  It must contain a list of values for
   "cmd".  e.g.:  cmd = first, second, third;

   It may also contain "abbrev", "c-text", "dispatch", "ident" and
   "copyright" values.
  
   "copyright" See the AutoOpts documentation for its usage.
   "ident"     is inserted at the top of all output files.
   "c-text"    is inserted at the top of the .c files
   "dispatch"  describes the format of handler functions:
      "fmt"    how to construct the name from the command name.  e.g. "cmd_%s"
      "arg"    The type and name of argument(s) to handler functions
      "ret"    the function return type.
   The handler functions are expected to have the following profile,
   assuming we are expanding the "cmd" named "mumble" and "dispatch" has:
      ret = int;
      fmt = 'cmd_%s_hdlr';
      arg = 'void * cookie';

   then:

      extern int cmd_mumble_hdlr(char const * cmd, void * cookie);
   though "cmd" will point past the command name.

   "abbrev"    is used to construct the name for an invalid value handler

THE TEMPLATE PRODUCES:

   <base-name> is based on the name of your definitions file:

   <base-name>.h    The enumeration of the commands in the form:
                    <BASE_NAME>_<COMMAND> of type <base_name>_enum_t

                    External declarations of the two or three generated
                    procedures.  (dispatch_<base_name> is produced IFF
                    there is a "dispatch" value.

   <base-name>.c    The code for the two or three procedures.

   <base-name>-hdlr.c  This is produced if there is a "dispatch" value and if
                    the environment variable EMIT_DISPATCH is set.

   If your commands include one named, "help", then an input string starting
   with a "?" will match the help command.

   If your commands include one named, "null", then an empty or all spaces
   input string will match the null command.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

  H E A D E R

=][=

== h                =][=

INVOKE initialize  \=]
[= header-includes  =]

typedef enum { [= (. INVALID) =] = 0,
[=

 ;;  "clist" contains the names of the commands in C program name format.
 ;;  Upper case them, prepend "TYPE_" to the front and comma separate them.
 ;;  We now have our list of enumeration names.
 ;;
 (shell (string-append
    "( tr a-z A-Z | "
    "  columns -I4 --spread=1 --sep=, --format='" TYPE "_%s' "
    ") <<_EOCmds_\n" clist "\n_EOCmds_" ))

=]
} [= (. enum-nm) =];

#define [= (. TYPE) =]_COUNT [= (count "cmd") =]

extern [= (. enum-nm) =]
[= (. tp-nm) =]_enum(char const * name, char const ** next);

extern char const *
[= (. tp-nm) =]_name([= (. enum-nm) =] cmd);[=

IF (exist? "dispatch")  =][=
   FOR dispatch         =][=

      IF (define arg-list  "char const * cmd")
         (define call-list "cmd")
         (exist? "dispatch.arg")        =][=

         (out-push-new) =][=

         FOR arg        =], [= arg =][=
            (set! call-list (string-append call-list ", "
                  (shellf "echo '%s' | sed $'s/.*[ \t*]//'" (get "arg")) ))
            =][=
         ENDFOR         =][=

         (set! arg-list (string-append arg-list (out-pop #t))) =][=

      ENDIF

=]
extern [= ret =] dispatch_[= (. tp-nm) =]([= (. arg-list) =]);[=

   ENDFOR dispatch      =][=

ENDIF

=]

#endif /* [= (. header-guard) =] */
[=#

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    C   P R O G R A M   T E X T

=][=

== c            \=]

#include <ctype.h>
#include <string.h>
#include "[= (. header-file) =]"
[= dispatch.c-text =][=

INVOKE construct-string-table

=]
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *  The hash code was generated by "gperf(1GNU)" from the following:
 *
[= ;; */
  (make-gperf str-table-name clist)
  (emit (shellf "sed '/^int main(/,$d;s/^/ *  /' ${gpdir}/%s.gperf"
                str-table-name))
  (emit "\n */\n")
  (gperf-code str-table-name)
=]
/*
 *  END of gperf code
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

[= (. enum-nm) =]
[= (. tp-nm) =]_enum(char const * name, char const ** next)
{
    size_t nmlen = 0;
    char   name_buf[[= (+ max-len 1) =]];

    /*
     * Skip leading white space and figure out how many name characters
     * there are.  It cannot be longer than the longest name in our table.
     */
    while (isspace(*name))   name++;

    if (isalpha(*name)) {
        char const * ps = name;
        char * pd = name_buf;
        for (;;) {
            char ch = *(ps++);
            if (isupper(ch))
                *(pd++) = _tolower(ch);

            else if (isalnum(ch))
                *(pd++) = ch;

            else switch (ch) {
                case '-':
                case '_': *(pd++) = '_'; break;
                default:  goto name_scan_done;
            }

            if (++nmlen > [= (. max-len) =])
                return [= (. INVALID) =];
        } name_scan_done:;
        *pd = '\0';
    }

    if (nmlen == 0) {
[= IF (> (string-length special-char-handling) 1) \=]
        [= (. enum-nm) =] res = [=  (. INVALID)    =];
        /*
         * If there were no name characters found, then
         * check for commands triggerd by a single non-alpha character:
         */
        switch (*name) {[=
            (. special-char-handling)             \=]
        }
        return (res);
[= ELSE  no special char handling                 \=]
        return ([= (. INVALID) =]);
[= ENDIF special char handling                    \=]
    }

    if (nmlen >= [= (. min-len) =]) do {
	struct [= (. str-table-name)
                =]_index const * pi = [= (. str-table-name)
                =]_find(name_buf, nmlen);

	if (pi == NULL)
	    break;

        if (next != NULL) {
            name += nmlen;
            while (isspace(*name)) name++;
            *next = name;
        }
	return ([= (. enum-nm) =])pi->idx;
    } while (0);

    /*
     *  An exact match is not possible.  Check for partial match.
     */
    {
        /*
         *  indexes of names in [= (. str-table-name) =] in alphabetical order.
         */
        static int const alpha_ix[[=(. cmd-ct)=]] = {
[=
(shell (string-append

    "( sort | awk '{ print $2 }' | columns -I12 -S,
    ) <<_EOF_\n" sort-list "_EOF_"

))
=]
        };
        /*
         *  Map the alphabetized index into the enum value
         */
        static int const enum_val[[=(. cmd-ct)=]] = {
[=

 ;;  Emit the comma separated list of offsets in their original order
 ;;
 (shell (string-append

    "( tr a-z A-Z | sort | "
    "  columns -I12 --spread=1 --sep=, --format='" TYPE "_%s' "
    ") <<_EOCmds_\n" clist "\n_EOCmds_"

))

=] };

        return ([= (. enum-nm) =])partial_name_match(
            name_buf, [= (sprintf "%s, alpha_ix, enum_val, %d"
                          str-table-name (- cmd-ct 1)) =]);
    }
}

/*
 * Translate the command code into the name
 */
char const *
[= (. tp-nm) =]_name([= (. enum-nm) =] cmd)
{
    /*
     *  Table of name offsets indexed by the enum value
     */
    static unsigned int const enum_idx[[=(. cmd-ct)=]] = {
[=

 ;;  Emit the comma separated list of offsets in their original order
 ;;
 (shell (string-append

"columns -I8 -S, --spread=1 <<_EOF_\n" enum-list "_EOF_"

))

=] };
    unsigned int ix = (unsigned int)cmd - 1;
    if (ix > [= (- cmd-ct 1) =])
        ix = 0;
    else
        ix = enum_idx[ix];
    return [= (. str-table-name) =] + ix;
}[=

INVOKE dispatcher

=][=

ESAC prefix =]
/* end of [= (out-name) =] */
[=#

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Dispatcher   =][=

 (if (< 1 (count "dispatch"))
     (error "too many dispatch descriptions")) =][=

FOR dispatch

=]

typedef [= ret =] (handle_[= (. tp-nm) =]_t)([= (. arg-list) =]);

extern handle_[= (. tp-nm) =]_t [=
    (define  cmd-fmt (if (exist? "fmt") (get "fmt") "do_%s"))

    (define inval-cmd (string-append "inval_"
            (if (exist? "abbrev") (get "abbrev") tp-nm)  ))
    (sprintf cmd-fmt inval-cmd) =],
[=
(define cmd-procs (shellf

"columns -I4 --spread=1 --sep=, --format='%s'<<_EOF_\n%s\n_EOF_"
  cmd-fmt (string->c-name! (join "\n" (stack "cmd")))

))

cmd-procs

=];

static handle_[= (. tp-nm) =]_t * const hdl_procs[[= (+ 1 cmd-ct) =]] = {
    [= (sprintf cmd-fmt inval-cmd) =],
[= (. cmd-procs) =] };

[= ret =]
dispatch_[= (. tp-nm) =]([= (. arg-list) =])
{
    [= (. enum-nm) =]     id  = [= (. tp-nm) =]_enum(cmd, &cmd);
    handle_[= (. tp-nm) =]_t * hdl = hdl_procs[id];
    [=
     (if (= (get "ret") "void")
         (sprintf "(*hdl)(%s);\n}"        call-list)
         (sprintf "return (*hdl)(%s);\n}" call-list)
     ) =][=

   IF (getenv "EMIT_DISPATCH")  =][=
      INVOKE Handler    =][=
   ENDIF                =][=

ENDFOR dispatch         =][=

ENDDEF dispatcher

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Handler          =][=

 (out-push-new (string-append (base-name) "-hdlr.c"))
 (define fmt-fmt (sprintf
 "handling %%-%ds (%%d) cmd - args:  '%%%%s'\\n" max-len))
 
 (set-writable)

\=]
[= INVOKE preamble      =]
#include <stdio.h>
#include "[= (. header-file)    =]"
[= dispatch.c-text =][=

   FOR cmd              =][=
      IF (not (= (get "cmd") "help")) =]

[= ret =]
[= (sprintf cmd-fmt (get "cmd")) =]([= (. arg-list) =])
{
#ifdef DEBUG
    printf("[= (sprintf fmt-fmt (get "cmd") (+ 1 (for-index)))
            =]", cmd);
#else
    fputs("[= cmd =]\n", stdout);
#endif
    return ([= ret =])0;
}
[=

      ENDIF not "help" \=]
[= ENDFOR cmd          \=]
[= IF (match-value? = "cmd" "help")     =][=
      IF (getenv "SHOW_NOHELP")         =][=
                (define show-nohelp #t) =][=
      ELSE =][= (define show-nohelp #f) =][=
      ENDIF=]

[= ret =]
[= (define help-fmt (sprintf "%%-%ds%%s\n"
      (- (+ max-len 4) (remainder max-len 4)) ))
   (define cmd-name "")

   (sprintf cmd-fmt "help") =]([= (. arg-list) =])
{
    static char const h_txt[ [=
        (out-push-new)      =][=
     FOR cmd                =][=
        (set! cmd-name (get "cmd"))
        (if (exist? (string-append cmd-name "-help"))
            (sprintf help-fmt cmd-name (get (string-append cmd-name "-help")) )
            (if (= cmd-name "help")
                (sprintf help-fmt cmd-name "Display this help text")
                (if show-nohelp
                    (sprintf help-fmt cmd-name "No provided help"))
        )   )               =][=
     ENDFOR each "cmd"      =][=

    (define help-text (out-pop #t))
    (string-length help-text)   =] ] =
       [= (c-string help-text)  =];
    if (*cmd != '\0')
        printf("help args:  %s\n", cmd);
    fwrite(h_txt, [= (string-length help-text) =], 1, stdout);
    return ([= ret =])0;
}
[= ENDIF have-a-help cmd    =]

[= ret =]
[= (sprintf cmd-fmt inval-cmd)  =]([= (. arg-list) =])
{
    printf("handling invalid (%d) command:  '%s'\n",
           [= (. INVALID) =], cmd);
    return ([= ret =])0;
}

int
main( int argc, char** argv )
{
    while (--argc > 0)
        dispatch_[= (. tp-nm) =]( *++argv[=FOR arg=], 0[=ENDFOR=] );
    return 0;
}
[= (out-pop)
   (set-writable #f)    =][=

ENDDEF Handler

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Preamble                 =][=

(dne " * " "/*")                =][=

  IF (exist? "copyright")       =][=
  CASE (get "copyright.type")   =][=

    =  gpl  =]
 *
[= (gpl  prog-name " * " )      =][=

    = lgpl  =]
 *
[=(lgpl prog-name (get "copyright.owner") " * " ) =][=

    =  bsd  =]
 *
[=(bsd  prog-name (get "copyright.owner") " * " ) =][=

    = note  =]
 *
 * Copyright (c) [= copyright.years =] by [= copyright.owner =]
 * All rights reserved.
 *
[=(prefix " * " (get "copyright.text"))=][=

    *       =] * <<indeterminate license type>>[=

  ESAC                          =][=
  ENDIF  copyright exists       =]
 */
[= (if (exist? "ident")
       (string-append (get "ident") "\n")) =][=

ENDDEF Preamble

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE spec-char-cmd            =]
        case [= spec-char =]:  /* [= (. cmd-name) =] command alias */
            res = [= (. TYPE) =]_[= (string-upcase! cmd-name) =];[=
  IF (not (= cmd-name "null"))  =]
            name++;[=
  ENDIF is cmd-name not null?   =]
            break;
[=

ENDDEF spec-char-cmd

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Initialize   =][=

   (define tp-nm    (string-downcase! (if (exist? "type")
           (string->c-name! (get "type"))
           (string->c-name! (base-name)) )))

   (define enum-nm  (string-append tp-nm "_enum_t"))
   (define TYPE     (string-upcase (if (exist? "prefix")
           (string->c-name! (get "prefix"))
           tp-nm )))

   (define INVALID  (string-append TYPE "_" (if (exist? "invalid")
     (string-upcase! (string->c-name! (get "invalid"))) "INVALID" )))

   (define name-len 0)
   (define max-len  0)
   (define min-len  256)
   (define cmd-ct   (count "cmd"))
   (define clist    (string->c-name! (join "\n" (stack "cmd"))))
   (define cmd-name "")
   (if (exist? "header-file")
       (out-move (get "header-file")) )
   (make-header-guard "select") =][=
   (out-push-new)               =][=

   FOR cmd          =][=

     (set! cmd-name (string->c-name! (get "cmd")))
     (set! name-len (string-length cmd-name))
     (if (> name-len max-len)
         (set! max-len name-len) )
     (if (< name-len min-len)
         (set! min-len name-len) ) =][=

     CASE cmd       =][=
     =  help        =][= INVOKE spec-char-cmd  spec-char = "'?'" =][=
     =  null        =][= INVOKE spec-char-cmd  spec-char = "NUL" =][=
     ESAC cmd       =][=

   ENDFOR           =][=

   (define special-char-handling (out-pop #t))
   (if (not (defined? 'special-char-handling ;; '
       )    )
       (define special-char-handling "") ) =][=

ENDDEF Initialize

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE construct-string-table =][=

   (define str-table-name tp-nm)
   (string-table-new str-table-name)
   (string-table-add str-table-name "** INVALID **")
   (define ix 0)
   (define enum-list "")
   (define sort-list "")

   =][=

   FOR  cmd

     =][=

     (set! cmd-name (get "cmd"))
     (set! ix (string-table-add str-table-name cmd-name))
     (set! enum-list (string-append enum-list (sprintf "%d\n"             ix)))
     (set! sort-list (string-append sort-list (sprintf "%s %d\n" cmd-name ix)))

     =][=

   ENDFOR

   =][=

   (emit-string-table str-table-name)  =][=

ENDDEF construct-string-table

\=]