295 lines
8.8 KiB
C
295 lines
8.8 KiB
C
/***
|
|
* Option parsing, and error formatting
|
|
* for the command line.
|
|
* @module cl
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <libgen.h>
|
|
#include <errno.h>
|
|
#ifdef BSD
|
|
# include <string.h>
|
|
#endif
|
|
|
|
#include <lua.h>
|
|
#include <lauxlib.h>
|
|
|
|
#include "callisto.h"
|
|
#include "util.h"
|
|
|
|
|
|
static void
|
|
fmesg(lua_State *L, FILE* f, int shift)
|
|
{
|
|
int paramc, i;
|
|
char *progname; /* argv[0] */
|
|
|
|
paramc = lua_gettop(L); /* get parameter count */
|
|
|
|
lua_geti(L, 1 + shift, 0); /* get index 0 of table at index 1 (argv) */
|
|
if (lua_type(L, -1) != LUA_TSTRING) { /* if argv[0] is not a string... */
|
|
luaL_argerror(L, 1, "must have a string in index 0)");
|
|
}
|
|
progname = (char *)lua_tostring(L, -1); /* set progname to argv[0] */
|
|
|
|
/* format using string.format */
|
|
lua_getglobal(L, "string");
|
|
lua_getfield(L, -1, "format");
|
|
|
|
for (i = 2 + shift; i <= paramc; i++) /* for every parameter */
|
|
lua_pushvalue(L, i); /* push argument */
|
|
|
|
lua_call(L, paramc - (1 + shift), 1); /* call string.format */
|
|
|
|
fprintf(f, "%s: %s\n", basename(progname), lua_tostring(L, -1)); /* print */
|
|
}
|
|
|
|
/***
|
|
* Prints a formatted error message to standard error.
|
|
* It looks like so:
|
|
*
|
|
* `progname: message`
|
|
*
|
|
* where *progname* is the name of the current script
|
|
* being executed and *message* is the *message* parameter.
|
|
*
|
|
* The *message* parameter may optionally be followed by
|
|
* a variable number of arguments which are formatted
|
|
* using *string.format*.
|
|
*
|
|
* @function error
|
|
* @usage
|
|
local succeeded, err = io.open("file.txt")
|
|
if not succeeded then
|
|
cl.error(arg, "could not open " .. err)
|
|
end
|
|
* @tparam table arg The command line argument table (this function only uses index *[0]*)
|
|
* @tparam string message The message to print after the program's name. Supports *string.format*-style format specifiers.
|
|
* @param ... Any additional values specified by the format specifiers in the *message* parameter.
|
|
*/
|
|
static int
|
|
cl_error(lua_State *L)
|
|
{
|
|
luaL_checktype(L, 1, LUA_TTABLE);
|
|
luaL_checkstring(L, 2);
|
|
|
|
fmesg(L, stderr, 0);
|
|
|
|
return 0;
|
|
}
|
|
/***
|
|
* Prints a formatted message to standard output.
|
|
* It looks like so:
|
|
*
|
|
* `progname: message`
|
|
*
|
|
* where *progname* is the name of the current script
|
|
* being executed and *message* is the (optionally
|
|
* formatted) *message* parameter.
|
|
*
|
|
* The *message* parameter may optionally be followed by
|
|
* a variable number of arguments which are formatted
|
|
* using *string.format*.
|
|
*
|
|
* @function mesg
|
|
* @usage cl.mesg(arg, "message to stdout")
|
|
* @tparam table arg The command line argument table (this function only uses index *[0]*)
|
|
* @tparam string message The message to print after the program's name. Supports *string.format*-style format specifiers.
|
|
* @param ... Any additional values specified by the format specifiers in the *message* parameter.
|
|
*/
|
|
static int
|
|
cl_mesg(lua_State *L)
|
|
{
|
|
luaL_checktype(L, 1, LUA_TTABLE);
|
|
luaL_checkstring(L, 2);
|
|
|
|
fmesg(L, stdout, 0);
|
|
|
|
return 0;
|
|
}
|
|
/***
|
|
* Convenience function that calls *cl.error* to
|
|
* print a formatted error message to standard error,
|
|
* then terminates script execution and calls *os.exit*.
|
|
*
|
|
* For more information on the *message* parameter,
|
|
* see the *cl.error* function.
|
|
*
|
|
* @function panic
|
|
* @usage
|
|
local succeeded, err, code = io.open("file.txt")
|
|
if not succeeded then
|
|
-- use a custom exit code:
|
|
cl.panic(code, arg, "could not open " .. err)
|
|
-- use the default exit code (1):
|
|
cl.panic(arg, "could not open " .. err)
|
|
end
|
|
* @tparam[opt] integer code The exit code to return to the OS.
|
|
* @tparam table arg The command line argument table (this function only uses index *[0]*)
|
|
* @tparam string message The message to print after the program's name. Supports *string.format*-style format specifiers.
|
|
* @param ... Any additional values specified by the format specifiers in the *message* parameter.
|
|
*/
|
|
static int
|
|
cl_panic(lua_State *L)
|
|
{
|
|
ubyte code;
|
|
ubyte isinteger;
|
|
|
|
isinteger = lua_isinteger(L, 1);
|
|
|
|
/* get exit code */
|
|
code = lua_tointeger(L, 1) * isinteger + 1 * !isinteger;
|
|
luaL_checktype(L, 1 + isinteger, LUA_TTABLE);
|
|
luaL_checkstring(L, 2 + isinteger);
|
|
fmesg(L, stderr, isinteger);
|
|
|
|
/* format using string.format */
|
|
lua_getglobal(L, "os");
|
|
lua_getfield(L, -1, "exit");
|
|
|
|
/* push arguments to os.exit */
|
|
lua_pushinteger(L, code);
|
|
lua_pushboolean(L, 1);
|
|
|
|
lua_call(L, 2, 0); /* call os.exit */
|
|
|
|
return 0;
|
|
}
|
|
/***
|
|
* Parses the command line argument list *arg*.
|
|
* The string *optstring* may contain the
|
|
* following elements: individual characters,
|
|
* and characters followed by a colon.
|
|
* A character followed by a single colon
|
|
* indicates that an argument is to follow
|
|
* the option on the command line. For example,
|
|
* an option string `"x"` permits a **-x** option,
|
|
* and an option string `"x:"` permits a **-x**
|
|
* option that must take an argument. An option
|
|
* string of `"x:yz"` permits a **-x** option that
|
|
* takes an argument, and **-y** and **-z** options,
|
|
* which do not.
|
|
*
|
|
* The function *fn* is run each time a new option
|
|
* is processed. of the argument list. It takes the
|
|
* parameters *opt*, *optarg*, *optindex*, and *opterror*.
|
|
* *opt* is a string containing the option used. It is set
|
|
* to the option the user specified on the command line.
|
|
* If the user specifies an unknown option (one that is
|
|
* not specified in *optstring*), the value of *opt* will
|
|
* be set to nil. If the user specifies an option that
|
|
* requires an argument, but does not specify its argument,
|
|
* the value of *opt* will be the string `"*"` (a single
|
|
* asterisk). The second parameter, *optarg*, is a string
|
|
* containing the option argument (if applicable).
|
|
* *optindex* is an integer that contains the index of the
|
|
* last command line argument processed. The last parameter,
|
|
* *opterror*, is set in case of an option error (if *opt*
|
|
* is nil or set to the string `"*"`), and is set to the
|
|
* option that caused an error (in the case of *opt* being
|
|
* nil, this will be the unknown option the user specified,
|
|
* or in the case of *opt* being the string `"*"`, this
|
|
* will be the option that required an argument).
|
|
*
|
|
* @function parseopts
|
|
* @usage
|
|
cl.parseopts(arg, "abc:", function(opt, optarg, optindex, opterror)
|
|
if opt == 'a' then
|
|
print("-a was used")
|
|
elseif opt == 'b' then
|
|
print("-b was used")
|
|
elseif opt == 'c' then
|
|
print("-c was used, with argument " .. optarg)
|
|
elseif opt == '*' then
|
|
print("missing argument for -" .. opterror)
|
|
elseif opt == nil then -- or ``if not opt``
|
|
print("unknown option -" .. opterror)
|
|
end
|
|
end)
|
|
* @tparam table arg The argument table to parse.
|
|
* @tparam string optstring The option string, containing options that should be parsed.
|
|
* @tparam function fn The function to be run each time a new option is specified on the command line.
|
|
*/
|
|
static int
|
|
cl_parseopts(lua_State *L)
|
|
{
|
|
int argc; /* command line argument count */
|
|
int i;
|
|
char ch, s[2]; /* opt character and string */
|
|
char loptopt[2]; /* opterror, returned to Lua */
|
|
char **argv; /* parameter 1 (table), command line argument vector */
|
|
char *optstring; /* parameter 2 (string), optstring passed to getopt */
|
|
|
|
optstring = malloc(lua_rawlen(L, 2) * sizeof(char *));
|
|
|
|
/* parameter type checking */
|
|
luaL_checktype(L, 1, LUA_TTABLE);
|
|
strlcpy(optstring, luaL_checkstring(L, 2), lua_rawlen(L, 2) * sizeof(char *));
|
|
luaL_checktype(L, 3, LUA_TFUNCTION);
|
|
|
|
argc = (int)lua_rawlen(L, 1) + 1; /* get argv length */
|
|
argv = lua_newuserdatauv(L, (argc + 1) * sizeof(char *), 0);
|
|
argv[argc] = NULL;
|
|
|
|
for (i = 0; i < argc; i++) { /* for every argument */
|
|
lua_pushinteger(L, i); /* push argv index */
|
|
lua_gettable(L, 1); /* push argv[i] */
|
|
argv[i] = (char *)luaL_checkstring(L, -1);
|
|
}
|
|
|
|
if (optstring[0] == ':')
|
|
return luaL_argerror(L, 2, "option string must not start with a colon (:)");
|
|
|
|
strprepend(optstring, ":");
|
|
|
|
/* getopt loop */
|
|
while ((ch = getopt(argc, argv, optstring)) != -1) {
|
|
/* construct string containing ch */
|
|
s[0] = ch;
|
|
s[1] = 0;
|
|
lua_pushvalue(L, 3);
|
|
|
|
/* first function parameter: opt */
|
|
if (ch == '?') /* in case of unknown option */
|
|
lua_pushnil(L);
|
|
else if (ch == ':') /* in case of missing option argument */
|
|
lua_pushliteral(L, "*");
|
|
else /* otherwise just push the option character */
|
|
lua_pushstring(L, s);
|
|
|
|
/* second function parameter: optarg */
|
|
lua_pushstring(L, optarg);
|
|
/* third function parameter: optindex */
|
|
lua_pushinteger(L, optind);
|
|
/* fourth function parameter: opterror
|
|
* (only non-nil on error) */
|
|
if (optopt == '?') { /* error was not encountered */
|
|
lua_pushnil(L);
|
|
} else {
|
|
loptopt[0] = optopt;
|
|
loptopt[1] = 0;
|
|
lua_pushstring(L, loptopt);
|
|
}
|
|
|
|
lua_pcall(L, 4, 0, 0); /* call Lua function */
|
|
}
|
|
free(optstring);
|
|
return 0;
|
|
}
|
|
|
|
static const luaL_Reg cllib[] = {
|
|
{"mesg", cl_mesg},
|
|
{"error", cl_error},
|
|
{"panic", cl_panic},
|
|
{"parseopts", cl_parseopts},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
int
|
|
luaopen_cl(lua_State *L)
|
|
{
|
|
luaL_newlib(L, cllib);
|
|
return 0;
|
|
}
|