List for cgit developers and users
 help / color / mirror / Atom feed
* [PATCH v2 0/9] filter framework and lua support: complete
@ 2014-01-13 14:00 Jason
  2014-01-13 14:00 ` [PATCH v2 1/9] filter: add fprintf_filter function Jason
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


In this second installment I've squashed things down and cleanly separated
the framework commits from the lua commits. It should be much easier to
comprehend.

The weakest part is still the Makefile changes, which might not be very
portable and could probably be more configurable somehow. If anybody would
like to step in and brush this up, that'd be quite nice. You can base commits
off of jd-jk/filter-infra, and I'll fold it into this series.

The other controversial item is the write() hooking. The reason we do this
instead of replacing the html_raw function is that in various places, such
as lines 82 and 83 of ui-patch.c, we use other facilities for writing to
standard out -- either via stdio (which has its own buffering), or via git,
which also has its own buffering. So, it turned out to be safer to hook
libc's write than to try and monkey patch git itself.

Another comment brought up in v1 was adding a filter_lua.c file. I'd like to
keep things fairly compact for now, partially because I like inline and
static functions, and partially because I don't want to create a filter.h.
Right now despite supporting a scripting language, this is pretty lean; I'd
like for it to remain so. At least for now.

Jason A. Donenfeld (6):
  filter: allow for cleanup hook for filter types
  filter: basic write hooking infrastructure
  filter: add lua support
  filter: return on null filter from open and close
  filter: add support for email filter
  filter: add gravatar scripts

John Keeping (3):
  filter: add fprintf_filter function
  filter: add interface layer
  filter: introduce "filter type" prefix

 cgit.c                     |  15 +-
 cgit.h                     |  18 ++-
 cgit.mk                    |  22 ++-
 cgitrc.5.txt               |  56 +++++++
 filter.c                   | 376 ++++++++++++++++++++++++++++++++++++++++++---
 filters/email-gravatar.lua |  25 +++
 filters/email-gravatar.py  |  33 ++++
 shared.c                   |   1 +
 ui-commit.c                |  22 ++-
 ui-log.c                   |   2 +
 ui-refs.c                  |   9 +-
 ui-repolist.c              |   6 +-
 ui-snapshot.c              |  11 +-
 ui-summary.c               |   8 +-
 ui-tag.c                   |   2 +
 15 files changed, 550 insertions(+), 56 deletions(-)
 create mode 100644 filters/email-gravatar.lua
 create mode 100755 filters/email-gravatar.py

-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 1/9] filter: add fprintf_filter function
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 2/9] filter: add interface layer Jason
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


From: John Keeping <john at keeping.me.uk>

This stops the code in cgit.c::print_repo needing to inspect the
cgit_filter structure, meaning that we can abstract out different filter
types that will have different fields that need to be printed.

Signed-off-by: John Keeping <john at keeping.me.uk>
---
 cgit.c   | 6 +++---
 cgit.h   | 1 +
 filter.c | 5 +++++
 3 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/cgit.c b/cgit.c
index 0be41b8..29b658e 100644
--- a/cgit.c
+++ b/cgit.c
@@ -706,11 +706,11 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
 	fprintf(f, "repo.enable-log-linecount=%d\n",
 	        repo->enable_log_linecount);
 	if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
-		fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
+		cgit_fprintf_filter(repo->about_filter, f, "repo.about-filter=");
 	if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
-		fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
+		cgit_fprintf_filter(repo->commit_filter, f, "repo.commit-filter=");
 	if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
-		fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
+		cgit_fprintf_filter(repo->source_filter, f, "repo.source-filter=");
 	if (repo->snapshots != ctx.cfg.snapshots) {
 		char *tmp = build_snapshot_setting(repo->snapshots);
 		fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : "");
diff --git a/cgit.h b/cgit.h
index e6e7715..9b4be26 100644
--- a/cgit.h
+++ b/cgit.h
@@ -345,6 +345,7 @@ extern int cgit_parse_snapshots_mask(const char *str);
 
 extern int cgit_open_filter(struct cgit_filter *filter, ...);
 extern int cgit_close_filter(struct cgit_filter *filter);
+extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix);
 extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
 
 extern void cgit_prepare_repo_env(struct cgit_repo * repo);
diff --git a/filter.c b/filter.c
index d8c0116..80cf689 100644
--- a/filter.c
+++ b/filter.c
@@ -63,6 +63,11 @@ done:
 
 }
 
+void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
+{
+	fprintf(f, "%s%s\n", prefix, filter->cmd);
+}
+
 struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
 {
 	struct cgit_filter *f;
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 2/9] filter: add interface layer
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
  2014-01-13 14:00 ` [PATCH v2 1/9] filter: add fprintf_filter function Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 3/9] filter: introduce "filter type" prefix Jason
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


From: John Keeping <john at keeping.me.uk>

Change the existing cgit_{open,close,fprintf}_filter functions to
delegate to filter-specific implementations accessed via function
pointers on the cgit_filter object.

We treat the "exec" filter type slightly specially here by putting its
structure definition in the header file and providing an "init" function
to set up the function pointers.  This is required so that the
ui-snapshot.c code that applies a compression filter can continue to use
the filter interface to do so.

Signed-off-by: John Keeping <john at keeping.me.uk>
---
 cgit.h        |  8 ++++++++
 filter.c      | 66 ++++++++++++++++++++++++++++++++++++++++++++---------------
 ui-snapshot.c | 11 +++++-----
 3 files changed, 63 insertions(+), 22 deletions(-)

diff --git a/cgit.h b/cgit.h
index 9b4be26..92e8c55 100644
--- a/cgit.h
+++ b/cgit.h
@@ -57,6 +57,13 @@ typedef enum {
 } filter_type;
 
 struct cgit_filter {
+	int (*open)(struct cgit_filter *, va_list ap);
+	int (*close)(struct cgit_filter *);
+	void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix);
+};
+
+struct cgit_exec_filter {
+	struct cgit_filter base;
 	char *cmd;
 	char **argv;
 	int extra_args;
@@ -346,6 +353,7 @@ extern int cgit_parse_snapshots_mask(const char *str);
 extern int cgit_open_filter(struct cgit_filter *filter, ...);
 extern int cgit_close_filter(struct cgit_filter *filter);
 extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix);
+extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
 extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
 
 extern void cgit_prepare_repo_env(struct cgit_repo * repo);
diff --git a/filter.c b/filter.c
index 80cf689..0f3edb0 100644
--- a/filter.c
+++ b/filter.c
@@ -13,15 +13,13 @@
 #include <string.h>
 #include <stdlib.h>
 
