From: Richard Kettlewell Date: Sat, 3 May 2008 19:15:50 +0000 (+0100) Subject: Synchronize with disorder.dev X-Git-Tag: 4.0~76^2~63 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/9944848ae35d2a1cc38c48eaee41920d0d510439?hp=21edea328f05e59abb83065c82ff44e6100bd5e4 Synchronize with disorder.dev --- diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index e714a22..c9b2061 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -907,14 +907,20 @@ character reference, e.g. \fBý\fR. Use \fB@\fR to insert a literal \fB@\fR without falling foul of the expansion syntax. .SS "Expansion Syntax" -Expansions are surrounded by at ("@") symbols take the form of a keyword -followed by zero or more arguments. -Arguments may either be quoted by curly brackets ("{" and "}") or separated +An expansion starts with an at ("@") symbol and takes the form of an expansion +name followed by zero or more arguments. +.PP +Arguments can be quoted by curly brackets ("{" and "}") or separated by colons (":"). Both kinds may be mixed in a single expansion, though doing so seems likely to cause confusion. The descriptions below contain suggested forms for each expansion. .PP +An expansion is terminated by another "@" symbol, but this is optional in the +specific case that the last argument is quoted by curly brackets and it is +followed by whitespace and then some character that is not "{" (since that +could be interpreted as a further argument). +.PP Leading and trailing whitespace in unquoted arguments is ignored, as is whitespace (including newlines) following a close bracket ("}"). .PP diff --git a/server/cgi.c b/server/cgi.c index 2017d1b..ef693da 100644 --- a/server/cgi.c +++ b/server/cgi.c @@ -53,6 +53,7 @@ #include "printf.h" #include "mime.h" #include "unicode.h" +#include "hash.h" struct kvp *cgi_args; @@ -64,6 +65,41 @@ struct column { char **columns; }; +/* macros */ +struct cgi_macro { + int nargs; + char **args; + const char *value; +}; + +static hash *cgi_macros; + +/** @brief Parse of a template */ +struct cgi_element { + /** @brief Next element */ + struct cgi_element *next; + + /** @brief Element type */ + int type; +#define ELEMENT_TEXT 0 +#define ELEMENT_EXPANSION 1 + + /** @brief Line number at start of element */ + int line; + + /** @brief Plain text */ + char *text; + + /** @brief Expansion name */ + char *name; + + /** @brief Argument count */ + int nargs; + + /** @brief Argument values (NOT recursively expanded) */ + char **args; +}; + #define RELIST(x) struct re *x, **x##_tail = &x static int have_read_options; @@ -71,6 +107,12 @@ static struct kvp *labels; static struct column *columns; static void include_options(const char *name); +static void cgi_expand_parsed(const char *name, + struct cgi_element *head, + const struct cgi_expansion *expansions, + size_t nexpansions, + cgi_sink *output, + void *u); static void cgi_parse_get(void) { const char *q; @@ -360,35 +402,44 @@ void cgi_expand(const char *template, cgi_expand_string(template, b, expansions, nexpansions, output, u); } -void cgi_expand_string(const char *name, - const char *template, - const struct cgi_expansion *expansions, - size_t nexpansions, - cgi_sink *output, - void *u) { - int braces, n, m, line = 1, sline; - char *argname; +/** @brief Return a linked list of the parse of @p template */ +static struct cgi_element *cgi_parse_string(const char *name, + const char *template) { + int braces, line = 1, sline; const char *p; struct vector v; struct dynstr d; - cgi_sink parameter_output; - + struct cgi_element *head = 0, **tailp = &head, *e; + while(*template) { if(*template != '@') { - p = template; - while(*p && *p != '@') { - if(*p == '\n') ++line; - ++p; + sline = line; + dynstr_init(&d); + /* Gather up text without any expansions in. */ + while(*template && *template != '@') { + if(*template == '\n') + ++line; + dynstr_append(&d, *template++); } - output->sink->write(output->sink, template, p - template); - template = p; + dynstr_terminate(&d); + e = xmalloc(sizeof *e); + e->next = 0; + e->line = sline; + e->type = ELEMENT_TEXT; + e->text = d.vec; + *tailp = e; + tailp = &e->next; continue; } vector_init(&v); braces = 0; + p = template; ++template; sline = line; while(*template != '@') { + /* Skip whitespace */ + while(isspace((unsigned char)*template)) + ++template; dynstr_init(&d); if(*template == '{') { /* bracketed arg */ @@ -401,16 +452,21 @@ void cgi_expand_string(const char *name, } dynstr_append(&d, *template++); } - if(!*template) fatal(0, "%s:%d: unterminated expansion", name, sline); + if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'", + name, sline, (int)(template - p), p); ++template; - /* skip whitespace after closing bracket */ - while(isspace((unsigned char)*template)) - ++template; + if(isspace((unsigned char)*template)) { + /* We have @{...} */ + for(p = template; isspace((unsigned char)*p); ++p) + ; + /* Now we are looking at . If it's "{" then that + * must be the next argument. Otherwise we infer that this + * is really the end of the expansion. */ + if(*p != '{') + goto finished_expansion; + } } else { /* unbracketed arg */ - /* leading whitespace is not significant in unquoted args */ - while(isspace((unsigned char)*template)) - ++template; while(*template && *template != '@' && *template != '{' && *template != ':') { if(*template == '\n') ++line; @@ -418,7 +474,8 @@ void cgi_expand_string(const char *name, } if(*template == ':') ++template; - if(!*template) fatal(0, "%s:%d: unterminated expansion", name, sline); + if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'", + name, sline, (int)(template - p), p); /* trailing whitespace is not significant in unquoted args */ while(d.nvec && (isspace((unsigned char)d.vec[d.nvec - 1]))) --d.nvec; @@ -427,37 +484,154 @@ void cgi_expand_string(const char *name, vector_append(&v, d.vec); } ++template; + finished_expansion: vector_terminate(&v); /* @@ terminates this file */ if(v.nvec == 0) break; - if((n = table_find(expansions, - offsetof(struct cgi_expansion, name), - sizeof (struct cgi_expansion), - nexpansions, - v.vec[0])) < 0) - fatal(0, "%s:%d: unknown expansion '%s'", name, line, v.vec[0]); - if(v.nvec - 1 < expansions[n].minargs) - fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)", - name, line, v.vec[0], expansions[n].minargs, v.nvec - 1); - if(v.nvec - 1 > expansions[n].maxargs) - fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)", - name, line, v.vec[0], expansions[n].maxargs, v.nvec - 1); - /* for ordinary expansions, recursively expand the arguments */ - if(!(expansions[n].flags & EXP_MAGIC)) { - for(m = 1; m < v.nvec; ++m) { - dynstr_init(&d); - byte_xasprintf(&argname, "<%s:%d arg #%d>", name, sline, m); - parameter_output.quote = 0; - parameter_output.sink = sink_dynstr(&d); - cgi_expand_string(argname, v.vec[m], - expansions, nexpansions, - ¶meter_output, u); - dynstr_terminate(&d); - v.vec[m] = d.vec; + e = xmalloc(sizeof *e); + e->next = 0; + e->line = sline; + e->type = ELEMENT_EXPANSION; + e->name = v.vec[0]; + e->nargs = v.nvec - 1; + e->args = &v.vec[1]; + *tailp = e; + tailp = &e->next; + } + return head; +} + +void cgi_expand_string(const char *name, + const char *template, + const struct cgi_expansion *expansions, + size_t nexpansions, + cgi_sink *output, + void *u) { + cgi_expand_parsed(name, cgi_parse_string(name, template), + expansions, nexpansions, output, u); +} + +/** @brief Expand a list of arguments in place */ +static void cgi_expand_all_args(const char *name, + int line, + const struct cgi_expansion *expansions, + size_t nexpansions, + void *u, + char **args, + int nargs) { + int n; + struct dynstr d; + char *argname; + cgi_sink parameter_output; + + for(n = 0; n < nargs; ++n) { + dynstr_init(&d); + byte_xasprintf(&argname, "<%s:%d arg #%d>", name, line,n); + parameter_output.quote = 0; + parameter_output.sink = sink_dynstr(&d); + cgi_expand_string(argname, args[n], + expansions, nexpansions, + ¶meter_output, u); + dynstr_terminate(&d); + args[n] = d.vec; + } +} + + +/** @brief Substitute macro arguments in place */ +static void cgi_substitute_args(const char *name, + struct cgi_element *head, + const struct cgi_macro *macro, + char **values) { + struct cgi_element *e; + int n; + + for(e = head; e; e = e->next) { + if(e->type != ELEMENT_EXPANSION) + continue; + /* See if this is an argument name */ + for(n = 0; n < macro->nargs; ++n) + if(!strcmp(e->name, macro->args[n])) + break; + if(n < macro->nargs) { + /* It is! */ + if(e->nargs != 0) + fatal(0, "%s:%d: macro argument (%s) cannot take parameters", + name, e->line, e->name); + /* Replace it with the argument text */ + e->type = ELEMENT_TEXT; + e->text = values[n]; + continue; + } + /* It's not a macro argument. We must recurse into its arguments to + * substitute the macro arguments. */ + /* TODO */ + /* In order to do this we must parse it and our callers must expect the + * parsed form. We're not ready for this yet... */ + } +} + +static void cgi_expand_parsed(const char *name, + struct cgi_element *head, + const struct cgi_expansion *expansions, + size_t nexpansions, + cgi_sink *output, + void *u) { + int n; + const struct cgi_macro *macro; + struct cgi_element *e, *macro_head; + + for(e = head; e; e = e->next) { + switch(e->type) { + case ELEMENT_TEXT: + output->sink->write(output->sink, e->text, strlen(e->text)); + break; + case ELEMENT_EXPANSION: + if((n = table_find(expansions, + offsetof(struct cgi_expansion, name), + sizeof (struct cgi_expansion), + nexpansions, + e->name)) >= 0) { + /* We found a built-in */ + if(e->nargs < expansions[n].minargs) + fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)", + name, e->line, e->name, expansions[n].minargs, e->nargs); + if(e->nargs > expansions[n].maxargs) + fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)", + name, e->line, e->name, expansions[n].maxargs, e->nargs); + /* for ordinary expansions, recursively expand the arguments */ + if(!(expansions[n].flags & EXP_MAGIC)) + cgi_expand_all_args(name, e->line, expansions, nexpansions, u, + e->args, e->nargs); + expansions[n].handler(e->nargs, e->args, output, u); + } else if(cgi_macros && (macro = hash_find(cgi_macros, e->name))) { + /* We found a macro */ + if(e->nargs != macro->nargs) + fatal(0, "%s:%d: wrong number of arguments to @%s@ (need %d, got %d)", + name, e->line, e->name, macro->nargs, e->nargs); + /* Expand arguments */ + cgi_expand_all_args(name, e->line, expansions, nexpansions, u, + e->args, e->nargs); + /* Parse the macro value. Doing this every time isn't very efficient, + * but NB that we mess with the result of the parse, so for the time + * being we do need to do it. */ + macro_head = cgi_parse_string(e->name, macro->value); + /* Substitute in argument values */ + cgi_substitute_args(name, macro_head, macro, e->args); + /* Expand the result */ + cgi_expand_parsed(e->name, + macro_head, + expansions, + nexpansions, + output, + u); + } else { + /* Totally undefined */ + fatal(0, "%s:%d: unknown expansion '%s'", name, e->line, e->name); } + break; } - expansions[n].handler(v.nvec - 1, v.vec + 1, output, u); } } @@ -625,6 +799,20 @@ char **cgi_columns(const char *name, int *ncolumns) { } } +void cgi_define(const char *name, + int nargs, + char **args, + const char *value) { + struct cgi_macro m; + + if(!cgi_macros) + cgi_macros = hash_new(sizeof(struct cgi_macro)); + m.nargs = nargs; + m.args = args; + m.value = value; + hash_add(cgi_macros, name, &m, HASH_INSERT_OR_REPLACE); +} + /* Local Variables: c-basic-offset:2 diff --git a/server/cgi.h b/server/cgi.h index 2381c03..8a76d31 100644 --- a/server/cgi.h +++ b/server/cgi.h @@ -65,6 +65,11 @@ struct cgi_expansion { void (*handler)(int nargs, char **args, cgi_sink *output, void *u); }; +void cgi_define(const char *name, + int nargs, + char **args, + const char *value); + void cgi_expand(const char *name, const struct cgi_expansion *expansions, size_t nexpansions, diff --git a/server/dcgi.c b/server/dcgi.c index 33630fa..16a5a6c 100644 --- a/server/dcgi.c +++ b/server/dcgi.c @@ -1760,6 +1760,17 @@ static void exp_image(int attribute((unused)) nargs, cgi_output(output, "/disorder/%s", imagestem); } +static void exp_define(int attribute((unused)) nargs, + char **args, + cgi_sink attribute((unused)) *output, + void attribute((unused)) *u) { + const char *n = args[0], *a = args[1], *v = args[2]; + int nas; + char **as = split(a, &nas, 0, 0, 0); + + cgi_define(n, nas, as, v); +} + static const struct cgi_expansion expansions[] = { { "#", 0, INT_MAX, EXP_MAGIC, exp_comment }, { "action", 0, 0, 0, exp_action }, @@ -1767,6 +1778,7 @@ static const struct cgi_expansion expansions[] = { { "arg", 1, 1, 0, exp_arg }, { "basename", 0, 1, 0, exp_basename }, { "choose", 2, 2, EXP_MAGIC, exp_choose }, + { "define", 3, 3, EXP_MAGIC, exp_define }, { "dirname", 0, 1, 0, exp_dirname }, { "enabled", 0, 0, 0, exp_enabled }, { "eq", 2, 2, 0, exp_eq },