From 2257512d31e410938ae2f7f7f511830989c58f7f Mon Sep 17 00:00:00 2001 Message-Id: <2257512d31e410938ae2f7f7f511830989c58f7f.1715012608.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sun, 11 May 2008 12:38:59 +0100 Subject: [PATCH] dirname/basename expansions; template fiddling Organization: Straylight/Edgeware From: Richard Kettlewell --- lib/filepart.c | 95 +++++++++++++++++--- lib/filepart.h | 2 + lib/macros-builtin.c | 35 ++++++-- lib/t-filepart.c | 53 +++++++++--- lib/t-macros.c | 6 ++ lib/test.c | 47 +++++++++- lib/test.h | 9 +- server/actions.c | 2 +- server/cgimain.c | 5 +- server/macros-disorder.c | 1 + templates/macros.tmpl | 8 +- templates/playing.tmpl | 177 +++++++++++++++++++++----------------- templates/stdhead.tmpl | 12 +-- templates/stylesheet.tmpl | 14 +-- 14 files changed, 331 insertions(+), 135 deletions(-) diff --git a/lib/filepart.c b/lib/filepart.c index c080b4d..7af7de0 100644 --- a/lib/filepart.c +++ b/lib/filepart.c @@ -25,30 +25,103 @@ #include "types.h" #include +#include +#include #include "filepart.h" #include "mem.h" +/** @brief Parse a filename + * @param path Filename to parse + * @param Where to put directory name, or NULL + * @param Where to put basename, or NULL + */ +static void parse_filename(const char *path, + char **dirnamep, + char **basenamep) { + const char *s, *e = path + strlen(path); + + /* Strip trailing slashes. We never take these into account. */ + while(e > path && e[-1] == '/') + --e; + if(e == path) { + /* The path is empty or contains only slashes */ + if(*path) { + if(dirnamep) + *dirnamep = xstrdup("/"); + if(basenamep) + *basenamep = xstrdup("/"); + } else { + if(dirnamep) + *dirnamep = xstrdup(""); + if(basenamep) + *basenamep = xstrdup(""); + } + } else { + /* The path isn't empty and has more than just slashes. e therefore now + * points at the end of the basename. */ + s = e; + while(s > path && s[-1] != '/') + --s; + /* Now s points at the start of the basename */ + if(basenamep) + *basenamep = xstrndup(s, e - s); + if(s > path) { + --s; + /* s must now be pointing at a '/' before the basename */ + assert(*s == '/'); + while(s > path && s[-1] == '/') + --s; + /* Now s must be pointing at the last '/' after the dirname */ + assert(*s == '/'); + if(s == path) { + /* If we reached the start we must be at the root */ + if(dirnamep) + *dirnamep = xstrdup("/"); + } else { + /* There's more than just the root here */ + if(dirnamep) + *dirnamep = xstrndup(path, s - path); + } + } else { + /* There wasn't a slash */ + if(dirnamep) + *dirnamep = xstrdup("."); + } + } +} + /** @brief Return the directory part of @p path * @param path Path to parse * @return Directory part of @p path * * Extracts the directory part of @p path. This is a simple lexical * transformation and no canonicalization is performed. The result will only - * ever end "/" if it is the root directory. + * ever end "/" if it is the root directory. The result will be "." if there + * is no directory part. */ char *d_dirname(const char *path) { - const char *s; + char *d = 0; - if((s = strrchr(path, '/'))) { - while(s > path && s[-1] == '/') - --s; - if(s == path) - return xstrdup("/"); - else - return xstrndup(path, s - path); - } else - return xstrdup("."); + parse_filename(path, &d, 0); + assert(d != 0); + return d; +} + +/** @brief Return the basename part of @p path + * @param Path to parse + * @return Base part of @p path + * + * Extracts the base part of @p path. This is a simple lexical transformation + * and no canonicalization is performed. The result is always newly allocated + * even if compares equal to @p path. + */ +char *d_basename(const char *path) { + char *b = 0; + + parse_filename(path, 0, &b); + assert(b != 0); + return b; } /** @brief Find the extension part of @p path diff --git a/lib/filepart.h b/lib/filepart.h index fdc9074..9b6cf62 100644 --- a/lib/filepart.h +++ b/lib/filepart.h @@ -24,6 +24,8 @@ #ifndef FILEPART_H #define FILEPART_H +char *d_basename(const char *path); + char *d_dirname(const char *path); /* return the directory name part of @path@ */ diff --git a/lib/macros-builtin.c b/lib/macros-builtin.c index d8551fb..c96506f 100644 --- a/lib/macros-builtin.c +++ b/lib/macros-builtin.c @@ -48,6 +48,7 @@ #include "split.h" #include "printf.h" #include "vector.h" +#include "filepart.h" static struct vector include_path; @@ -395,13 +396,32 @@ static int exp_define(int attribute((unused)) nargs, 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; +} + /** @brief Register built-in expansions */ void mx_register_builtin(void) { - 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); + 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); @@ -409,6 +429,11 @@ void mx_register_builtin(void) { mx_register("not", 1, 1, exp_not); mx_register("shell", 1, 1, exp_shell); mx_register("urlquote", 1, 1, exp_urlquote); + 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 diff --git a/lib/t-filepart.c b/lib/t-filepart.c index d2e741b..3eff101 100644 --- a/lib/t-filepart.c +++ b/lib/t-filepart.c @@ -20,20 +20,47 @@ */ #include "test.h" +#define check_filepart(PATH, DIR, BASE) do { \ + char *d = d_dirname(PATH), *b = d_basename(PATH); \ + \ + if(strcmp(d, DIR)) { \ + fprintf(stderr, "%s:%d: d_dirname failed:\n" \ + " path: %s\n" \ + " got: %s\n" \ + "expected: %s\n", \ + __FILE__, __LINE__, \ + PATH, d, DIR); \ + count_error(); \ + } \ + if(strcmp(b, BASE)) { \ + fprintf(stderr, "%s:%d: d_basename failed:\n" \ + " path: %s\n" \ + " got: %s\n" \ + "expected: %s\n", \ + __FILE__, __LINE__, \ + PATH, d, DIR); \ + count_error(); \ + } \ +} while(0) + static void test_filepart(void) { - check_string(d_dirname("/"), "/"); - check_string(d_dirname("////"), "/"); - check_string(d_dirname("/spong"), "/"); - check_string(d_dirname("////spong"), "/"); - check_string(d_dirname("/foo/bar"), "/foo"); - check_string(d_dirname("////foo/////bar"), "////foo"); - check_string(d_dirname("./bar"), "."); - check_string(d_dirname(".//bar"), "."); - check_string(d_dirname("."), "."); - check_string(d_dirname(".."), "."); - check_string(d_dirname("../blat"), ".."); - check_string(d_dirname("..//blat"), ".."); - check_string(d_dirname("wibble"), "."); + check_filepart("", "", ""); + check_filepart("/", "/", "/"); + check_filepart("////", "/", "/"); + check_filepart("/spong", "/", "spong"); + check_filepart("/spong/", "/", "spong"); + check_filepart("/spong//", "/", "spong"); + check_filepart("////spong", "/", "spong"); + check_filepart("/foo/bar", "/foo", "bar"); + check_filepart("/foo/bar/", "/foo", "bar"); + check_filepart("////foo/////bar", "////foo", "bar"); + check_filepart("./bar", ".", "bar"); + check_filepart(".//bar", ".", "bar"); + check_filepart(".", ".", "."); + check_filepart("..", ".", ".."); + check_filepart("../blat", "..", "blat"); + check_filepart("..//blat", "..", "blat"); + check_filepart("wibble", ".", "wibble"); check_string(extension("foo.c"), ".c"); check_string(extension(".c"), ".c"); check_string(extension("."), "."); diff --git a/lib/t-macros.c b/lib/t-macros.c index 19bc14a..2bf747f 100644 --- a/lib/t-macros.c +++ b/lib/t-macros.c @@ -213,6 +213,12 @@ static void test_macros(void) { fprintf(stderr, ">>> expect error message about 'if':\n"); check_macro("badex3", "<@if{1}{2}{3}{4}{5}>", "<[['if' too many args]]>", 0); + + check_macro("dirname1", "@dirname{foo/bar}", "foo", 0); + check_macro("dirname2", "@dirname{foo & something/bar}", + "foo & something", 0); + check_macro("basename1", "@basename{xyzzy/plugh}", "plugh", 0); + check_macro("basename2", "@basename{xyzzy/a long long tests, errors; int fail_first; +int verbose; void count_error(void) { ++errors; @@ -34,7 +37,7 @@ const char *format(const char *s) { struct dynstr d; int c; char buf[10]; - + dynstr_init(&d); while((c = (unsigned char)*s++)) { if(c >= ' ' && c <= '~') @@ -52,7 +55,7 @@ const char *format_utf32(const uint32_t *s) { struct dynstr d; uint32_t c; char buf[64]; - + dynstr_init(&d); while((c = *s++)) { sprintf(buf, " %04lX", (long)c); @@ -90,6 +93,46 @@ const char *do_printf(const char *fmt, ...) { return s; } +static const struct option options[] = { + { "verbose", no_argument, 0, 'v' }, + { "fail-first", no_argument, 0, 'F' }, + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, +}; + +/* display usage message and terminate */ +static void help(void) { + xprintf("Usage:\n" + " %s [OPTIONS]\n" + "Options:\n" + " --help, -h Display usage message\n" + " --version, -V Display version number\n" + " --verbose, -v Verbose output\n" + " --fail-first, -F Stop on first failure\n", + progname); + xfclose(stdout); + exit(0); +} + +void test_init(int argc, char **argv) { + int n; + + set_progname(argv); + mem_init(); + while((n = getopt_long(argc, argv, "vFhV", options, 0)) >= 0) { + switch(n) { + case 'v': verbose = 1; break; + case 'F': fail_first = 1; break; + case 'h': help(); + case 'V': version(progname); + default: exit(1); + } + } + if(getenv("FAIL_FIRST")) + fail_first = 1; +} + + /* Local Variables: c-basic-offset:2 diff --git a/lib/test.h b/lib/test.h index 3c43da0..7a0ef5d 100644 --- a/lib/test.h +++ b/lib/test.h @@ -72,6 +72,7 @@ extern long long tests, errors; extern int fail_first; +extern int verbose; /** @brief Checks that @p expr is nonzero */ #define insist(expr) do { \ @@ -130,13 +131,13 @@ const char *format(const char *s); const char *format_utf32(const uint32_t *s); uint32_t *ucs4parse(const char *s); const char *do_printf(const char *fmt, ...); +void test_init(int argc, char **argv); #define TEST(name) \ - int main(void) { \ - mem_init(); \ - fail_first = !!getenv("FAIL_FIRST"); \ + int main(int argc, char **argv) { \ + test_init(argc, argv); \ test_##name(); \ - if(errors) \ + if(errors || verbose) \ fprintf(stderr, "test_"#name": %lld errors out of %lld tests\n", \ errors, tests); \ return !!errors; \ diff --git a/server/actions.c b/server/actions.c index 7b69932..1a6f7c9 100644 --- a/server/actions.c +++ b/server/actions.c @@ -92,7 +92,7 @@ static void act_playing(void) { "\n", refresh, url, dcgi_cookie_header()) < 0) fatal(errno, "error writing to stdout"); - dcgi_expand(action ? action : "playing"); + dcgi_expand("playing"); } static void act_disable(void) { diff --git a/server/cgimain.c b/server/cgimain.c index 638a713..253f82e 100644 --- a/server/cgimain.c +++ b/server/cgimain.c @@ -47,7 +47,8 @@ int main(int argc, char **argv) { configfile = xstrdup(conf); if(getenv("DISORDER_DEBUG")) debugging = 1; - if(config_read(0)) + /* Read configuration */ + if(config_read(0/*!server*/)) exit(EXIT_FAILURE); /* Figure out our URL. This can still be overridden from the config file if * necessary but it shouldn't be necessary in ordinary installations. */ @@ -68,7 +69,7 @@ int main(int argc, char **argv) { /* Create the initial connection, trying the cookie if we found a suitable * one. */ dcgi_login(); - /* The main program... */ + /* Do whatever the user wanted */ dcgi_action(NULL); /* In practice if a write fails that probably means the web server went away, * but we log it anyway. */ diff --git a/server/macros-disorder.c b/server/macros-disorder.c index bb920f4..e58324b 100644 --- a/server/macros-disorder.c +++ b/server/macros-disorder.c @@ -105,6 +105,7 @@ static int exp_arg(int attribute((unused)) nargs, struct sink *output, void attribute((unused)) *u) { const char *s = cgi_get(args[0]); + if(s) return sink_writes(output, cgi_sgmlquote(s)) < 0 ? -1 : 0; diff --git a/templates/macros.tmpl b/templates/macros.tmpl index 450783e..cb763b2 100644 --- a/templates/macros.tmpl +++ b/templates/macros.tmpl @@ -18,9 +18,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -@define {action} {} - {@if {@arg:mgmt@} - {manage} - {@arg:action@}@}@ +@define {ifmanage} {yes} {no} + {@if {@eq {@arg{action}}{manage}} + {@yes} + {@no}} }@@@ diff --git a/templates/playing.tmpl b/templates/playing.tmpl index c016858..084f5c3 100644 --- a/templates/playing.tmpl +++ b/templates/playing.tmpl @@ -1,4 +1,3 @@ -@include{macros.tmpl}@# +@discard{ + @define {ifmanage} {yes no} + {@if {@eq {@arg{action}}{manage}} + {@yes} + {@no}} + @define {back} {} + {@ifmanage{&back=manage}{}} + @include{macros.tmpl} +}@# -@include{stdhead.tmpl} +@include{stdhead.tmpl}@# @if{@isplaying} - {@part{@playing}{title}@label{playing.title}} + {@playing{@part{@id}{title}}} + {@label{playing.title}} @include{topbar.tmpl}