-int cgit_open_filter(struct cgit_filter *filter, ...)
+static int open_exec_filter(struct cgit_filter *base, va_list ap)
 {
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base;
 	int i;
-	va_list ap;
 
-	va_start(ap, filter);
 	for (i = 0; i < filter->extra_args; i++)
 		filter->argv[i+1] = va_arg(ap, char *);
-	va_end(ap);
 
 	filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
 		"Unable to duplicate STDOUT");
@@ -41,9 +39,9 @@ int cgit_open_filter(struct cgit_filter *filter, ...)
 	return 0;
 }
 
-
-int cgit_close_filter(struct cgit_filter *filter)
+static int close_exec_filter(struct cgit_filter *base)
 {
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base;
 	int i, exit_status;
 
 	chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
@@ -63,21 +61,50 @@ done:
 
 }
 
-void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
+static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix)
 {
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base;
 	fprintf(f, "%s%s\n", prefix, filter->cmd);
 }
 
-struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
+int cgit_open_filter(struct cgit_filter *filter, ...)
 {
-	struct cgit_filter *f;
-	int args_size = 0;
+	int result;
+	va_list ap;
+	va_start(ap, filter);
+	result = filter->open(filter, ap);
+	va_end(ap);
+	return result;
+}
 
-	if (!cmd || !cmd[0])
-		return NULL;
+int cgit_close_filter(struct cgit_filter *filter)
+{
+	return filter->close(filter);
+}
+
+void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
+{
+	filter->fprintf(filter, f, prefix);
+}
+
+void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
+{
+	memset(filter, 0, sizeof(*filter));
+	filter->base.open = open_exec_filter;
+	filter->base.close = close_exec_filter;
+	filter->base.fprintf = fprintf_exec_filter;
+	filter->cmd = cmd;
+	filter->argv = argv;
+}
+
+static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filtertype)
+{
+	struct cgit_exec_filter *f;
+	int args_size = 0;
 
-	f = xmalloc(sizeof(struct cgit_filter));
-	memset(f, 0, sizeof(struct cgit_filter));
+	f = xmalloc(sizeof(*f));
+	/* We leave argv for now and assign it below. */
+	cgit_exec_filter_init(f, xstrdup(cmd), NULL);
 
 	switch (filtertype) {
 		case SOURCE:
@@ -91,10 +118,17 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
 			break;
 	}
 
-	f->cmd = xstrdup(cmd);
 	args_size = (2 + f->extra_args) * sizeof(char *);
 	f->argv = xmalloc(args_size);
 	memset(f->argv, 0, args_size);
 	f->argv[0] = f->cmd;
-	return f;
+	return &f->base;
+}
+
+struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
+{
+	if (!cmd || !cmd[0])
+		return NULL;
+
+	return new_exec_filter(cmd, filtertype);
 }
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 5136c49..7115ec4 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -58,13 +58,12 @@ static int write_compressed_tar_archive(const char *hex,
 					char *filter_argv[])
 {
 	int rv;
-	struct cgit_filter f = {
-		.cmd = filter_argv[0],
-		.argv = filter_argv,
-	};
-	cgit_open_filter(&f);
+	struct cgit_exec_filter f;
+	cgit_exec_filter_init(&f, filter_argv[0], filter_argv);
+
+	cgit_open_filter(&f.base);
 	rv = write_tar_archive(hex, prefix);
-	cgit_close_filter(&f);
+	cgit_close_filter(&f.base);
 	return rv;
 }
 
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 3/9] filter: introduce "filter type" prefix
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
  2014-01-13 14:00 ` [PATCH v2 1/9] filter: add fprintf_filter function Jason
  2014-01-13 14:00 ` [PATCH v2 2/9] filter: add interface layer Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 4/9] filter: allow for cleanup hook for filter types Jason
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


From: John Keeping <john at keeping.me.uk>

This allows different filter implementations to be specified in the
configuration file.  Currently only "exec" is supported, but it may now
be specified either with or without the "exec:" prefix.

Signed-off-by: John Keeping <john at keeping.me.uk>
---
 cgitrc.5.txt |  9 +++++++++
 filter.c     | 33 +++++++++++++++++++++++++++++++--
 2 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 52caed0..60159f6 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -557,6 +557,15 @@ config files, e.g. "repo.desc" becomes "desc".
 
 FILTER API
 ----------
+By default, filters are separate processes that are executed each time they
+are needed.  Alternative technologies may be used by prefixing the filter
+specification with the relevant string; available values are:
+
+'exec:'::
+	The default "one process per filter" mode.
+
+Parameters are provided to filters as follows.
+
 about filter::
 	This filter is given a single parameter: the filename of the source
 	file to filter. The filter can use the filename to determine (for
diff --git a/filter.c b/filter.c
index 0f3edb0..ba66e46 100644
--- a/filter.c
+++ b/filter.c
@@ -64,7 +64,7 @@ done:
 static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix)
 {
 	struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base;
-	fprintf(f, "%s%s\n", prefix, filter->cmd);
+	fprintf(f, "%sexec:%s\n", prefix, filter->cmd);
 }
 
 int cgit_open_filter(struct cgit_filter *filter, ...)
@@ -125,10 +125,39 @@ static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filterty
 	return &f->base;
 }
 
+static const struct {
+	const char *prefix;
+	struct cgit_filter *(*ctor)(const char *cmd, filter_type filtertype);
+} filter_specs[] = {
+	{ "exec", new_exec_filter },
+};
+
 struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
 {
+	char *colon;
+	int i;
+	size_t len;
 	if (!cmd || !cmd[0])
 		return NULL;
 
-	return new_exec_filter(cmd, filtertype);
+	colon = strchr(cmd, ':');
+	len = colon - cmd;
+	/*
+	 * In case we're running on Windows, don't allow a single letter before
+	 * the colon.
+	 */
+	if (len == 1)
+		colon = NULL;
+
+	/* If no prefix is given, exec filter is the default. */
+	if (!colon)
+		return new_exec_filter(cmd, filtertype);
+
+	for (i = 0; i < ARRAY_SIZE(filter_specs); i++) {
+		if (len == strlen(filter_specs[i].prefix) &&
+		    !strncmp(filter_specs[i].prefix, cmd, len))
+			return filter_specs[i].ctor(colon + 1, filtertype);
+	}
+
+	die("Invalid filter type: %.*s", (int) len, cmd);
 }
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 4/9] filter: allow for cleanup hook for filter types
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
                   ` (2 preceding siblings ...)
  2014-01-13 14:00 ` [PATCH v2 3/9] filter: introduce "filter type" prefix Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 5/9] filter: basic write hooking infrastructure Jason
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


At some point, we're going to want to do lazy deallocation of filters.
For example, if we implement lua, we'll want to load the lua runtime
once for each filter, even if that filter is called many times.
Similarly, for persistent exec filters, we'll want to load it once,
despite many open_filter and close_filter calls, and only reap the child
process at the end of the cgit process. For this reason, we add here a
cleanup function that is called at the end of cgit's main().

