diff --git a/Makefile.local b/Makefile.local index a890df20..296995d3 100644 --- a/Makefile.local +++ b/Makefile.local @@ -290,6 +290,8 @@ notmuch_client_srcs = \ notmuch-show.c \ notmuch-tag.c \ notmuch-time.c \ + sprinter-json.c \ + sprinter-text.c \ query-string.c \ mime-node.c \ crypto.c \ diff --git a/sprinter-json.c b/sprinter-json.c new file mode 100644 index 00000000..46496551 --- /dev/null +++ b/sprinter-json.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include "sprinter.h" + +struct sprinter_json { + struct sprinter vtable; + FILE *stream; + /* Top of the state stack, or NULL if the printer is not currently + * inside any aggregate types. */ + struct json_state *state; + + /* A flag to signify that a separator should be inserted in the + * output as soon as possible. + */ + notmuch_bool_t insert_separator; +}; + +struct json_state { + struct json_state *parent; + /* True if nothing has been printed in this aggregate yet. + * Suppresses the comma before a value. */ + notmuch_bool_t first; + /* The character that closes the current aggregate. */ + char close; +}; + +/* Helper function to set up the stream to print a value. If this + * value follows another value, prints a comma. */ +static struct sprinter_json * +json_begin_value (struct sprinter *sp) +{ + struct sprinter_json *spj = (struct sprinter_json *) sp; + + if (spj->state) { + if (! spj->state->first) { + fputc (',', spj->stream); + if (spj->insert_separator) { + fputc ('\n', spj->stream); + spj->insert_separator = FALSE; + } else { + fputc (' ', spj->stream); + } + } else { + spj->state->first = FALSE; + } + } + return spj; +} + +/* Helper function to begin an aggregate type. Prints the open + * character and pushes a new state frame. */ +static void +json_begin_aggregate (struct sprinter *sp, char open, char close) +{ + struct sprinter_json *spj = json_begin_value (sp); + struct json_state *state = talloc (spj, struct json_state); + + fputc (open, spj->stream); + state->parent = spj->state; + state->first = TRUE; + state->close = close; + spj->state = state; +} + +static void +json_begin_map (struct sprinter *sp) +{ + json_begin_aggregate (sp, '{', '}'); +} + +static void +json_begin_list (struct sprinter *sp) +{ + json_begin_aggregate (sp, '[', ']'); +} + +static void +json_end (struct sprinter *sp) +{ + struct sprinter_json *spj = (struct sprinter_json *) sp; + struct json_state *state = spj->state; + + fputc (spj->state->close, spj->stream); + spj->state = state->parent; + talloc_free (state); + if (spj->state == NULL) + fputc ('\n', spj->stream); +} + +static void +json_string (struct sprinter *sp, const char *val) +{ + static const char *const escapes[] = { + ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b", + ['\f'] = "\\f", ['\n'] = "\\n", ['\t'] = "\\t" + }; + struct sprinter_json *spj = json_begin_value (sp); + + fputc ('"', spj->stream); + for (; *val; ++val) { + unsigned char ch = *val; + if (ch < ARRAY_SIZE (escapes) && escapes[ch]) + fputs (escapes[ch], spj->stream); + else if (ch >= 32) + fputc (ch, spj->stream); + else + fprintf (spj->stream, "\\u%04x", ch); + } + fputc ('"', spj->stream); +} + +static void +json_integer (struct sprinter *sp, int val) +{ + struct sprinter_json *spj = json_begin_value (sp); + + fprintf (spj->stream, "%d", val); +} + +static void +json_boolean (struct sprinter *sp, notmuch_bool_t val) +{ + struct sprinter_json *spj = json_begin_value (sp); + + fputs (val ? "true" : "false", spj->stream); +} + +static void +json_null (struct sprinter *sp) +{ + struct sprinter_json *spj = json_begin_value (sp); + + fputs ("null", spj->stream); +} + +static void +json_map_key (struct sprinter *sp, const char *key) +{ + struct sprinter_json *spj = (struct sprinter_json *) sp; + + json_string (sp, key); + fputs (": ", spj->stream); + spj->state->first = TRUE; +} + +static void +json_set_prefix (unused (struct sprinter *sp), unused (const char *name)) +{ +} + +static void +json_separator (struct sprinter *sp) +{ + struct sprinter_json *spj = (struct sprinter_json *) sp; + + spj->insert_separator = TRUE; +} + +struct sprinter * +sprinter_json_create (const void *ctx, FILE *stream) +{ + static const struct sprinter_json template = { + .vtable = { + .begin_map = json_begin_map, + .begin_list = json_begin_list, + .end = json_end, + .string = json_string, + .integer = json_integer, + .boolean = json_boolean, + .null = json_null, + .map_key = json_map_key, + .separator = json_separator, + .set_prefix = json_set_prefix, + .is_text_printer = FALSE, + } + }; + struct sprinter_json *res; + + res = talloc (ctx, struct sprinter_json); + if (! res) + return NULL; + + *res = template; + res->stream = stream; + return &res->vtable; +} diff --git a/sprinter-text.c b/sprinter-text.c new file mode 100644 index 00000000..b208840b --- /dev/null +++ b/sprinter-text.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include "sprinter.h" + +/* "Structured printer" interface for unstructured text printing. + * Note that --output=summary is dispatched and formatted in + * notmuch-search.c, the code in this file is only used for all other + * output types. + */ + +struct sprinter_text { + struct sprinter vtable; + FILE *stream; + + /* The current prefix to be printed with string/integer/boolean + * data. + */ + const char *current_prefix; + + /* A flag to indicate if this is the first tag. Used in list of tags + * for summary. + */ + notmuch_bool_t first_tag; +}; + +static void +text_string (struct sprinter *sp, const char *val) +{ + struct sprinter_text *sptxt = (struct sprinter_text *) sp; + + if (sptxt->current_prefix != NULL) + fprintf (sptxt->stream, "%s:", sptxt->current_prefix); + + fputs(val, sptxt->stream); +} + +static void +text_integer (struct sprinter *sp, int val) +{ + struct sprinter_text *sptxt = (struct sprinter_text *) sp; + + fprintf (sptxt->stream, "%d", val); +} + +static void +text_boolean (struct sprinter *sp, notmuch_bool_t val) +{ + struct sprinter_text *sptxt = (struct sprinter_text *) sp; + + fputs (val ? "true" : "false", sptxt->stream); +} + +static void +text_separator (struct sprinter *sp) +{ + struct sprinter_text *sptxt = (struct sprinter_text *) sp; + + fputc ('\n', sptxt->stream); +} + +static void +text_set_prefix (struct sprinter *sp, const char *prefix) +{ + struct sprinter_text *sptxt = (struct sprinter_text *) sp; + + sptxt->current_prefix = prefix; +} + +/* The structure functions begin_map, begin_list, end and map_key + * don't do anything in the text formatter. + */ + +static void +text_begin_map (unused (struct sprinter *sp)) +{ +} + +static void +text_begin_list (unused (struct sprinter *sp)) +{ +} + +static void +text_end (unused (struct sprinter *sp)) +{ +} + +static void +text_null (unused (struct sprinter *sp)) +{ +} + +static void +text_map_key (unused (struct sprinter *sp), unused (const char *key)) +{ +} + +struct sprinter * +sprinter_text_create (const void *ctx, FILE *stream) +{ + static const struct sprinter_text template = { + .vtable = { + .begin_map = text_begin_map, + .begin_list = text_begin_list, + .end = text_end, + .string = text_string, + .integer = text_integer, + .boolean = text_boolean, + .null = text_null, + .map_key = text_map_key, + .separator = text_separator, + .set_prefix = text_set_prefix, + .is_text_printer = TRUE, + }, + }; + struct sprinter_text *res; + + res = talloc (ctx, struct sprinter_text); + if (! res) + return NULL; + + *res = template; + res->stream = stream; + return &res->vtable; +} diff --git a/sprinter.h b/sprinter.h index 77dc26f3..6680d419 100644 --- a/sprinter.h +++ b/sprinter.h @@ -55,4 +55,14 @@ typedef struct sprinter { notmuch_bool_t is_text_printer; } sprinter_t; + +/* Create a new unstructured printer that emits the default text format + * for "notmuch search". */ +struct sprinter * +sprinter_text_create (const void *ctx, FILE *stream); + +/* Create a new structure printer that emits JSON. */ +struct sprinter * +sprinter_json_create (const void *ctx, FILE *stream); + #endif // NOTMUCH_SPRINTER_H