From f640bcb3ba82576ec131c564f2b347e2f3accdd3 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sun, 4 May 2008 00:21:25 +0100 Subject: [PATCH] Main template expander and some built-in expansions. All untested. Organization: Straylight/Edgeware From: Richard Kettlewell --- lib/Makefile.am | 2 +- lib/macros-builtin.c | 257 +++++++++++++++++++++++++++++++++++++++++++ lib/macros.c | 205 ++++++++++++++++++++++++++++++++++ lib/macros.h | 52 ++++++++- 4 files changed, 514 insertions(+), 2 deletions(-) create mode 100644 lib/macros-builtin.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 852852d..2d3856a 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -60,7 +60,7 @@ libdisorder_a_SOURCES=charset.c charset.h \ kvp.c kvp.h \ log.c log.h log-impl.h \ logfd.c logfd.h \ - macros.c macros.h \ + macros.c macros-builtin.c macros.h \ mem.c mem.h mem-impl.h \ mime.h mime.c \ mixer.c mixer.h mixer-oss.c mixer-alsa.c \ diff --git a/lib/macros-builtin.c b/lib/macros-builtin.c new file mode 100644 index 0000000..63aa930 --- /dev/null +++ b/lib/macros-builtin.c @@ -0,0 +1,257 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2008 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/** @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. + */ + +#include +#include "types.h" + +#include +#include +#include +#include +#include + +#include "macros.h" +#include "sink.h" +#include "syscalls.h" +#include "log.h" +#include "wstat.h" +#include "kvp.h" + +/** @brief Return 1 if @p s is 'true' else 0 */ +int mx_str2bool(const char *s) { + return !strcmp(s, "true"); +} + +/** @brief Return "true" if @p n is nonzero else "false" */ +const char *mx_bool2str(int n) { + return n ? "true" : "false"; +} + +/* @include{TEMPLATE}@ + * + * Includes TEMPLATE as if its text were substituted for the @include + * expansion. TODO + */ +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"); +} + +/* @include{COMMAND} + * + * Executes COMMAND via the shell (using "sh -c") and copies its + * standard output to the template output. The shell command output + * is not expanded or modified in any other way. + * + * The shell command's standard error is copied to the error log. + * + * If the shell exits nonzero then this is reported to the error log + * but otherwise no special action is taken. + */ +static int exp_shell(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + int w, p[2], n; + char buffer[4096]; + pid_t pid; + + xpipe(p); + if(!(pid = xfork())) { + exitfn = _exit; + xclose(p[0]); + xdup2(p[1], 1); + xclose(p[1]); + execlp("sh", "sh", "-c", args[0], (char *)0); + fatal(errno, "error executing sh"); + } + xclose(p[1]); + while((n = read(p[0], buffer, sizeof buffer))) { + if(n < 0) { + if(errno == EINTR) + continue; + else + fatal(errno, "error reading from pipe"); + } + if(output->write(output, buffer, n) < 0) + return -1; + } + xclose(p[0]); + while((n = waitpid(pid, &w, 0)) < 0 && errno == EINTR) + ; + if(n < 0) + fatal(errno, "error calling waitpid"); + if(w) + error(0, "shell command '%s' %s", args[0], wstat(w)); + return 0; +} + +/* @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. + */ +static int exp_if(int nargs, + const struct mx_node **args, + struct sink *output, + void *u) { + char *s; + int rc; + + if((rc = mx_expandstr(args[0], &s, u))) + return rc; + if(mx_str2bool(s)) + return mx_expand(args[1], output, u); + else if(nargs > 2) + return mx_expand(args[2], output, u); + else + return 0; +} + +/* @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 + * necessary to compute the answer are evaluated (starting from the first one), + * so if later branches have side effects they may not take place. + */ +static int exp_and(int nargs, + const struct mx_node **args, + struct sink *output, + void *u) { + int n, result = 1, rc; + char *s; + + for(n = 0; n < nargs; ++n) { + if((rc = mx_expandstr(args[n], &s, u))) + return rc; + if(!mx_str2bool(s)) { + result = 0; + break; + } + } + if(sink_writes(output, mx_bool2str(result)) < 0) + return -1; + else + return 0; +} + +/* @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 + * as necessary to compute the answer are evaluated (starting from the first + * one), so if later branches have side effects they may not take place. + */ +static int exp_or(int nargs, + const struct mx_node **args, + struct sink *output, + void *u) { + int n, result = 0, rc; + char *s; + + for(n = 0; n < nargs; ++n) { + if((rc = mx_expandstr(args[n], &s, u))) + return rc; + if(mx_str2bool(s)) { + result = 1; + break; + } + } + if(sink_writes(output, mx_bool2str(result)) < 0) + return -1; + else + return 0; +} + +/* @not{CONDITION} + * + * Expands to "true" unless CONDITION is "true" in which case "false". + */ +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; +} + +/* @#{...} + * + * Expands to nothing. The argument(s) are not fully evaluated, and no side + * effects occur. + */ +static int exp_comment(int attribute((unused)) nargs, + const struct mx_node attribute((unused)) **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + return 0; +} + +/* @urlquote{STRING} + * + * URL-quotes a string, i.e. replaces any characters not safe to use unquoted + * in a URL with %-encoded form. + */ +static int exp_urlquote(int attribute((unused)) nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + if(sink_writes(output, urlencodestring(args[0])) < 0) + return -1; + else + return 0; +} + +void mx_register_builtin(void) { + 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("not", 1, 1, exp_not); + mx_magic_register("#", 0, INT_MAX, exp_comment); + mx_register("urlquote", 1, 1, exp_urlquote); + /* TODO: eq */ + /* TODO: ne */ + /* TODO: define */ + /* TODO: discard */ +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/macros.c b/lib/macros.c index e617203..31d765e 100644 --- a/lib/macros.c +++ b/lib/macros.c @@ -28,14 +28,44 @@ #include #include #include +#include +#include +#include +#include +#include #include "macros.h" #include "mem.h" #include "vector.h" #include "log.h" +#include "hash.h" +#include "sink.h" +#include "syscalls.h" VECTOR_TYPE(mx_node_vector, const struct mx_node *, xrealloc); +/** @brief Definition of an expansion */ +struct expansion { + /** @brief Minimum permitted arguments */ + int min; + + /** @brief Maximum permitted arguments */ + int max; + + /** @brief Flags */ + unsigned flags; + + /** @brief Callback (cast to appropriate type) */ + void (*callback)(); +}; + +/** @brief Expansion takes parsed templates, not strings */ +#define EXP_MAGIC 0x0001 + +static hash *expansions; + +/* Parsing ------------------------------------------------------------------ */ + /** @brief Parse a template * @param filename Input filename (for diagnostics) * @param line Line number (use 1 on initial call) @@ -236,6 +266,181 @@ char *mx_dump(const struct mx_node *m) { return d->vec; } +/* Expansion registration --------------------------------------------------- */ + +static void mx__register(unsigned flags, + const char *name, + int min, + int max, + void (*callback)()) { + struct expansion e[1]; + + if(!expansions) + expansions = hash_new(sizeof(struct expansion)); + e->min = min; + e->max = max; + e->flags = flags; + e->callback = callback; + hash_add(expansions, name, &e, HASH_INSERT_OR_REPLACE); +} + +/** @brief Register a simple expansion rule + * @param name Name + * @param min Minimum number of arguments + * @param max Maximum number of arguments + * @param callback Callback to write output + */ +void mx_register(const char *name, + int min, + int max, + mx_simple_callback *callback) { + mx__register(0, name, min, max, (void (*)())callback); +} + +/** @brief Register a magic expansion rule + * @param name Name + * @param min Minimum number of arguments + * @param max Maximum number of arguments + * @param callback Callback to write output + */ +void mx_magic_register(const char *name, + int min, + int max, + mx_magic_callback *callback) { + mx__register(EXP_MAGIC, name, min, max, (void (*)())callback); +} + +/* Expansion ---------------------------------------------------------------- */ + +/** @brief Expand a template + * @param m Where to start + * @param output Where to send output + * @param u User data + * @return 0 on success, non-0 on error∑ + * + * If a sink write fails then -1 is returned. If any callback returns non-zero + * then that value is returned. It is suggested that callbacks adopt this + * policy too and use positive values to mean other kinds of fatal error. + */ +int mx_expand(const struct mx_node *m, + struct sink *output, + void *u) { + const struct expansion *e; + int rc; + + if(!m) + return 0; + switch(m->type) { + case MX_TEXT: + if(sink_writes(output, m->text) < 0) + return -1; + break; + case MX_EXPANSION: + if(!(e = hash_find(expansions, m->name))) { + error(0, "%s:%d: unknown expansion name '%s'", + m->filename, m->line, m->name); + if(sink_printf(output, "[[%s unknown]]", m->name) < 0) + return -1; + } else if(m->nargs < e->min) { + error(0, "%s:%d: expansion '%s' requires %d args, only %d given", + m->filename, m->line, m->name, e->min, m->nargs); + if(sink_printf(output, "[[%s too few args]]", m->name) < 0) + return -1; + } else if(m->nargs > e->max) { + error(0, "%s:%d: expansion '%s' takes at most %d args, but %d given", + m->filename, m->line, m->name, e->max, m->nargs); + if(sink_printf(output, "[[%s too many args]]", m->name) < 0) + return -1; + } else if(e->flags & EXP_MAGIC) { + /* Magic callbacks we can call directly */ + if((rc = ((mx_magic_callback *)e->callback)(m->nargs, + m->args, + output, + u))) + return rc; + } else { + /* For simple callbacks we expand their arguments for them */ + char **args = xcalloc(1 + m->nargs, sizeof (char *)); + int n; + + for(n = 0; n < m->nargs; ++n) + if((rc = mx_expandstr(m->args[n], &args[n], u))) + return rc; + args[n] = NULL; + if((rc = ((mx_simple_callback *)e->callback)(m->nargs, + args, + output, + u))) + return rc; + } + break; + default: + assert(!"invalid m->type"); + } + return mx_expand(m, output, u); +} + +/** @brief Expand a template storing the result in a string + * @param m Where to start + * @param sp Where to store string + * @param u User data + * @return 0 on success, non-0 on error + * + * Same return conventions as mx_expand(). This wrapper is slightly more + * convenient to use from 'magic' expansions. + */ +int mx_expandstr(const struct mx_node *m, + char **sp, + void *u) { + struct dynstr d[1]; + int rc; + + if(!(rc = mx_expand(m, sink_dynstr(d), u))) { + dynstr_terminate(d); + *sp = d->vec; + } + return rc; +} + +/** @brief Expand a template file + * @param path Filename + * @param output Where to send output + * @param u User data + * @return 0 on success, non-0 on error + * + * Same return conventions as mx_expand(). + */ +int mx_expand_file(const char *path, + struct sink *output, + void *u) { + int fd, n; + struct stat sb; + char *b; + off_t sofar; + + 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); + sofar = 0; + b = xmalloc_noptr(sb.st_size); + while(sofar < sb.st_size) { + n = read(fd, b + sofar, sb.st_size - sofar); + if(n > 0) + sofar += n; + else if(n == 0) + fatal(0, "unexpected EOF reading %s", path); + else if(errno != EINTR) + fatal(errno, "error reading %s", path); + } + xclose(fd); + return mx_expand(mx_parse(path, 1, b, b + sb.st_size), + output, + u); +} + /* Local Variables: c-basic-offset:2 diff --git a/lib/macros.h b/lib/macros.h index 0b2dc08..aa20f2d 100644 --- a/lib/macros.h +++ b/lib/macros.h @@ -25,6 +25,8 @@ #ifndef MACROS_H #define MACROS_H +struct sink; + /** @brief One node in a macro expansion parse tree */ struct mx_node { /** @brief Next element or NULL at end of list */ @@ -62,9 +64,57 @@ const struct mx_node *mx_parse(const char *filename, int line, const char *input, const char *end); - char *mx_dump(const struct mx_node *m); + +/** @brief Callback for simple expansions + * @param nargs Number of arguments + * @param args Pointer to array of arguments + * @param output Where to send output + * @param u User data + * @return 0 on success, non-zero on error + */ +typedef int mx_simple_callback(int nargs, + char **args, + struct sink *output, + void *u); + +/** @brief Callback for magic expansions + * @param nargs Number of arguments + * @param args Pointer to array of arguments + * @param output Where to send output + * @param u User data + * @return 0 on success, non-zero on error + */ +typedef int mx_magic_callback(int nargs, + const struct mx_node **args, + struct sink *output, + void *u); + +void mx_register(const char *name, + int min, + int max, + mx_simple_callback *callback); +void mx_magic_register(const char *name, + int min, + int max, + mx_magic_callback *callback); + +void mx_register_builtin(void); + +int mx_expand_file(const char *path, + struct sink *output, + void *u); +int mx_expand(const struct mx_node *m, + struct sink *output, + void *u); +int mx_expandstr(const struct mx_node *m, + char **sp, + void *u); + +int mx_str2bool(const char *s); +const char *mx_bool2str(int n); + #endif /* MACROS_H */ -- [mdw]