Signed-off-by: Jason A. Donenfeld <Jason at zx2c4.com>
---
 cgit.c   |  1 +
 cgit.h   |  2 ++
 filter.c | 90 +++++++++++++++++++++++++++++++++++++++++++++-------------------
 3 files changed, 66 insertions(+), 27 deletions(-)

diff --git a/cgit.c b/cgit.c
index 29b658e..4f31e58 100644
--- a/cgit.c
+++ b/cgit.c
@@ -951,6 +951,7 @@ int main(int argc, const char **argv)
 		ctx.cfg.cache_size = 0;
 	err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
 			    ctx.qry.raw, ttl, process_request, &ctx);
+	cgit_cleanup_filters();
 	if (err)
 		cgit_print_error("Error processing page: %s (%d)",
 				 strerror(err), err);
diff --git a/cgit.h b/cgit.h
index 92e8c55..893c38f 100644
--- a/cgit.h
+++ b/cgit.h
@@ -60,6 +60,7 @@ struct cgit_filter {
 	int (*open)(struct cgit_filter *, va_list ap);
 	int (*close)(struct cgit_filter *);
 	void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix);
+	void (*cleanup)(struct cgit_filter *);
 };
 
 struct cgit_exec_filter {
@@ -355,6 +356,7 @@ extern int cgit_close_filter(struct cgit_filter *filter);
 extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix);
 extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
 extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
+extern void cgit_cleanup_filters(void);
 
 extern void cgit_prepare_repo_env(struct cgit_repo * repo);
 
diff --git a/filter.c b/filter.c
index ba66e46..30bc74b 100644
--- a/filter.c
+++ b/filter.c
@@ -13,6 +13,25 @@
 #include <string.h>
 #include <stdlib.h>
 
+static inline void reap_filter(struct cgit_filter *filter)
+{
+	if (filter && filter->cleanup)
+		filter->cleanup(filter);
+}
+
+void cgit_cleanup_filters(void)
+{
+	int i;
+	reap_filter(ctx.cfg.about_filter);
+	reap_filter(ctx.cfg.commit_filter);
+	reap_filter(ctx.cfg.source_filter);
+	for (i = 0; i < cgit_repolist.count; ++i) {
+		reap_filter(cgit_repolist.repos[i].about_filter);
+		reap_filter(cgit_repolist.repos[i].commit_filter);
+		reap_filter(cgit_repolist.repos[i].source_filter);
+	}
+}
+
 static int open_exec_filter(struct cgit_filter *base, va_list ap)
 {
 	struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base;
@@ -67,34 +86,17 @@ static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *p
 	fprintf(f, "%sexec:%s\n", prefix, filter->cmd);
 }
 
-int cgit_open_filter(struct cgit_filter *filter, ...)
-{
-	int result;
-	va_list ap;
-	va_start(ap, filter);
-	result = filter->open(filter, ap);
-	va_end(ap);
-	return result;
-}
-
-int cgit_close_filter(struct cgit_filter *filter)
-{
-	return filter->close(filter);
-}
-
-void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
-{
-	filter->fprintf(filter, f, prefix);
-}
-
-void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
+static void cleanup_exec_filter(struct cgit_filter *base)
 {
-	memset(filter, 0, sizeof(*filter));
-	filter->base.open = open_exec_filter;
-	filter->base.close = close_exec_filter;
-	filter->base.fprintf = fprintf_exec_filter;
-	filter->cmd = cmd;
-	filter->argv = argv;
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base;
+	if (filter->argv) {
+		free(filter->argv);
+		filter->argv = NULL;
+	}
+	if (filter->cmd) {
+		free(filter->cmd);
+		filter->cmd = NULL;
+	}
 }
 
 static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filtertype)
@@ -125,6 +127,39 @@ static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filterty
 	return &f->base;
 }
 
+void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
+{
+	memset(filter, 0, sizeof(*filter));
+	filter->base.open = open_exec_filter;
+	filter->base.close = close_exec_filter;
+	filter->base.fprintf = fprintf_exec_filter;
+	filter->base.cleanup = cleanup_exec_filter;
+	filter->cmd = cmd;
+	filter->argv = argv;
+}
+
+int cgit_open_filter(struct cgit_filter *filter, ...)
+{
+	int result;
+	va_list ap;
+	va_start(ap, filter);
+	result = filter->open(filter, ap);
+	va_end(ap);
+	return result;
+}
+
+int cgit_close_filter(struct cgit_filter *filter)
+{
+	return filter->close(filter);
+}
+
+void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
+{
+	filter->fprintf(filter, f, prefix);
+}
+
+
+
 static const struct {
 	const char *prefix;
 	struct cgit_filter *(*ctor)(const char *cmd, filter_type filtertype);
@@ -161,3 +196,4 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
 
 	die("Invalid filter type: %.*s", (int) len, cmd);
 }
+
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 5/9] filter: basic write hooking infrastructure
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
                   ` (3 preceding siblings ...)
  2014-01-13 14:00 ` [PATCH v2 4/9] filter: allow for cleanup hook for filter types Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 6/9] filter: add lua support Jason
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


Filters can now call hook_write and unhook_write if they want to
redirect writing to stdout to a different function. This saves us from
potential file descriptor pipes and other less efficient mechanisms.

We do this instead of replacing the call in html_raw because some places
stdlib's printf functions are used (ui-patch or within git itself),
which has its own internal buffering, which makes it difficult to
interlace our function calls. So, we dlsym libc's write and then
override it in the link stage.

While we're at it, we move considerations of argument count into the
generic new filter handler.

Signed-off-by: Jason A. Donenfeld <Jason at zx2c4.com>
---
 cgit.c   |  2 ++
 cgit.h   |  3 ++-
 cgit.mk  |  4 +++-
 filter.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++-----------------
 4 files changed, 67 insertions(+), 23 deletions(-)

diff --git a/cgit.c b/cgit.c
index 4f31e58..725fd65 100644
--- a/cgit.c
+++ b/cgit.c
@@ -904,6 +904,8 @@ int main(int argc, const char **argv)
 	const char *path;
 	int err, ttl;
 
+	cgit_init_filters();
+
 	prepare_context(&ctx);
 	cgit_repolist.length = 0;
 	cgit_repolist.count = 0;
diff --git a/cgit.h b/cgit.h
index 893c38f..519d2af 100644
--- a/cgit.h
+++ b/cgit.h
@@ -61,13 +61,13 @@ struct cgit_filter {
 	int (*close)(struct cgit_filter *);
 	void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix);
 	void (*cleanup)(struct cgit_filter *);
