unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* Proposed structure formatter API
@ 2012-06-30  2:59 Austin Clements
  2012-07-12  7:43 ` Structured Formatters for JSON Output craven
  0 siblings, 1 reply; 18+ messages in thread
From: Austin Clements @ 2012-06-30  2:59 UTC (permalink / raw)
  To: Peter Feigl, Jameson Graef Rollins; +Cc: notmuch

[-- Attachment #1: Type: text/plain, Size: 2551 bytes --]

On IRC today, Peter renewed discussions of supporting an S-expression
format, which I think would be a fantastic thing to have.

A while back [1] I tried to explain an API I thought would form a good
foundation for this by abstracting away the differences between JSON and
S-expression output so both structured formats can use the same code,
but I didn't express the idea very clearly.  I built essentially this
API for another project recently and it worked well (and I learned a few
things), so I figured I would give it another shot.  It's simple enough
that I tossed together an example implementation, which is attached.

The idea is similar to the existing format structures, but instead of
dealing with complex, high-level concepts like threads or messages, this
API deals with syntax-level concepts like lists and strings.  All of the
structured output formats can then be supported by a single high-level
formatter backed by different syntax formatters.

There are a few ways something like this could be integrated with the
high-level formatters.  For show, the least invasive thing would be to
have two notmuch_show_format structures with different part functions.
These part functions would be thin wrappers that simply create the right
structure printer and then pass it to a common part function.

It would be even nicer if we could get rid of the
message_set_{start,sep,end} fields, since those duplicate functionality
from the structure printer and their use is haphazard and overly
complicated.  We could do this by changing notmuch_show_format to
something like

typedef struct notmuch_show_format {
    struct sprinter *(*new_sprinter) (const void *ctx);
    notmuch_status_t (*part) (const void *ctx, struct sprinter *sp,
                              struct mime_node *node,
                              const struct notmuch_show_params *params);
} notmuch_show_format_t;

For the JSON and S-expression show formats, new_sprinter would be
sprinter_json_new and sprinter_sexp_new, respectively, and they could
share a part function.  For the text, mbox, and raw formatters,
new_sprinter could simply be NULL, or we could provide a "NULL structure
printer" implementation that does nothing.

We could do something similar for reply.

search_format is more complicated, but might also benefit more.  Most of
those fields have to do with how to print lists and objects and could be
removed if the format simply provided a new_sprinter method like the
notmuch_show_format I suggested above.

[1] id:"20120121220407.GK16740@mit.edu"


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: sprinter.c --]
[-- Type: text/x-csrc, Size: 4987 bytes --]

#include <stdbool.h>
#include <stdio.h>
#include <talloc.h>
#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))

/* Structure printer interface */
struct sprinter
{
    void (*begin_map) (struct sprinter *);
    void (*begin_list) (struct sprinter *);
    void (*end) (struct sprinter *);

    void (*string) (struct sprinter *, const char *);
    void (*integer) (struct sprinter *, int);
    void (*boolean) (struct sprinter *, bool);
    void (*null) (struct sprinter *);
    void (*map_key) (struct sprinter *, const char *);

    void (*frame) (struct sprinter *);
};

/* Create a new structure printer that emits JSON */
struct sprinter *
sprinter_json_new(const void *ctx, FILE *stream);

int
main(int argc, char **argv)
{
    struct sprinter *test = sprinter_json_new (NULL, stdout);
    test->begin_list (test);
    test->string (test, "test");
    test->integer (test, 42);
    test->boolean (test, true);
    test->null (test);
    test->frame (test);
    test->begin_map (test);
    test->map_key (test, "Hello");
    test->string (test, "world\n");
    test->end (test);
    test->frame (test);
    test->end (test);
    talloc_free (test);
}

/*
 * Every below here is private implementation.
 */

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

struct json_state
{
    struct json_state *parent;
    /* True if nothing has been printed in this aggregate yet.
     * Suppresses the comma before a value. */
    bool 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)
	    fputs (", ", 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)
{
    const static 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, bool 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_frame(struct sprinter *sp)
{
    struct sprinter_json *spj = (struct sprinter_json*)sp;
    fputc ('\n', spj->stream);
}

struct sprinter *
sprinter_json_new(const void *ctx, FILE *stream)
{
    const static 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,
	    .frame = json_frame,
	}
    };
    struct sprinter_json *res;

    res = talloc (ctx, struct sprinter_json);
    if (!res)
	return NULL;

    *res = template;
    res->stream = stream;
    return &res->vtable;
}

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

end of thread, other threads:[~2012-07-13  0:10 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-06-30  2:59 Proposed structure formatter API Austin Clements
2012-07-12  7:43 ` Structured Formatters for JSON Output craven
2012-07-12  7:43   ` [PATCH v4 1/3] Add support for structured output formatters craven
2012-07-12  9:14     ` Mark Walters
2012-07-12  9:21     ` Mark Walters
2012-07-12 11:22       ` craven
2012-07-12 11:25         ` Mark Walters
2012-07-12 12:05           ` Tomi Ollila
2012-07-12 10:05     ` Tomi Ollila
2012-07-12 20:29     ` Austin Clements
2012-07-12 22:08     ` Austin Clements
2012-07-12  7:43   ` [PATCH v4 2/3] Add structured output formatter for JSON craven
2012-07-12 10:10     ` Tomi Ollila
2012-07-12 20:34       ` Austin Clements
2012-07-12 22:07     ` Austin Clements
2012-07-12  7:43   ` [PATCH v4 3/3] Use the structured format printer for JSON in notmuch search craven
2012-07-13  0:02     ` Austin Clements
2012-07-13  0:10       ` Austin Clements

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

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 read-only IMAP folder(s) and NNTP newsgroup(s).