X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/f640bcb3ba82576ec131c564f2b347e2f3accdd3..5c1ae3bc9c3a3cbed192fed2d68fc9b7bbe5c96f:/lib/macros-builtin.c diff --git a/lib/macros-builtin.c b/lib/macros-builtin.c index 63aa930..24822bb 100644 --- a/lib/macros-builtin.c +++ b/lib/macros-builtin.c @@ -21,9 +21,8 @@ /** @file lib/macros-builtin.c * @brief Built-in expansions * - * This is a grab-bag of non-domain-specific expansions. Note that - * documentation will be generated from the comments at the head of - * each function. + * This is a grab-bag of non-domain-specific expansions. Documentation will be + * generated from the comments at the head of each function. */ #include @@ -34,13 +33,24 @@ #include #include #include +#include +#include +#include +#include "hash.h" +#include "mem.h" #include "macros.h" #include "sink.h" #include "syscalls.h" #include "log.h" #include "wstat.h" #include "kvp.h" +#include "split.h" +#include "printf.h" +#include "vector.h" +#include "filepart.h" + +static struct vector include_path; /** @brief Return 1 if @p s is 'true' else 0 */ int mx_str2bool(const char *s) { @@ -52,19 +62,96 @@ const char *mx_bool2str(int n) { return n ? "true" : "false"; } -/* @include{TEMPLATE}@ +/** @brief Write a boolean result */ +int mx_bool_result(struct sink *output, int result) { + if(sink_writes(output, mx_bool2str(result)) < 0) + return -1; + else + return 0; +} + +/** @brief Search the include path */ +char *mx_find(const char *name) { + char *path; + int n; + + if(name[0] == '/') { + if(access(name, O_RDONLY) < 0) { + error(errno, "cannot read %s", name); + return 0; + } + path = xstrdup(name); + } else { + /* Search the include path */ + for(n = 0; n < include_path.nvec; ++n) { + byte_xasprintf(&path, "%s/%s", include_path.vec[n], name); + if(access(path, O_RDONLY) == 0) + break; + } + if(n >= include_path.nvec) { + error(0, "cannot find '%s' in search path", name); + return 0; + } + } + return path; +} + +/*! @include{TEMPLATE}@ + * + * Includes TEMPLATE. + * + * TEMPLATE can be an absolute filename starting with a '/'; only the file with + * exactly this name will be included. * - * Includes TEMPLATE as if its text were substituted for the @include - * expansion. TODO + * Alternatively it can be a relative filename, not starting with a '/'. In + * this case the file will be searched for in the include path. When searching + * paths, unreadable files are treated as if they do not exist (rather than + * matching then producing an error). + * + * If the name chosen ends ".tmpl" then the file will be expanded as a + * template. Anything else is included byte-for-byte without further + * modification. + * + * Only regular files are allowed (no devices, sockets or name pipes). */ static int exp_include(int attribute((unused)) nargs, - char attribute((unused)) **args, - struct sink attribute((unused)) *output, - void attribute((unused)) *u) { - assert(!"TODO implement search path"); + char **args, + struct sink *output, + void *u) { + const char *path; + int fd, n; + char buffer[4096]; + struct stat sb; + + if(!(path = mx_find(args[0]))) { + if(sink_printf(output, "[[cannot find '%s']]", args[0]) < 0) + return 0; + return 0; + } + /* If it's a template expand it */ + if(strlen(path) >= 5 && !strncmp(path + strlen(path) - 5, ".tmpl", 5)) + return mx_expand_file(path, output, u); + /* Read the raw file. As with mx_expand_file() we insist that the file is a + * regular file. */ + if((fd = open(path, O_RDONLY)) < 0) + fatal(errno, "error opening %s", path); + if(fstat(fd, &sb) < 0) + fatal(errno, "error statting %s", path); + if(!S_ISREG(sb.st_mode)) + fatal(0, "%s: not a regular file", path); + while((n = read(fd, buffer, sizeof buffer)) > 0) { + if(sink_write(output, buffer, n) < 0) { + xclose(fd); + return -1; + } + } + if(n < 0) + fatal(errno, "error reading %s", path); + xclose(fd); + return 0; } -/* @include{COMMAND} +/*! @include{COMMAND}@ * * Executes COMMAND via the shell (using "sh -c") and copies its * standard output to the template output. The shell command output @@ -113,7 +200,7 @@ static int exp_shell(int attribute((unused)) nargs, return 0; } -/* @if{CONDITION}{IF-TRUE}{IF-FALSE} +/*! @if{CONDITION}{IF-TRUE}{IF-FALSE}@ * * If CONDITION is "true" then evaluates to IF-TRUE. Otherwise * evaluates to IF-FALSE. The IF-FALSE part is optional. @@ -125,7 +212,7 @@ static int exp_if(int nargs, char *s; int rc; - if((rc = mx_expandstr(args[0], &s, u))) + if((rc = mx_expandstr(args[0], &s, u, "argument #0 (CONDITION)"))) return rc; if(mx_str2bool(s)) return mx_expand(args[1], output, u); @@ -135,7 +222,7 @@ static int exp_if(int nargs, return 0; } -/* @and{BRANCH}{BRANCH}... +/*! @and{BRANCH}{BRANCH}...@ * * Expands to "true" if all the branches are "true" otherwise to "false". If * there are no brances then the result is "true". Only as many branches as @@ -147,23 +234,21 @@ static int exp_and(int nargs, struct sink *output, void *u) { int n, result = 1, rc; - char *s; + char *s, *argname; for(n = 0; n < nargs; ++n) { - if((rc = mx_expandstr(args[n], &s, u))) + byte_xasprintf(&argname, "argument #%d", n); + if((rc = mx_expandstr(args[n], &s, u, argname))) return rc; if(!mx_str2bool(s)) { result = 0; break; } } - if(sink_writes(output, mx_bool2str(result)) < 0) - return -1; - else - return 0; + return mx_bool_result(output, result); } -/* @or{BRANCH}{BRANCH}... +/*! @or{BRANCH}{BRANCH}...@ * * Expands to "true" if any of the branches are "true" otherwise to "false". * If there are no brances then the result is "false". Only as many branches @@ -175,23 +260,21 @@ static int exp_or(int nargs, struct sink *output, void *u) { int n, result = 0, rc; - char *s; + char *s, *argname; for(n = 0; n < nargs; ++n) { - if((rc = mx_expandstr(args[n], &s, u))) + byte_xasprintf(&argname, "argument #%d", n); + if((rc = mx_expandstr(args[n], &s, u, argname))) return rc; if(mx_str2bool(s)) { result = 1; break; } } - if(sink_writes(output, mx_bool2str(result)) < 0) - return -1; - else - return 0; + return mx_bool_result(output, result); } -/* @not{CONDITION} +/*! @not{CONDITION}@ * * Expands to "true" unless CONDITION is "true" in which case "false". */ @@ -199,13 +282,10 @@ static int exp_not(int attribute((unused)) nargs, char **args, struct sink *output, void attribute((unused)) *u) { - if(sink_writes(output, mx_bool2str(!mx_str2bool(args[0]))) < 0) - return -1; - else - return 0; + return mx_bool_result(output, !mx_str2bool(args[0])); } -/* @#{...} +/*! @#{...}@ * * Expands to nothing. The argument(s) are not fully evaluated, and no side * effects occur. @@ -217,7 +297,7 @@ static int exp_comment(int attribute((unused)) nargs, return 0; } -/* @urlquote{STRING} +/*! @urlquote{STRING}@ * * URL-quotes a string, i.e. replaces any characters not safe to use unquoted * in a URL with %-encoded form. @@ -232,19 +312,147 @@ static int exp_urlquote(int attribute((unused)) nargs, return 0; } +/*! @eq{S1}{S2}...@ + * + * Expands to "true" if all the arguments are identical, otherwise to "false" + * (i.e. if any pair of arguments differs). + * + * If there are no arguments then expands to "true". Evaluates all arguments + * (with their side effects) even if that's not strictly necessary to discover + * the result. + */ +static int exp_eq(int nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + int n, result = 1; + + for(n = 1; n < nargs; ++n) { + if(strcmp(args[n], args[0])) { + result = 0; + break; + } + } + return mx_bool_result(output, result); +} + +/*! @ne{S1}{S2}...@ + * + * Expands to "true" if all of the arguments differ from one another, otherwise + * to "false" (i.e. if any value appears more than once). + * + * If there are no arguments then expands to "true". Evaluates all arguments + * (with their side effects) even if that's not strictly necessary to discover + * the result. + */ +static int exp_ne(int nargs, + char **args, + struct sink *output, + void attribute((unused))*u) { + hash *h = hash_new(sizeof (char *)); + int n, result = 1; + + for(n = 0; n < nargs; ++n) + if(hash_add(h, args[n], "", HASH_INSERT)) { + result = 0; + break; + } + return mx_bool_result(output, result); +} + +/*! @discard{...}@ + * + * Expands to nothing. Unlike the comment expansion @#{...}, side effects of + * arguments are not suppressed. So this can be used to surround a collection + * of macro definitions with whitespace, free text commentary, etc. + */ +static int exp_discard(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + return 0; +} + +/*! @define{NAME}{ARG1 ARG2...}{DEFINITION}@ + * + * Define a macro. The macro will be called NAME and will act like an + * expansion. When it is expanded, the expansion is replaced by DEFINITION, + * with each occurence of @ARG1@ etc replaced by the parameters to the + * expansion. + */ +static int exp_define(int attribute((unused)) nargs, + const struct mx_node **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + char **as, *name, *argnames; + int rc, nas; + + if((rc = mx_expandstr(args[0], &name, u, "argument #0 (NAME)"))) + return rc; + if((rc = mx_expandstr(args[1], &argnames, u, "argument #1 (ARGS)"))) + return rc; + as = split(argnames, &nas, 0, 0, 0); + mx_register_macro(name, nas, as, args[2]); + return 0; +} + +/*! @basename{PATH} + * + * Expands to the UNQUOTED basename of PATH. + */ +static int exp_basename(int attribute((unused)) nargs, + char **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + return sink_writes(output, d_basename(args[0])) < 0 ? -1 : 0; +} + +/*! @dirname{PATH} + * + * Expands to the UNQUOTED directory name of PATH. + */ +static int exp_dirname(int attribute((unused)) nargs, + char **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + return sink_writes(output, d_dirname(args[0])) < 0 ? -1 : 0; +} + +/*! @q{STRING} + * + * Expands to STRING. + */ +static int exp_q(int attribute((unused)) nargs, + char **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + return sink_writes(output, args[0]) < 0 ? -1 : 0; +} + +/** @brief Register built-in expansions */ void mx_register_builtin(void) { + mx_register("basename", 1, 1, exp_basename); + mx_register("dirname", 1, 1, exp_dirname); + mx_register("discard", 0, INT_MAX, exp_discard); + mx_register("eq", 0, INT_MAX, exp_eq); mx_register("include", 1, 1, exp_include); - mx_register("shell", 1, 1, exp_shell); - mx_magic_register("if", 2, 3, exp_if); - mx_magic_register("and", 0, INT_MAX, exp_and); - mx_magic_register("or", 0, INT_MAX, exp_or); + mx_register("ne", 0, INT_MAX, exp_ne); mx_register("not", 1, 1, exp_not); - mx_magic_register("#", 0, INT_MAX, exp_comment); + mx_register("shell", 1, 1, exp_shell); mx_register("urlquote", 1, 1, exp_urlquote); - /* TODO: eq */ - /* TODO: ne */ - /* TODO: define */ - /* TODO: discard */ + mx_register("q", 1, 1, exp_q); + mx_register_magic("#", 0, INT_MAX, exp_comment); + mx_register_magic("and", 0, INT_MAX, exp_and); + mx_register_magic("define", 3, 3, exp_define); + mx_register_magic("if", 2, 3, exp_if); + mx_register_magic("or", 0, INT_MAX, exp_or); +} + +/** @brief Add a directory to the search path + * @param s Directory to add + */ +void mx_search_path(const char *s) { + vector_append(&include_path, xstrdup(s)); } /*