+	int argument_count;
 };
 
 struct cgit_exec_filter {
 	struct cgit_filter base;
 	char *cmd;
 	char **argv;
-	int extra_args;
 	int old_stdout;
 	int pipe_fh[2];
 	int pid;
@@ -357,6 +357,7 @@ extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char
 extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
 extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
 extern void cgit_cleanup_filters(void);
+extern void cgit_init_filters(void);
 
 extern void cgit_prepare_repo_env(struct cgit_repo * repo);
 
diff --git a/cgit.mk b/cgit.mk
index 19a76e7..9d6dea8 100644
--- a/cgit.mk
+++ b/cgit.mk
@@ -61,6 +61,8 @@ $(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION
 $(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \
 	-DCGIT_VERSION='"$(CGIT_VERSION)"'
 
+CGIT_LIBS += -ldl
+
 
 # Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not
 # handled by that and we must handle them ourselves.
@@ -88,4 +90,4 @@ $(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs)
 	$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $<
 
 $(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS)
-	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS)
diff --git a/filter.c b/filter.c
index 30bc74b..f5a5992 100644
--- a/filter.c
+++ b/filter.c
@@ -12,6 +12,11 @@
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
+#include <dlfcn.h>
+
+static ssize_t (*libc_write)(int fd, const void *buf, size_t count);
+static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL;
+static struct cgit_filter *current_write_filter = NULL;
 
 static inline void reap_filter(struct cgit_filter *filter)
 {
@@ -32,12 +37,43 @@ void cgit_cleanup_filters(void)
 	}
 }
 
+void cgit_init_filters(void)
+{
+	libc_write = dlsym(RTLD_NEXT, "write");
+	if (!libc_write)
+		die("Could not locate libc's write function");
+}
+
+ssize_t write(int fd, const void *buf, size_t count)
+{
+	if (fd != STDOUT_FILENO || !filter_write)
+		return libc_write(fd, buf, count);
+	return filter_write(current_write_filter, buf, count);
+}
+
+static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count))
+{
+	/* We want to avoid buggy nested patterns. */
+	assert(filter_write == NULL);
+	assert(current_write_filter == NULL);
+	current_write_filter = filter;
+	filter_write = new_write;
+}
+
+static inline void unhook_write()
+{
+	assert(filter_write != NULL);
+	assert(current_write_filter != NULL);
+	filter_write = NULL;
+	current_write_filter = NULL;
+}
+
 static int open_exec_filter(struct cgit_filter *base, va_list ap)
 {
 	struct cgit_exec_filter *filter = (struct cgit_exec_filter *) base;
 	int i;
 
-	for (i = 0; i < filter->extra_args; i++)
+	for (i = 0; i < filter->base.argument_count; i++)
 		filter->argv[i+1] = va_arg(ap, char *);
 
 	filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
@@ -74,7 +110,7 @@ static int close_exec_filter(struct cgit_filter *base)
 	die("Subprocess %s exited abnormally", filter->cmd);
 
 done:
-	for (i = 0; i < filter->extra_args; i++)
+	for (i = 0; i < filter->base.argument_count; i++)
 		filter->argv[i+1] = NULL;
 	return 0;
 
@@ -99,7 +135,7 @@ static void cleanup_exec_filter(struct cgit_filter *base)
 	}
 }
 
-static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filtertype)
+static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count)
 {
 	struct cgit_exec_filter *f;
 	int args_size = 0;
@@ -107,20 +143,8 @@ static struct cgit_filter *new_exec_filter(const char *cmd, filter_type filterty
 	f = xmalloc(sizeof(*f));
 	/* We leave argv for now and assign it below. */
 	cgit_exec_filter_init(f, xstrdup(cmd), NULL);
-
-	switch (filtertype) {
-		case SOURCE:
-		case ABOUT:
-			f->extra_args = 1;
-			break;
-
-		case COMMIT:
-		default:
-			f->extra_args = 0;
-			break;
-	}
-
-	args_size = (2 + f->extra_args) * sizeof(char *);
+	f->base.argument_count = argument_count;
+	args_size = (2 + argument_count) * sizeof(char *);
 	f->argv = xmalloc(args_size);
 	memset(f->argv, 0, args_size);
 	f->argv[0] = f->cmd;
@@ -136,6 +160,8 @@ void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **ar
 	filter->base.cleanup = cleanup_exec_filter;
 	filter->cmd = cmd;
 	filter->argv = argv;
+	/* The argument count for open_filter is zero by default, unless called from new_filter, above. */
+	filter->base.argument_count = 0;
 }
 
 int cgit_open_filter(struct cgit_filter *filter, ...)
@@ -162,7 +188,7 @@ void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix
 
 static const struct {
 	const char *prefix;
-	struct cgit_filter *(*ctor)(const char *cmd, filter_type filtertype);
+	struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
 } filter_specs[] = {
 	{ "exec", new_exec_filter },
 };
@@ -172,6 +198,8 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
 	char *colon;
 	int i;
 	size_t len;
+	int argument_count;
+
 	if (!cmd || !cmd[0])
 		return NULL;
 
@@ -184,16 +212,27 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
 	if (len == 1)
 		colon = NULL;
 
+	switch (filtertype) {
+		case SOURCE:
+		case ABOUT:
+			argument_count = 1;
+			break;
+
+		case COMMIT:
+		default:
+			argument_count = 0;
+			break;
+	}
+
 	/* If no prefix is given, exec filter is the default. */
 	if (!colon)
-		return new_exec_filter(cmd, filtertype);
+		return new_exec_filter(cmd, argument_count);
 
 	for (i = 0; i < ARRAY_SIZE(filter_specs); i++) {
 		if (len == strlen(filter_specs[i].prefix) &&
 		    !strncmp(filter_specs[i].prefix, cmd, len))
-			return filter_specs[i].ctor(colon + 1, filtertype);
+			return filter_specs[i].ctor(colon + 1, argument_count);
 	}
 
 	die("Invalid filter type: %.*s", (int) len, cmd);
 }
-
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 6/9] filter: add lua support
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
                   ` (4 preceding siblings ...)
  2014-01-13 14:00 ` [PATCH v2 5/9] filter: basic write hooking infrastructure Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 7/9] filter: return on null filter from open and close Jason
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


Signed-off-by: Jason A. Donenfeld <Jason at zx2c4.com>
---
 cgit.mk      |  22 ++++++-
 cgitrc.5.txt |  29 ++++++++++
 filter.c     | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 234 insertions(+), 3 deletions(-)

diff --git a/cgit.mk b/cgit.mk
index 9d6dea8..25f2eab 100644
--- a/cgit.mk
+++ b/cgit.mk
@@ -25,6 +25,25 @@ ifdef NO_C99_FORMAT
 	CFLAGS += -DNO_C99_FORMAT
 endif
 
