add a commandline parser

parent acbb3827
*.sw?
coloritto
/* todo: make ; splitting work better
*
* argvs - argv - shell
*
* a small method/dispatcher for C functions following the int main (int argc,
* char **argv) pattern for receiving it's arguments.
*
* It is written in such a way that it should be easily repurposable for
* various purposes. And possibly easy to extend if one wants to add more
* advacned behavior.
*
* For zn purposes, it the arguments should probably be changed to an array
* of ZnId's, as should the return value.
*
* Authored by Øyvind Kolås, 2012
*/
#include <stdio.h>
#define USE_LINENOISE
#ifdef USE_LINENOISE
#include "linenoise/linenoise.h"
#include "linenoise/linenoise.c"
#else
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#endif
#include "argvs.h"
typedef struct _CmdIterator CmdIterator;
typedef struct _CmdEntry CmdEntry;
struct _CmdEntry {
int (*fun)(COMMAND_ARGS);
char *name;
int req_args;
char *args;
char *help;
};
static CmdEntry commands[] = {{0,}};
static CmdEntry dynamic_commands[512] = {{0,}};
static int dynamic_count = 0;
void
argvs_add (int (*fun)(COMMAND_ARGS),
const char *name,
int required_arguments,
const char *argument_help,
const char *help)
{
CmdEntry *entry = &dynamic_commands[dynamic_count++];
entry->fun = fun;
if (name)
entry->name = strdup (name);
entry->req_args = required_arguments;
if (argument_help)
entry->args = strdup (argument_help);
if (help)
entry->help = strdup (help);
}
static CmdIterator *cmd_iterator_new (void *foo);
static CmdEntry *cmd_iterator_next (CmdIterator *i);
static void cmd_iterator_stop (CmdIterator *i);
static void
argvs_free (CmdEntry *e)
{
free (e->help);
free (e->args);
free (e->name);
}
static void
argvs_cleanup (void)
{
CmdIterator *i = cmd_iterator_new (NULL);
CmdEntry *e;
while ((e = cmd_iterator_next (i)))
argvs_free (e);
}
#include "argvs-commands.inc"
/* cmds.inc is supposed to be created by your build system by using the
* following shell-script to run with your .c files as argument for
* collecting command definitions.
*/
#if 0
#!/bin/sh
echo "/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */"
echo "/* this file is autogenerated by $0 */"
echo "/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */"
# create function declarations for each item
cat $* | grep COMMAND_ARGS | grep ^int | sort | \
sed -e 's/^int */int /' -e 's#(COMMAND_ARGS).*#(COMMAND_ARGS);#'
# create a function that registers all items found
echo "void argvs_register(void) {"
cat $* | grep COMMAND_ARGS | grep ^int | sort | \
sed -e 's/^int */ argvs_add (/ ' -e 's#(COMMAND_ARGS) */\*#, #' -e 's#\*/$#);#'
echo "};"
#endif
struct _CmdIterator {
int i;
int j;
CmdEntry internal;
char *name;
char *req_args;
char *help;
};
static CmdIterator *cmd_iterator_new (void *foo)
{
CmdIterator *i = malloc (sizeof (CmdIterator));
static int first_run = 1;
if (first_run)
{
/* register the build-system harvested items */
argvs_register ();
first_run = 0;
/* free up allocations, so valgrind will not be angry */
atexit (argvs_cleanup);
}
/* at the moment, creating the iterator fills it up with possible sources,
* this could be done half-lazily,.. adding references to unused sources
* and expanding each as it is consumed, this allows stopping earlier with
* less resource use.
*/
i->name = NULL;
i->j = -1;
i->i = -1;
return i;
}
static void cmd_iterator_stop (CmdIterator *i)
{
if (i->name)
free (i->name);
i->name = NULL;
free (i);
}
static CmdEntry *cmd_iterator_next (CmdIterator *i)
{
if (i->name)
free (i->name);
i->name = NULL;
/* first iterate commands from db, then fixed list - this
* order permits overriding internal commands like forth does */
i->j++;
if (i->j < dynamic_count)
return &dynamic_commands[i->j];
/* first iterate commands from db, then fixed list - this
* order permits overriding internal commands like forth does */
i->i++;
if ((unsigned)i->i < sizeof (commands) / sizeof (commands[0]))
if (commands[i->i].fun)
return &commands[i->i];
cmd_iterator_stop (i);
return NULL;
}
/******* the rest should be using the iteration API for access
* to commands and not poke around in internals ************/
static char *string_chop_head (char *orig) /* return pointer to reset after arg */
{
int j=0;
int eat=0; /* number of chars to eat at start */
if(orig)
{
int got_more;
char *o = orig;
while(o[j] == ' ')
{j++;eat++;}
if (o[j]=='"')
{
eat++;j++;
while(o[j] != '"' &&
o[j] != 0)
j++;
o[j]='\0';
j++;
}
else if (o[j]=='\'')
{
eat++;j++;
while(o[j] != '\'' &&
o[j] != 0)
j++;
o[j]='\0';
j++;
}
else
{
while(o[j] != ' ' &&
o[j] != 0 &&
o[j] != ';')
j++;
}
if (o[j] == 0 ||
o[j] == ';')
got_more = 0;
else
got_more = 1;
o[j]=0; /* XXX: this is where foo;bar won't work but foo ;bar works*/
if(eat)
{
int k;
for (k=0; k<j-eat; k++)
orig[k] = orig[k+eat];
}
if (got_more)
return &orig[j+1];
}
return NULL;
}
#define CMDFILE "/tmp/zn-cmds"
void rec_reset (void)
{
unlink (CMDFILE);
return;
{
FILE *f = fopen (CMDFILE, "w");
fprintf (f, "%s", "");
fclose (f);
}
}
void rec_append (const char *str)
{
FILE *f = fopen (CMDFILE, "a");
fprintf (f, "%s\n", str);
fclose (f);
}
int argvs_eval (const char *cmdline)
{
char *cargv[32];
int cargc;
char *rest, *copy;
int ret = 0;
copy = calloc (strlen (cmdline)+2, 1);
strcpy (copy, cmdline);
rest = copy;
/*
rec_append (copy);
*/
/* XXX: detect ; and pause ... we could have a label that we go back
* to and evaluate again when one is encountered... t c*/
do {
cargc = 0;
while (rest && cargc < 30 && rest[0] != ';')
{
cargv[cargc++] = rest;
rest = string_chop_head (rest);
}
cargv[cargc] = NULL;
if (cargv[0])
{
CmdIterator *ci = cmd_iterator_new (NULL);
CmdEntry *command;
while ((command = cmd_iterator_next (ci)))
if (!strcmp (cargv[0], command->name))
{
if (command->req_args > cargc - 1)
{
printf ("command '%s' needs %i args, %i given\n",
command->name, command->req_args, cargc - 1);
ret = -1;
}
else
ret = command->fun (cargc, cargv, NULL);
if (ret)
printf ("%s returned: %i\n", command->name, ret);
cmd_iterator_stop (ci);
break;
goto b;
}
b:
if (cargv[0][0])
{
//XXX: somehow pipe this back into the ui
//fprintf (stdout, "unknown command '%s'\n", cargv[0]);
//fflush (NULL);
//sleep (1);
}
}
if (rest && rest[0]==';')
{
rest++;
while (rest[0] == ' ')rest++;
}
} while (rest && rest[0]);
free (copy);
return ret;
}
//#include "common.h"
static int echo_on = 0;
static char prompt[60] = "> ";
void update_prompt ()
{
#if HAVE_ZN
ZnId focus = zn_get_focus (zn);
if (focus)
{
const char *title = zn_loadz (zn, focus);
snprintf (prompt, 59, "%s> ", title);
zn_unref (zn, focus);
} else
#endif
{
snprintf (prompt, 59, "> ");
}
}
int cmd_echo_on (COMMAND_ARGS) /* "echo_on", 0, "", "enables command echoing udring sourcing" */
{
echo_on = 1;
return 0;
}
int argvs_source (const char *path)
{
FILE *f = fopen(path, "r");
if (!f)
return -1;
char commandline[1024]="";
for (fgets(commandline,1023,f);
fgets(commandline,1023,f);)
{
commandline[strlen(commandline)-1]=0; /* remove newline */
if (echo_on && commandline[0])
{
update_prompt ();
printf ("%s", prompt);
printf ("%s\n", commandline);
}
argvs_eval (commandline);
}
fclose (f);
return 0;
}
int cmd_source (COMMAND_ARGS) /* ".", 1, "<path>", "sources script at path"*/
{
return argvs_source (argv[1]);
}
#ifdef USE_LINENOISE
static int str_has_prefix (const char *string, const char *buf)
{
return !strncmp (string, buf, strlen (buf));
}
static void linenoise_completion (const char *buf, linenoiseCompletions *lc)
{
CmdIterator *ci = cmd_iterator_new (NULL);
CmdEntry *command;
while ((command = cmd_iterator_next (ci)))
{
if (str_has_prefix (command->name, buf))
linenoiseAddCompletion (lc, (void*)command->name);
}
/* add completions for some central commands,. like help.. */
}
#endif
int cmd_argvs (COMMAND_ARGS) /* "argvs", 0, "<script | -c <command> >", "the minimalistic shell with int argc, char **argv semantics you now use" */
{
if (argv[1] && !strcmp(argv[1], "-c"))
{
argvs_eval (argv[2]);
}
else if (argc > 1)
{
CmdIterator *ci = cmd_iterator_new (NULL);
CmdEntry *command;
while ((command = cmd_iterator_next (ci)))
{
if (!strcmp (argv[1], command->name))
{
if (command->req_args > argc - 2)
{
printf ("command '%s' needs %i args, %i given\n",
command->name, command->req_args, argc - 2);
cmd_iterator_stop (ci);
return -1;
}
cmd_iterator_stop (ci);
return command->fun (argc-1, argv+1, NULL);
}
}
/* didn't match a command, gamble that it is a file instead */
{
cmd_source (argc, argv, NULL);
}
}
else
{
#ifdef USE_LINENOISE
char *commandline;
linenoiseSetCompletionCallback(linenoise_completion);
update_prompt ();
while ( (commandline = linenoise(prompt))!=NULL &&
strcmp(commandline, "q") &&
strcmp(commandline, "quit"))
{
linenoiseHistoryAdd (commandline);
argvs_eval (commandline);
free (commandline);
update_prompt ();
}
#else
char commandline[1024];
printf ("> ");
fgets (commandline, 1023, stdin);
for (;
strcmp (commandline, "quit\n") && strcmp (commandline,"q\n");
fgets (commandline, 1023, stdin))
{
commandline[strlen(commandline)-1]=0; /* remove newline */
argvs_eval (commandline);
printf ("> ");
}
#endif
}
return 0;
}
int cmd_aa_help (COMMAND_ARGS) /* "?", 0, "[command]", "get's help about a zn subcommand" */
{
if (argv[1])
{
CmdIterator *ci = cmd_iterator_new (NULL);
CmdEntry *command;
while ((command = cmd_iterator_next (ci)))
{
if (!strcmp (argv[1], "?") ||
!strcmp (command->name, argv[1]))
printf ("%s %s\n %s\n\n", command->name, command->args,
command->help);
}
}
else
{
CmdIterator *ci = cmd_iterator_new (NULL);
printf ("Available commands:\n");
CmdEntry *command;
while ((command = cmd_iterator_next (ci)))
printf ("%s ", command->name);
printf ("\n\n");
printf ("Enter ? <commandname> for details on a command\n"
" ? ? for details on all commands\n");
}
return 0;
}
#ifdef COMPILE_TEST
int main (int argc, char **argv)
{
/* just transfer control to the shell */
cmd_argvs (argc, argv, NULL);
return 0;
}
#endif
#ifndef __ARGVS__
#define __ARGVS__
/********/
#define UNUSED __attribute__((__unused__))
#define COMMAND_ARGS int argc UNUSED, char **argv UNUSED, void *userdata
/*******/
void argvs_add (int (*fun)(COMMAND_ARGS),
const char *name,
int required_arguments,
const char *argument_help,
const char *help);
int argvs_eval (const char *cmdline); /* this evals one command,
no newlines or semicolons are taken into account. */
int argvs_source (const char *path);
#endif
#!/bin/sh
echo "/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */"
echo "/* this file is autogenerated by $0 */"
echo "/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */"
# create function declarations for each item
cat $* | grep COMMAND_ARGS | grep ^int | sort | \
sed -e 's/^int */int /' -e 's#(COMMAND_ARGS).*#(COMMAND_ARGS);#'
# create a function that registers all items found
echo "void argvs_register(void) {"
cat $* | grep COMMAND_ARGS | grep ^int | sort | \
sed -e 's/^int */ argvs_add (/ ' -e 's#(COMMAND_ARGS) */\*#, #' -e 's#\*/$#);#'
echo "};"
#ifndef COLORITTO_H
#define COLORITTO_H
#include "luz.h"
typedef enum ColorittoDef {
COLORITTO_SCALAR = 1 << 0,
COLORITTO_LAB = 1 << 1,
COLORITTO_LCH = 1 << 2,
COLORITTO_RGB = 1 << 3,
COLORITTO_SRGB = 1 << 4,
COLORITTO_XYZ = 1 << 5,
COLORITTO_xyY = 1 << 6,
COLORITTO_SPECTRUM_ON_WHITE = 1 << 7,
COLORITTO_SPECTRUM_ON_WHITE_RAMP = 1 << 8,
COLORITTO_SPECTRUM_ON_WHITE_AND_BLACK = 1 << 9,
COLORITTO_SPECTRUM_ON_WHITE_AND_BLACK_RAMP = 1 << 10,
COLORITTO_COATS = 1 << 11,
} ColorittoDef;
typedef struct _Coloritto Coloritto;
/* maybe rename to coloritto_set .. and just Coloritto */
void coloritto_set (Luz *luz, Coloritto *color,
ColorittoDef color_type,
const float *data);
int coloritto_is_set (Coloritto *color,
ColorittoDef color_type);
void coloritto_get (Luz *luz, Coloritto *color,
ColorittoDef color_type,
float *data);
void coloritto_set_name (Coloritto *color, const char *name);
const char *coloritto_get_name (Coloritto *color);
// XXX: todo, make this API share with a paletette storage in Coloritto
// handles.
void coloritto_set_coat (Coloritto *color,
int coat_no,
Coloritto *coat,
float strength);
Coloritto *
coloritto_get_coat (Coloritto *color,
int coat_no,
float *strength);
int coloritto_get_n_coats (Coloritto *color);
void coloritto_set_n_coats (Coloritto *color, int coats);
#if NYI
void coloritto_mix (Coloritto *color,
mix_type,
float mix_amount,
Coloritto *other_color);
#endif
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment