Mercurial > sci
changeset 30:43333d18e4b8
scid: document theme
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 04 Aug 2022 14:54:43 +0200 |
parents | 695637f1d8a7 |
children | 8c2087e7d381 |
files | lib/util.c lib/util.h scid/page-index.c scid/pageutil.c scid/scid.c scid/scid.h scid/theme.c scid/theme.h themes/bulma/theme.js |
diffstat | 9 files changed, 293 insertions(+), 182 deletions(-) [+] |
line wrap: on
line diff
--- a/lib/util.c Thu Aug 04 14:13:58 2022 +0200 +++ b/lib/util.c Thu Aug 04 14:54:43 2022 +0200 @@ -18,7 +18,7 @@ #include <sys/stat.h> #include <assert.h> -#include <err.h> +#include <errno.h> #include <fcntl.h> #include <libgen.h> #include <limits.h> @@ -36,7 +36,7 @@ void *ret; if (!(ret = malloc(size))) - err(1, "malloc"); + util_die("malloc: %s\n", strerror(errno)); return ret; } @@ -47,7 +47,7 @@ void *ret; if (!(ret = calloc(n, size))) - err(1, "calloc"); + util_die("calloc: %s\n", strerror(errno)); return ret; } @@ -58,7 +58,7 @@ void *ret; if (!(ret = realloc(ptr, size)) && size) - err(1, "realloc"); + util_die("realloc: %s\n", strerror(errno)); return ret; } @@ -69,7 +69,7 @@ void *ret; if (!(ret = malloc(size))) - err(1, "malloc"); + util_die("malloc: %s\n", strerror(errno)); return memcpy(ret, ptr, size); } @@ -80,7 +80,7 @@ char *ret; if (!(ret = strdup(src))) - err(1, "strdup"); + util_die("strdup: %s\n", strerror(errno)); return ret; } @@ -93,7 +93,7 @@ char *ret; if (!(ret = strndup(src, n))) - err(1, "strndup"); + util_die("strndup: %s\n", strerror(errno)); return ret; } @@ -128,25 +128,23 @@ FILE *fp; if (!(fp = fmemopen(buf, size, mode))) - err(1, "fmemopen"); + util_die("fmemopen: %s\n", strerror(errno)); return fp; } -char * -util_printf(char *buf, size_t bufsz, const char *fmt, ...) +FILE * +util_open_memstream(char **out, size_t *outsz) { - assert(buf); - assert(bufsz); - assert(fmt); + assert(out); + assert(outsz); - va_list ap; + FILE *fp; - va_start(ap, fmt); - vsnprintf(buf, bufsz, fmt, ap); - va_end(ap); + if (!(fp = open_memstream(out, outsz))) + util_die("open_memstream: %s\n", strerror(errno)); - return buf; + return fp; } char * @@ -185,3 +183,33 @@ va_end(ap); exit(1); } + +json_t * +util_json_pack(const char *fmt, ...) +{ + va_list ap; + json_t *doc; + json_error_t err; + + va_start(ap, fmt); + doc = json_vpack_ex(&err, 0, fmt, ap); + va_end(ap); + + if (!doc) + util_die("json_vpack_ex: %s\n", err.text); + + return doc; +} + +char * +util_json_dump(const json_t *json) +{ + assert(json); + + char *ret; + + if (!(ret = json_dumps(json, JSON_COMPACT))) + util_die("json_dump: %s\n", strerror(ENOMEM)); + + return ret; +}
--- a/lib/util.h Thu Aug 04 14:13:58 2022 +0200 +++ b/lib/util.h Thu Aug 04 14:54:43 2022 +0200 @@ -22,6 +22,8 @@ #include <stddef.h> #include <stdio.h> +#include <jansson.h> + #define UTIL_SIZE(x) (sizeof (x) / sizeof (x[0])) void * @@ -45,9 +47,6 @@ char * util_strndup(const char *, size_t); -size_t -util_strlcpy(char *, const char *, size_t); - char * util_basename(const char *); @@ -57,18 +56,26 @@ FILE * util_fmemopen(void *, size_t, const char *); -char * -util_printf(char *, size_t, const char *, ...); +FILE * +util_open_memstream(char **, size_t *); char * util_read(const char *); -const char * -util_path(const char *); - void util_die(const char *, ...); +json_t * +util_json_pack(const char *, ...); + +char * +util_json_dump(const json_t *); + +/* defined in extern/ */ + +size_t +util_strlcpy(char *, const char *, size_t); + long long util_strtonum(const char *, long long, long long, const char **);
--- a/scid/page-index.c Thu Aug 04 14:13:58 2022 +0200 +++ b/scid/page-index.c Thu Aug 04 14:54:43 2022 +0200 @@ -27,35 +27,8 @@ #include "theme.h" #include "util.h" -#if 0 - -/* - * Document we create for templatize. - * - * { - * "projects: [ - * { - * "name": "project name", - * "desc": "project short description", - * "url": "project URL or homepage", - * "jobs": [ - * { - * "job": job-id, - * "tag": "job tag / revision", - * "status": "failed / success" // failed if at least one has failed - * } - * ] - * "n-failed": number of failed jobs - * "n-succes": number of successful jobs - * } - * ] - * } - */ - -#endif - static void -fill_jobresults(json_t *project, json_t *job, json_t *jobresults) +set_job_status(json_t *project, json_t *job, json_t *jobresults) { json_t *iter, *status; int exitcode, sigcode; @@ -81,13 +54,17 @@ } static void -fill_jobs(json_t *project, json_t *jobs) +set_project_jobs(json_t *project, json_t *jobs) { json_t *iter, *jobresults; json_int_t job_id; size_t i; json_array_foreach(jobs, i, iter) { + /* Don't populate too much. */ + if (i + 1 >= 10) + break; + /* * For this job, find all jobresult to check how many have * failed or not. @@ -100,15 +77,18 @@ if (!(jobresults = db_jobresult_list_by_job_group(job_id))) continue; - fill_jobresults(project, iter, jobresults); + set_job_status(project, iter, jobresults); json_decref(jobresults); } json_object_set_new(project, "jobs", jobs); } +/* + * For every projects, find their jobs and add them as 'jobs' property. + */ static void -fill_projects(json_t *projects) +update_projects(json_t *projects) { json_t *jobs, *iter; const char *name; @@ -123,7 +103,7 @@ if (!(jobs = db_job_list(name))) continue; - fill_jobs(iter, jobs); + set_project_jobs(iter, jobs); } } @@ -135,12 +115,12 @@ /* First, fetch all projects. */ if ((projects = db_project_list())) { - fill_projects(projects); - + update_projects(projects); root = json_pack("{so}", "projects", projects); - printf("===\n%s\n===\n", json_dumps(root, JSON_INDENT(4))); - data = theme_page_index(scid.theme, root); + data = theme_page_index(root); pageutil_render(req, KHTTP_200, KMIME_TEXT_HTML, data); + free(data); + json_decref(root); json_decref(projects); } else pageutil_status(req, KHTTP_500);
--- a/scid/pageutil.c Thu Aug 04 14:13:58 2022 +0200 +++ b/scid/pageutil.c Thu Aug 04 14:54:43 2022 +0200 @@ -48,7 +48,7 @@ char *body; - body = theme_page_status(scid.theme, status); + body = theme_page_status(status); pageutil_render(req, status, KMIME_TEXT_HTML, body); free(body); }
--- a/scid/scid.c Thu Aug 04 14:13:58 2022 +0200 +++ b/scid/scid.c Thu Aug 04 14:54:43 2022 +0200 @@ -19,13 +19,7 @@ if (db_open(scid.dbpath) < 0) log_die("abort: unable to open database"); - scid.theme = theme_open(scid.themedir); -} - -const char * -scid_theme_path(const char *filename) -{ - return theme_path(scid.theme, filename); + theme_open(scid.themedir); } void
--- a/scid/scid.h Thu Aug 04 14:13:58 2022 +0200 +++ b/scid/scid.h Thu Aug 04 14:54:43 2022 +0200 @@ -3,20 +3,14 @@ #include <limits.h> -struct theme; - extern struct scid { char themedir[PATH_MAX]; char dbpath[PATH_MAX]; - struct theme *theme; } scid; void scid_init(void); -const char * -scid_theme_path(const char *filename); - void scid_finish(void);
--- a/scid/theme.c Thu Aug 04 14:13:58 2022 +0200 +++ b/scid/theme.c Thu Aug 04 14:54:43 2022 +0200 @@ -1,3 +1,21 @@ +/* + * theme.c -- theme management + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + #include <assert.h> #include <errno.h> #include <limits.h> @@ -14,10 +32,9 @@ #define SIGNATURE DUK_HIDDEN_SYMBOL("File") -struct theme { - char base[PATH_MAX]; +static struct { duk_context *ctx; -}; +} theme; /* {{{ mustache support */ @@ -198,7 +215,7 @@ }; MUSTACHE_TEMPLATE *tmpl; int status; - + if (!(tmpl = mustache_compile(input, strlen(input), &parser, (void *)path, 0))) return -1; @@ -241,7 +258,7 @@ if (json && !(doc = json_loads(json, 0, &err))) return duk_error(ctx, DUK_ERR_ERROR, "%d:%d:%s", err.line, err.column, err.text); - mch_templatize(fp, theme_path(scid.theme, path), doc); + mch_templatize(fp, theme_path(path), doc); if (doc) json_decref(doc); @@ -266,118 +283,99 @@ /* }}} */ static char * -call(struct theme *t, json_t *json, const char *function) +call(const json_t *json, const char *function) { - char *out = NULL, *dump = NULL; + char *out = NULL, *dump; size_t outsz = 0; - FILE *fp = NULL; - int nargs = 1; + FILE *fp; - duk_get_global_string(t->ctx, function); + duk_get_global_string(theme.ctx, function); - if (!duk_is_callable(t->ctx, -1)) - goto over; - if (!(fp = open_memstream(&out, &outsz))) - goto over; - - duk_push_pointer(t->ctx, fp); + if (duk_is_callable(theme.ctx, -1)) { + fp = util_open_memstream(&out, &outsz); + dump = util_json_dump(json); - if (json && (dump = json_dumps(json, JSON_COMPACT))) { - duk_push_string(t->ctx, dump); - duk_json_decode(t->ctx, -1); - nargs++; - } + duk_push_pointer(theme.ctx, fp); + duk_push_string(theme.ctx, dump); + duk_json_decode(theme.ctx, -1); - if (duk_pcall(t->ctx, nargs) != 0) - log_warn("theme: %s", duk_safe_to_string(t->ctx, -1)); + if (duk_pcall(theme.ctx, 2) != 0) + log_warn("theme: %s", duk_safe_to_string(theme.ctx, -1)); -over: - duk_pop(t->ctx); + duk_pop(theme.ctx); + fclose(fp); + free(dump); + } else + duk_pop(theme.ctx); - /* - * For convenience, otherwise all callers have to check for non-NULL - * after calling the function. - */ - free(dump); - - if (fp) - fclose(fp); if (!out) out = util_strdup(""); return out; } +void +theme_open(const char *directory) +{ + assert(directory); + + const char *path; + char *data; + + theme.ctx = duk_create_heap_default(); + path = theme_path("theme.js"); + + if (!(data = util_read(path))) + log_warn("theme: %s: %s", path, strerror(errno)); + else { + if (duk_peval_string(theme.ctx, data) != 0) + log_warn("theme: %s", duk_safe_to_string(theme.ctx, -1)); + + duk_pop(theme.ctx); + duk_push_object(theme.ctx); + duk_put_function_list(theme.ctx, -1, functions); + duk_put_global_string(theme.ctx, "Scid"); + free(data); + } +} + const char * -theme_path(struct theme *t, const char *filename) +theme_path(const char *filename) { assert(filename); /* Build path to the template file. */ static _Thread_local char path[PATH_MAX]; - snprintf(path, sizeof (path), "%s/%s", t->base, filename); + snprintf(path, sizeof (path), "%s/%s", scid.themedir, filename); return path; } -struct theme * -theme_open(const char *directory) +char * +theme_page_index(const json_t *json) { - assert(directory); - - struct theme *t; - char themefile[PATH_MAX], *data; - - t = util_calloc(1, sizeof (*t)); - t->ctx = duk_create_heap_default(); - util_strlcpy(t->base, directory, sizeof (t->base)); - - /* Open theme.js in the directory. */ - snprintf(themefile, sizeof (themefile), "%s/theme.js", t->base); + assert(json); - if (!(data = util_read(themefile))) - log_warn("theme: %s: %s", themefile, strerror(errno)); - else { - if (duk_peval_string(t->ctx, data) != 0) - log_warn("theme: %s", duk_safe_to_string(t->ctx, -1)); - - duk_pop(t->ctx); - duk_push_object(t->ctx); - duk_put_function_list(t->ctx, -1, functions); - duk_put_global_string(t->ctx, "Scid"); - free(data); - } - - return t; + return call(json, "onPageIndex"); } char * -theme_page_index(struct theme *t, json_t *json) -{ - assert(t); - - return call(t, json, "onPageIndex"); -} - -char * -theme_page_status(struct theme *t, enum khttp status) +theme_page_status(enum khttp status) { - assert(t); - - (void)t; - (void)status; + json_t *doc; + char *ret; -#if 0 - return call(t, json, "onPageStatus"); -#endif - return "ERROR"; + doc = util_json_pack("{si}", "status", status); + ret = call(doc, "onPageStatus"); + + json_decref(doc); + + return ret; } void -theme_free(struct theme *t) +theme_free(void) { - assert(t); - - duk_destroy_heap(t->ctx); + duk_destroy_heap(theme.ctx); }
--- a/scid/theme.h Thu Aug 04 14:13:58 2022 +0200 +++ b/scid/theme.h Thu Aug 04 14:54:43 2022 +0200 @@ -1,25 +1,123 @@ +/* + * theme.h -- theme management + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + #ifndef SCID_THEME_H #define SCID_THEME_H +/** + * \file theme.h + * \brief Theme management + * + * Themes are written in Javascript and usually with a combination of + * [mustache][] files as well. The Javascript code is executed before rendering + * the page with a JSON document containing the data to generate. + * + * This has been designed to address the issue that many web frameworks require + * different kind of HTML entities to generate the view and as such, the scid + * application can not generate those HTML tags. + * + * This module uses a global Javascript context to perform rendering, you must + * call theme_open before doing anything else. Once done, use theme_finish to + * cleanup resources. + */ + #include <jansson.h> #include "pageutil.h" -struct theme; - -struct theme * +/** + * Open the theme specified by directory path. + * + * \pre directory != NULL + * \param directory path to the theme directory (absolute path is recommended). + */ +void theme_open(const char *directory); +/** + * Return the path to the filename relative to the theme directory. + * + * \pre filename != NULL + * \param filename the filename to append + * \return the full path + * \note This function return a thread-local static variable. + */ const char * -theme_path(struct theme *t, const char *filename); - -char * -theme_page_index(struct theme *t, json_t *); +theme_path(const char *filename); +/** + * Render the index page. + * + * The document requires the following properties: + * + * ``` + * { + * "projects: [ + * { + * "name": "project name", + * "desc": "project short description", + * "url": "project URL or homepage", + * "jobs": [ + * { + * "job": job-id, + * "tag": "job tag / revision", + * "status": "failed / success" // failed if at least one has failed + * } + * ] + * "n-failed": number of failed jobs + * "n-succes": number of successful jobs + * } + * ] + * } + * ``` + * + * \pre doc != NULL + * \param doc the page document + * \return a newly allocated rendered string + * \note You must free the return value + */ +[[nodiscard]] char * -theme_page_status(struct theme *t, enum khttp status); +theme_page_index(const json_t *doc); +/** + * Render the status page (for error code). + * + * The document requires the following properties: + * + * ``` + * { + * "status": number // Exemple: 400, 401 + * } + * ``` + * + * \param status the status code + * \return a newly allocated rendered string + * \note You must free the return value + */ +[[nodiscard]] +char * +theme_page_status(enum khttp status); + +/** + * Cleanup theme resources. + */ void -theme_free(struct theme *t); +theme_finish(void); #endif /* !SCID_THEME_H */
--- a/themes/bulma/theme.js Thu Aug 04 14:13:58 2022 +0200 +++ b/themes/bulma/theme.js Thu Aug 04 14:54:43 2022 +0200 @@ -1,28 +1,40 @@ -function updateProject(project) -{ - for (var j = 0; j < project.jobs.length; ++j) { - if (project.jobs[j].status === "success") - project.jobs[j].classname = "is-success"; - else - project.jobs[j].classname = "is-danger"; - } -} +/* + * theme.js -- scid bulma theme + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ function onPageIndex(rdr, data) { Scid.render(rdr, "header.mustache", { - title: "index page" + title: "sci -- index page" }); /* * Add is-danger/is-success for every job depending on their success * status, this is required to show the appropriate tag. */ - for (var i = 0; i < data.projects.length; ++i) - updateProject(data.projects[i]); + for (var i = 0; i < data.projects.length; ++i) { + for (var j = 0; j < data.projects[i].jobs.length; ++j) { + if (data.projects[i].jobs[j].status === "success") + data.projects[i].jobs[j].classname = "is-success"; + else + data.projects[i].jobs[j].classname = "is-danger"; + } + } - Scid.print("RENDERING THIS"); - Scid.print(JSON.stringify(data)); Scid.render(rdr, "index.mustache", data); Scid.render(rdr, "footer.mustache"); }