+ifdef NO_LUA
+	CFLAGS += -DNO_LUA
+else
+
+ifeq (VANILLA,$(LUA_IMPLEMENTATION))
+	CFLAGS += -llua
+else
+	LUAJIT_LIBS := $(shell pkg-config --libs luajit)
+	LUAJIT_CFLAGS := $(shell pkg-config --cflags luajit)
+	CGIT_LIBS += $(LUAJIT_LIBS)
+	CFLAGS += $(LUAJIT_CFLAGS)
+endif
+
+endif
+
+CGIT_LIBS += -ldl
+
+
+
 CGIT_OBJ_NAMES += cgit.o
 CGIT_OBJ_NAMES += cache.o
 CGIT_OBJ_NAMES += cmd.o
@@ -61,9 +80,6 @@ $(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION
 $(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \
 	-DCGIT_VERSION='"$(CGIT_VERSION)"'
 
-CGIT_LIBS += -ldl
-
-
 # Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not
 # handled by that and we must handle them ourselves.
 cgit_dep_files := $(foreach f,$(CGIT_OBJS),$(dir $f).depend/$(notdir $f).d)
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 60159f6..78f33c8 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -564,6 +564,35 @@ specification with the relevant string; available values are:
 'exec:'::
 	The default "one process per filter" mode.
 
+'lua:'::
+	Executes the script using a built-in Lua interpreter. The script is
+	loaded once per execution of cgit, and may be called multiple times
+	during cgit's lifetime, making it a good choice for repeated filters
+	such as the 'email filter'. It responds to three functions:
+
+	'filter_open(argument1, argument2, argument3, ...)'::
+		This is called upon activation of the filter for a particular
+		set of data.
+	'filter_write(buffer)'::
+		This is called whenever cgit writes data to the webpage.
+	'filter_close()'::
+		This is called when the current filtering operation is
+		completed.
+	
+	Additionally, cgit exposes to the Lua the following built-in functions:
+
+	'html(str)'::
+		Writes 'str' to the webpage.
+	'html_txt(str)'::
+		HTML escapes and writes 'str' to the webpage.
+	'html_attr(str)'::
+		HTML escapes for an attribute and writes "str' to the webpage.
+	'html_url_path(str)'::
+		URL escapes for a path and writes 'str' to the webpage.
+	'html_url_arg(str)'::
+		URL escapes for an argument and writes 'str' to the webpage.
+
+
 Parameters are provided to filters as follows.
 
 about filter::
diff --git a/filter.c b/filter.c
index f5a5992..3702585 100644
--- a/filter.c
+++ b/filter.c
@@ -7,12 +7,19 @@
  */
 
 #include "cgit.h"
+#include "html.h"
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <dlfcn.h>
+#include <errno.h>
+#ifndef NO_LUA
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+#endif
 
 static ssize_t (*libc_write)(int fd, const void *buf, size_t count);
 static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL;
@@ -164,6 +171,182 @@ void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **ar
 	filter->base.argument_count = 0;
 }
 
+#ifndef NO_LUA
+struct lua_filter {
+	struct cgit_filter base;
+	char *script_file;
+	lua_State *lua_state;
+};
+
+static void error_lua_filter(struct lua_filter *filter)
+{
+	die("Lua error in %s: %s", filter->script_file, lua_tostring(filter->lua_state, -1));
+	lua_pop(filter->lua_state, 1);
+}
+
+static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf, size_t count)
+{
+	struct lua_filter *filter = (struct lua_filter *) base;
+
+	lua_getglobal(filter->lua_state, "filter_write");
+	lua_pushlstring(filter->lua_state, buf, count);
+	if (lua_pcall(filter->lua_state, 1, 0, 0)) {
+		error_lua_filter(filter);
+		errno = EIO;
+		return -1;
+	}
+	return count;
+}
+
+static inline int hook_lua_filter(lua_State *lua_state, void (*fn)(const char *txt))
+{
+	const char *str;
+	ssize_t (*save_filter_write)(struct cgit_filter *base, const void *buf, size_t count);
+	struct cgit_filter *save_filter;
+
+	str = lua_tostring(lua_state, 1);
+	if (!str)
+		return 0;
+	
+	save_filter_write = filter_write;
+	save_filter = current_write_filter;
+	unhook_write();
+	fn(str);
+	hook_write(save_filter, save_filter_write);
+
+	return 0;
+}
+
+static int html_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html);
+}
+
+static int html_txt_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_txt);
+}
+
+static int html_attr_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_attr);
+}
+
+static int html_url_path_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_url_path);
+}
+
+static int html_url_arg_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_url_arg);
+}
+
+static void cleanup_lua_filter(struct cgit_filter *base)
+{
+	struct lua_filter *filter = (struct lua_filter *) base;
+
+	if (!filter->lua_state)
+		return;
+
+	lua_close(filter->lua_state);
+	filter->lua_state = NULL;
+	if (filter->script_file) {
+		free(filter->script_file);
+		filter->script_file = NULL;
+	}
+}
+
+static int init_lua_filter(struct lua_filter *filter)
+{
+	if (filter->lua_state)
+		return 0;
+
+	if (!(filter->lua_state = luaL_newstate()))
+		return 1;
+
+	luaL_openlibs(filter->lua_state);
+
+	lua_pushcfunction(filter->lua_state, html_lua_filter);
+	lua_setglobal(filter->lua_state, "html");
+	lua_pushcfunction(filter->lua_state, html_txt_lua_filter);
+	lua_setglobal(filter->lua_state, "html_txt");
+	lua_pushcfunction(filter->lua_state, html_attr_lua_filter);
+	lua_setglobal(filter->lua_state, "html_attr");
+	lua_pushcfunction(filter->lua_state, html_url_path_lua_filter);
+	lua_setglobal(filter->lua_state, "html_url_path");
+	lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter);
+	lua_setglobal(filter->lua_state, "html_url_arg");
+
+	if (luaL_dofile(filter->lua_state, filter->script_file)) {
+		error_lua_filter(filter);
+		lua_close(filter->lua_state);
+		filter->lua_state = NULL;
+		return 1;
+	}
+	return 0;
+}
+
+static int open_lua_filter(struct cgit_filter *base, va_list ap)
+{
+	struct lua_filter *filter = (struct lua_filter *) base;
+	int i;
+
+	if (init_lua_filter(filter))
+		return 1;
+
+	hook_write(base, write_lua_filter);
+
+	lua_getglobal(filter->lua_state, "filter_open");
+	for (i = 0; i < filter->base.argument_count; ++i)
+		lua_pushstring(filter->lua_state, va_arg(ap, char *));
+	if (lua_pcall(filter->lua_state, filter->base.argument_count, 0, 0)) {
+		error_lua_filter(filter);
+		return 1;
+	}
+	return 0;
+}
+
+static int close_lua_filter(struct cgit_filter *base)
+{
+	struct lua_filter *filter = (struct lua_filter *) base;
+	int ret = 0;
+
+	lua_getglobal(filter->lua_state, "filter_close");
+	if (lua_pcall(filter->lua_state, 0, 0, 0)) {
+		error_lua_filter(filter);
+		ret = 1;
+	}
+	unhook_write();
+	return ret;
+}
+
+static void fprintf_lua_filter(struct cgit_filter *base, FILE *f, const char *prefix)
+{
+	struct lua_filter *filter = (struct lua_filter *) base;
+	fprintf(f, "%slua:%s\n", prefix, filter->script_file);
+}
+
+
+static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
+{
+	struct lua_filter *filter;
+
+	filter = xmalloc(sizeof(*filter));
+	memset(filter, 0, sizeof(*filter));
+	filter->base.open = open_lua_filter;
+	filter->base.close = close_lua_filter;
+	filter->base.fprintf = fprintf_lua_filter;
+	filter->base.cleanup = cleanup_lua_filter;
+	filter->base.argument_count = argument_count;
+	filter->script_file = xstrdup(cmd);
+
+	return &filter->base;
+}
+
+#endif
+
+
 int cgit_open_filter(struct cgit_filter *filter, ...)
 {
 	int result;
@@ -191,6 +374,9 @@ static const struct {
 	struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
 } filter_specs[] = {
 	{ "exec", new_exec_filter },
+#ifndef NO_LUA
+	{ "lua", new_lua_filter },
+#endif
 };
 
 struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 7/9] filter: return on null filter from open and close
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
                   ` (5 preceding siblings ...)
  2014-01-13 14:00 ` [PATCH v2 6/9] filter: add lua support Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 8/9] filter: add support for email filter Jason
  2014-01-13 14:00 ` [PATCH v2 9/9] filter: add gravatar scripts Jason
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


So that we don't have to include the if(filter) open_filter(filter)
block everywhere, we introduce the guard in the function itself. This
should simplify quite a bit of code.

Signed-off-by: Jason A. Donenfeld <Jason at zx2c4.com>
---
 filter.c      |  4 ++++
 ui-commit.c   | 18 ++++++------------
 ui-repolist.c |  6 ++----
 ui-summary.c  |  8 ++------
 4 files changed, 14 insertions(+), 22 deletions(-)

diff --git a/filter.c b/filter.c
index 3702585..7983737 100644
--- a/filter.c
+++ b/filter.c
@@ -351,6 +351,8 @@ int cgit_open_filter(struct cgit_filter *filter, ...)
 {
 	int result;
 	va_list ap;
+	if (!filter)
+		return 0;
 	va_start(ap, filter);
 	result = filter->open(filter, ap);
 	va_end(ap);
@@ -359,6 +361,8 @@ int cgit_open_filter(struct cgit_filter *filter, ...)
 
 int cgit_close_filter(struct cgit_filter *filter)
 {
+	if (!filter)
+		return 0;
 	return filter->close(filter);
 }
 
diff --git a/ui-commit.c b/ui-commit.c
index aa1892f..5ac79c0 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -107,28 +107,22 @@ void cgit_print_commit(char *hex, const char *prefix)
 	}
 	html("</table>\n");
 	html("<div class='commit-subject'>");
-	if (ctx.repo->commit_filter)
-		cgit_open_filter(ctx.repo->commit_filter);
+	cgit_open_filter(ctx.repo->commit_filter);
 	html_txt(info->subject);
-	if (ctx.repo->commit_filter)
-		cgit_close_filter(ctx.repo->commit_filter);
+	cgit_close_filter(ctx.repo->commit_filter);
 	show_commit_decorations(commit);
 	html("</div>");
 	html("<div class='commit-msg'>");
-	if (ctx.repo->commit_filter)
-		cgit_open_filter(ctx.repo->commit_filter);
+	cgit_open_filter(ctx.repo->commit_filter);
 	html_txt(info->msg);
-	if (ctx.repo->commit_filter)
-		cgit_close_filter(ctx.repo->commit_filter);
+	cgit_close_filter(ctx.repo->commit_filter);
 	html("</div>");
 	if (notes.len != 0) {
 		html("<div class='notes-header'>Notes</div>");
 		html("<div class='notes'>");
-		if (ctx.repo->commit_filter)
-			cgit_open_filter(ctx.repo->commit_filter);
+		cgit_open_filter(ctx.repo->commit_filter);
 		html_txt(notes.buf);
-		if (ctx.repo->commit_filter)
-			cgit_close_filter(ctx.repo->commit_filter);
+		cgit_close_filter(ctx.repo->commit_filter);
 		html("</div>");
 		html("<div class='notes-footer'></div>");
 	}
diff --git a/ui-repolist.c b/ui-repolist.c
index 7b1fec3..f9cb21a 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -333,9 +333,7 @@ void cgit_print_site_readme()
 {
 	if (!ctx.cfg.root_readme)
 		return;
-	if (ctx.cfg.about_filter)
-		cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme);
+	cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme);
 	html_include(ctx.cfg.root_readme);
-	if (ctx.cfg.about_filter)
-		cgit_close_filter(ctx.cfg.about_filter);
+	cgit_close_filter(ctx.cfg.about_filter);
 }
diff --git a/ui-summary.c b/ui-summary.c
index 725f3ab..ddd8f1b 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -151,16 +151,12 @@ void cgit_print_repo_readme(char *path)
 	 * filesystem, while applying the about-filter.
 	 */
 	html("<div id='summary'>");
-	if (ctx.repo->about_filter)
-		cgit_open_filter(ctx.repo->about_filter, filename);
-
+	cgit_open_filter(ctx.repo->about_filter, filename);
 	if (ref)
 		cgit_print_file(filename, ref, 1);
 	else
 		html_include(filename);
-
-	if (ctx.repo->about_filter)
-		cgit_close_filter(ctx.repo->about_filter);
+	cgit_close_filter(ctx.repo->about_filter);
 
 	html("</div>");
 	if (free_filename)
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 8/9] filter: add support for email filter
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
                   ` (6 preceding siblings ...)
  2014-01-13 14:00 ` [PATCH v2 7/9] filter: return on null filter from open and close Jason
@ 2014-01-13 14:00 ` Jason
  2014-01-13 14:00 ` [PATCH v2 9/9] filter: add gravatar scripts Jason
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


Signed-off-by: Jason A. Donenfeld <Jason at zx2c4.com>
---
 cgit.c       |  6 ++++++
 cgit.h       |  4 +++-
 cgitrc.5.txt | 18 ++++++++++++++++++
 filter.c     |  3 +++
 shared.c     |  1 +
 ui-commit.c  |  4 ++++
 ui-log.c     |  2 ++
 ui-refs.c    |  9 ++++++++-
 ui-tag.c     |  2 ++
 9 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/cgit.c b/cgit.c
index 725fd65..f3fe56b 100644
--- a/cgit.c
+++ b/cgit.c
@@ -89,6 +89,8 @@ static void repo_config(struct cgit_repo *repo, const char *name, const char *va
 			repo->commit_filter = cgit_new_filter(value, COMMIT);
 		else if (!strcmp(name, "source-filter"))
 			repo->source_filter = cgit_new_filter(value, SOURCE);
+		else if (!strcmp(name, "email-filter"))
+			repo->email_filter = cgit_new_filter(value, EMAIL);
 	}
 }
 
@@ -188,6 +190,8 @@ static void config_cb(const char *name, const char *value)
 		ctx.cfg.about_filter = cgit_new_filter(value, ABOUT);
 	else if (!strcmp(name, "commit-filter"))
 		ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT);
+	else if (!strcmp(name, "email-filter"))
+		ctx.cfg.email_filter = cgit_new_filter(value, EMAIL);
 	else if (!strcmp(name, "embedded"))
 		ctx.cfg.embedded = atoi(value);
 	else if (!strcmp(name, "max-atom-items"))
@@ -711,6 +715,8 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
 		cgit_fprintf_filter(repo->commit_filter, f, "repo.commit-filter=");
 	if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
 		cgit_fprintf_filter(repo->source_filter, f, "repo.source-filter=");
+	if (repo->email_filter && repo->email_filter != ctx.cfg.email_filter)
+		cgit_fprintf_filter(repo->email_filter, f, "repo.email-filter=");
 	if (repo->snapshots != ctx.cfg.snapshots) {
 		char *tmp = build_snapshot_setting(repo->snapshots);
 		fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : "");
diff --git a/cgit.h b/cgit.h
index 519d2af..e200a06 100644
--- a/cgit.h
+++ b/cgit.h
@@ -53,7 +53,7 @@ typedef void (*filepair_fn)(struct diff_filepair *pair);
 typedef void (*linediff_fn)(char *line, int len);
 
 typedef enum {
-	ABOUT, COMMIT, SOURCE
+	ABOUT, COMMIT, SOURCE, EMAIL
 } filter_type;
 
 struct cgit_filter {
@@ -99,6 +99,7 @@ struct cgit_repo {
 	struct cgit_filter *about_filter;
 	struct cgit_filter *commit_filter;
 	struct cgit_filter *source_filter;
+	struct cgit_filter *email_filter;
 	struct string_list submodules;
 };
 
@@ -250,6 +251,7 @@ struct cgit_config {
 	struct cgit_filter *about_filter;
 	struct cgit_filter *commit_filter;
 	struct cgit_filter *source_filter;
+	struct cgit_filter *email_filter;
 };
 
 struct cgit_page {
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 78f33c8..b7dc5a4 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -117,6 +117,14 @@ css::
 	Url which specifies the css document to include in all cgit pages.
 	Default value: "/cgit.css".
 
+email-filter::
+	Specifies a command which will be invoked to format names and email
+	address of committers, authors, and taggers, as represented in various
+	places throughout the cgit interface. This command will receive an
+	email address as its only command line argument, and the text to
+	format on STDIN. It is to write the formatted text back out onto
+	STDOUT. Default value: none. See also: "FILTER API".
+
 embedded::
 	Flag which, when set to "1", will make cgit generate a html fragment
 	suitable for embedding in other html pages. Default value: none. See
@@ -457,6 +465,10 @@ repo.defbranch::
 repo.desc::
 	The value to show as repository description. Default value: none.
 
+repo.email-filter::
+	Override the default email-filter. Default value: none. See also:
+	"enable-filter-overrides". See also: "FILTER API".
+
 repo.enable-commit-graph::
 	A flag which can be used to disable the global setting
 	`enable-commit-graph'. Default value: none.
