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");
 }