@label{playing.title}

@# Extra control buttons for the management page - @if{@arg{mgmt}}{ + @ifmanage{

@if{@paused}{ @# Paused @label{playing.pause} }{ @# Not paused @label{playing.pause} @@ -51,14 +60,14 @@ USA @if{@random-enabled}{ @# Random play enabled @label{playing.random} }{ @# Random play disabled @label{playing.random} @@ -66,19 +75,19 @@ USA @if{@enabled}{ @# Play enabled @label{playing.playing} }{ -@# Play disbaled +@# Play disabled @label{playing.playing} } -@3 Volume form +@# Volume form

@@ -86,7 +95,7 @@ USA @# Volume up button @right{volume}{ + href="@url?action=volume&delta=-@label{volume.resolution}@back"> @label{volume.reduce} @@ -97,7 +106,7 @@ USA @# Volume value widgets @label{volume.left} @label{volume.right} - + @# Volume set button @right{volume}{
- } + }{} @# Only display the table if there is something to put in it @if{@or{@isplaying}{@isqueue}}{ @@ -133,85 +142,93 @@ USA @label{heading.title} @label{heading.length}   - @if{@arg{mgmt}}{ + @ifmanage{         - }@ + }{} - @if{@isplaying}{ + @playing{ - @label{playing.now} + @label{playing.now} - @playing{ - @when@ - @if{@eq{@who@}{}@}{@if{@eq{@state@}{random}@}{@label{playing.randomtrack}}{ }@}{@who@}@ - @right{play}{@part{short}{artist}@}{@part{short}{artist}@}@ - @right{play}{@part{short}{album}@}{@part{short}{album}@}@ - @part{short}{title}@ - @length@ - @if{@scratchable@}{@label{playing.scratch}}{@label{playing.scratch}}@ - @if{@arg{mgmt}}{ + @when{@id} + @if{@eq{@who{@id}}{}} + {@if{@eq{@state{@id}}{random}} + {@label{playing.randomtrack}} + { }} + {@who{@id}} + + @right{play} + {@part{@id}{short}{artist}} + {@part{short}{artist}} + + @right{play} + {@part{short}{album}} + {@part{short}{album}} + + @part{@id}{short}{title} + @length{@id} + @if{@removabl{@id}} + { + @label{playing.scratch}} + {@label{playing.scratch}} + + @ifmanage{         - }@ + }{} - }@}@ + } @if{@isqueue@}{ - @label{playing.next} + @label{playing.next} + } @queue{ - @when@ - @if{@eq{@who@}{}@}{@if{@eq{@state@}{random}@}{@label{queue.randomtrack}}{ }@}{@who@}@ + @when + @if{@eq{@who@}{}@}{@if{@eq{@state@}{random}@}{@label{queue.randomtrack}}{ }@}{@who@} @right{play}{@part{short}{artist}@}{@part{short}{artist}@}@ + title="@part{@id}{artist}@" + href="@url?action=choose&directory=@urlquote{@dirname{@dirname{@track}@}@}@" + >@part{@id}{short}{artist}}{@part{@id}{short}{artist}} @right{play}{@part{short}{album}@}{@part{short}{album}@}@ + title="@part{@id}{album}@" + href="@url?action=choose&directory=@urlquote{@dirname{@track}@}@" + >@part{@id}{short}{album}}{@part{@id}{short}{album}@} @part{short}{title}@ - @length@ + title="@part{@id}{title}@">@part{@id}{short}{title} + @length @if{@removable@}{@label{playing.remove}}{@label{playing.remove}}@ + alt="@label{playing.remove}">} - @if{@arg{mgmt}}{ + @if{@eq{@arg{action}}{manage}}{ @if{@or{@isfirst@} {@not{@movable@}@}@}{ @@ -227,16 +244,16 @@ USA @label{playing.upall} @label{playing.up} - }@ + } @if{@or{@islast@} {@not{@movable@}@}@}{ @@ -253,32 +270,30 @@ USA @label{playing.downall} @label{playing.down} - }@ - - }@ + } + } - }@}@ + } -}@ +} -@include{topbarend}@ +@include{topbarend}@# -@@ - +}@ diff --git a/templates/stdhead.tmpl b/templates/stdhead.tmpl index e1a8174..9118ff1 100644 --- a/templates/stdhead.tmpl +++ b/templates/stdhead.tmpl @@ -1,9 +1,6 @@ -@include:stylesheet@ -@@ -Anything that goes in all html HEAD elements goes here. - + +Anything that goes in all html HEAD elements goes here. +}@# +@include{stylesheet.tmpl}@# diff --git a/templates/stylesheet.tmpl b/templates/stylesheet.tmpl index da58e56..da41aff 100644 --- a/templates/stylesheet.tmpl +++ b/templates/stylesheet.tmpl @@ -1,8 +1,4 @@ - -@@ -This file is a standard place to put a link to a stylesheet, -or an embedded stylesheet. - + +This file is a standard place to put a link to a stylesheet, +or an embedded stylesheet. +}@# + -- [mdw]