@@ -607,6 +619,12 @@ commit filter::
 	be filtered is available on standard input and the filtered text is
 	expected on standard output.
 
+email filter::
+	This filter is given a single parameter: the email address of the
+	relevent user. The filter will then receive the text string to format
+	on standard input and is expected to write to standard output the
+	formatted text to be included in the page.
+
 source filter::
 	This filter is given a single parameter: the filename of the source
 	file to filter. The filter can use the filename to determine (for
diff --git a/filter.c b/filter.c
index 7983737..08ce7a5 100644
--- a/filter.c
+++ b/filter.c
@@ -37,10 +37,12 @@ void cgit_cleanup_filters(void)
 	reap_filter(ctx.cfg.about_filter);
 	reap_filter(ctx.cfg.commit_filter);
 	reap_filter(ctx.cfg.source_filter);
+	reap_filter(ctx.cfg.email_filter);
 	for (i = 0; i < cgit_repolist.count; ++i) {
 		reap_filter(cgit_repolist.repos[i].about_filter);
 		reap_filter(cgit_repolist.repos[i].commit_filter);
 		reap_filter(cgit_repolist.repos[i].source_filter);
+		reap_filter(cgit_repolist.repos[i].email_filter);
 	}
 }
 
@@ -403,6 +405,7 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
 		colon = NULL;
 
 	switch (filtertype) {
+		case EMAIL:
 		case SOURCE:
 		case ABOUT:
 			argument_count = 1;
diff --git a/shared.c b/shared.c
index 4626148..7e88bbd 100644
--- a/shared.c
+++ b/shared.c
@@ -71,6 +71,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
 	ret->about_filter = ctx.cfg.about_filter;
 	ret->commit_filter = ctx.cfg.commit_filter;
 	ret->source_filter = ctx.cfg.source_filter;
+	ret->email_filter = ctx.cfg.email_filter;
 	ret->clone_url = ctx.cfg.clone_url;
 	ret->submodules.strdup_strings = 1;
 	return ret;
diff --git a/ui-commit.c b/ui-commit.c
index 5ac79c0..bd14ef0 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -44,20 +44,24 @@ void cgit_print_commit(char *hex, const char *prefix)
 	cgit_print_diff_ctrls();
 	html("<table summary='commit info' class='commit-info'>\n");
 	html("<tr><th>author</th><td>");
+	cgit_open_filter(ctx.repo->email_filter, info->author_email);
 	html_txt(info->author);
 	if (!ctx.cfg.noplainemail) {
 		html(" ");
 		html_txt(info->author_email);
 	}
+	cgit_close_filter(ctx.repo->email_filter);
 	html("</td><td class='right'>");
 	cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
 	html("</td></tr>\n");
 	html("<tr><th>committer</th><td>");
+	cgit_open_filter(ctx.repo->email_filter, info->committer_email);
 	html_txt(info->committer);
 	if (!ctx.cfg.noplainemail) {
 		html(" ");
 		html_txt(info->committer_email);
 	}
+	cgit_close_filter(ctx.repo->email_filter);
 	html("</td><td class='right'>");
 	cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
 	html("</td></tr>\n");
diff --git a/ui-log.c b/ui-log.c
index 584336a..957d887 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -168,7 +168,9 @@ static void print_commit(struct commit *commit, struct rev_info *revs)
 			 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
 	show_commit_decorations(commit);
 	html("</td><td>");
+	cgit_open_filter(ctx.repo->email_filter, info->author_email);
 	html_txt(info->author);
+	cgit_close_filter(ctx.repo->email_filter);
 
 	if (revs->graph) {
 		html("</td><td>");
diff --git a/ui-refs.c b/ui-refs.c
index c97b0c6..d125459 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -77,7 +77,9 @@ static int print_branch(struct refinfo *ref)
 	if (ref->object->type == OBJ_COMMIT) {
 		cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0);
 		html("</td><td>");
+		cgit_open_filter(ctx.repo->email_filter, info->author_email);
 		html_txt(info->author);
+		cgit_close_filter(ctx.repo->email_filter);
 		html("</td><td colspan='2'>");
 		cgit_print_age(info->commit->date, -1, NULL);
 	} else {
@@ -154,10 +156,15 @@ static int print_tag(struct refinfo *ref)
 		cgit_object_link(obj);
 	html("</td><td>");
 	if (info) {
-		if (info->tagger)
+		if (info->tagger) {
+			cgit_open_filter(ctx.repo->email_filter, info->tagger_email);
 			html_txt(info->tagger);
+			cgit_close_filter(ctx.repo->email_filter);
+		}
 	} else if (ref->object->type == OBJ_COMMIT) {
+		cgit_open_filter(ctx.repo->email_filter, ref->commit->author_email);
 		html_txt(ref->commit->author);
+		cgit_close_filter(ctx.repo->email_filter);
 	}
 	html("</td><td colspan='2'>");
 	if (info) {
diff --git a/ui-tag.c b/ui-tag.c
index ec9c757..adbdb90 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -77,11 +77,13 @@ void cgit_print_tag(char *revname)
 		}
 		if (info->tagger) {
 			html("<tr><td>tagged by</td><td>");
+			cgit_open_filter(ctx.repo->email_filter, info->tagger_email);
 			html_txt(info->tagger);
 			if (info->tagger_email && !ctx.cfg.noplainemail) {
 				html(" ");
 				html_txt(info->tagger_email);
 			}
+			cgit_close_filter(ctx.repo->email_filter);
 			html("</td></tr>\n");
 		}
 		html("<tr><td>tagged object</td><td class='sha1'>");
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v2 9/9] filter: add gravatar scripts
  2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
                   ` (7 preceding siblings ...)
  2014-01-13 14:00 ` [PATCH v2 8/9] filter: add support for email filter Jason
@ 2014-01-13 14:00 ` Jason
  8 siblings, 0 replies; 10+ messages in thread
From: Jason @ 2014-01-13 14:00 UTC (permalink / raw)


The lua one is hugely faster than the python one, but both are included
for comparison.

Signed-off-by: Jason A. Donenfeld <Jason at zx2c4.com>
---
 filters/email-gravatar.lua | 25 +++++++++++++++++++++++++
 filters/email-gravatar.py  | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+)
 create mode 100644 filters/email-gravatar.lua
 create mode 100755 filters/email-gravatar.py

diff --git a/filters/email-gravatar.lua b/filters/email-gravatar.lua
new file mode 100644
index 0000000..ef1bdbc
--- /dev/null
+++ b/filters/email-gravatar.lua
@@ -0,0 +1,25 @@
+-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
+-- It adds gravatar icons to author names. It is designed to be used with the lua:
+-- prefix in filters. It is much faster than the corresponding python script.
+--
+-- Requirements:
+-- 	luacrypto >= 0.3
+-- 	<http://mkottman.github.io/luacrypto/>
+--
+
+require("crypto")
+
+function filter_open(email)
+	buffer = ""
+	md5 = crypto.digest("md5", email:sub(2, -2):lower())
+end
+
+function filter_close()
+	html("<img src='//www.gravatar.com/avatar/" .. md5 .. "?s=16&d=retro' style='height:10pt;width:10pt'> " .. buffer)
+end
+
+function filter_write(str)
+	buffer = buffer .. str
+end
+
+
diff --git a/filters/email-gravatar.py b/filters/email-gravatar.py
new file mode 100755
index 0000000..52a184b
--- /dev/null
+++ b/filters/email-gravatar.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This
+# script is very slow, in comparison.
+#
+# This script may be used with the email-filter or repo.email-filter settings in cgitrc.
+#
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL        ( = repo.url       setting )
+# CGIT_REPO_NAME       ( = repo.name      setting )
+# CGIT_REPO_PATH       ( = repo.path      setting )
+# CGIT_REPO_OWNER      ( = repo.owner     setting )
+# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
+# CGIT_REPO_SECTION    ( = section        setting )
+# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
+#
+# It receives an email address on argv[1] and text on stdin. It prints
+# to stdout that text prepended by a gravatar at 10pt.
+
+import sys
+import hashlib
+
+email = sys.argv[1].lower().strip()
+if email[0] == '<':
+        email = email[1:]
+if email[-1] == '>':
+        email = email[0:-1]
+
+md5 = hashlib.md5(email.encode()).hexdigest()
+text = sys.stdin.read().strip()
+
+print("<img src='//www.gravatar.com/avatar/" + md5 + "?s=16&d=retro' style='height:10pt;width:10pt'> " + text)
-- 
1.8.5.2



^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2014-01-13 14:00 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-01-13 14:00 [PATCH v2 0/9] filter framework and lua support: complete Jason
2014-01-13 14:00 ` [PATCH v2 1/9] filter: add fprintf_filter function Jason
2014-01-13 14:00 ` [PATCH v2 2/9] filter: add interface layer Jason
2014-01-13 14:00 ` [PATCH v2 3/9] filter: introduce "filter type" prefix Jason
2014-01-13 14:00 ` [PATCH v2 4/9] filter: allow for cleanup hook for filter types Jason
2014-01-13 14:00 ` [PATCH v2 5/9] filter: basic write hooking infrastructure Jason
2014-01-13 14:00 ` [PATCH v2 6/9] filter: add lua support Jason
2014-01-13 14:00 ` [PATCH v2 7/9] filter: return on null filter from open and close Jason
2014-01-13 14:00 ` [PATCH v2 8/9] filter: add support for email filter Jason
2014-01-13 14:00 ` [PATCH v2 9/9] filter: add gravatar scripts Jason

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).