From 2da21ce300f2efe996d01d2a1278322fe08799b0 Mon Sep 17 00:00:00 2001 From: Peter Feigl Date: Thu, 6 Dec 2012 12:44:55 +0100 Subject: [PATCH] [PATCH v4 1/5] Adding an S-expression structured output printer. --- 1b/07f8a000e8df00c744b808a9b0cfe0952877dc | 341 ++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 1b/07f8a000e8df00c744b808a9b0cfe0952877dc diff --git a/1b/07f8a000e8df00c744b808a9b0cfe0952877dc b/1b/07f8a000e8df00c744b808a9b0cfe0952877dc new file mode 100644 index 000000000..f71e461d4 --- /dev/null +++ b/1b/07f8a000e8df00c744b808a9b0cfe0952877dc @@ -0,0 +1,341 @@ +Return-Path: +X-Original-To: notmuch@notmuchmail.org +Delivered-To: notmuch@notmuchmail.org +Received: from localhost (localhost [127.0.0.1]) + by olra.theworths.org (Postfix) with ESMTP id 8F1BA431FBC + for ; Thu, 6 Dec 2012 03:45:19 -0800 (PST) +X-Virus-Scanned: Debian amavisd-new at olra.theworths.org +X-Amavis-Alert: BAD HEADER SECTION, Duplicate header field: "References" +X-Spam-Flag: NO +X-Spam-Score: 0.001 +X-Spam-Level: +X-Spam-Status: No, score=0.001 tagged_above=-999 required=5 + tests=[FREEMAIL_FROM=0.001] autolearn=disabled +Received: from olra.theworths.org ([127.0.0.1]) + by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id mWooN+fDq2Yn for ; + Thu, 6 Dec 2012 03:45:17 -0800 (PST) +Received: from mail.nexoid.at (www.nexoid.at [178.79.130.240]) + (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) + (No client certificate requested) + by olra.theworths.org (Postfix) with ESMTPS id 363F6431FAE + for ; Thu, 6 Dec 2012 03:45:17 -0800 (PST) +Received: by mail.nexoid.at (Postfix, from userid 1000) + id 42DBD11C10B; Thu, 6 Dec 2012 12:45:14 +0100 (CET) +From: Peter Feigl +To: notmuch@notmuchmail.org +Subject: [PATCH v4 1/5] Adding an S-expression structured output printer. +Date: Thu, 6 Dec 2012 12:44:55 +0100 +Message-Id: + <1409adf6cbd310d7313bdc3a69e7e78c25859d81.1354794428.git.craven@gmx.net> +X-Mailer: git-send-email 1.8.0 +In-Reply-To: +References: +In-Reply-To: +References: +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +X-BeenThere: notmuch@notmuchmail.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: "Use and development of the notmuch mail system." + +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +X-List-Received-Date: Thu, 06 Dec 2012 11:45:19 -0000 + +This commit adds a structured output printer for Lisp +S-Expressions. Later commits will use this printer in notmuch search, +show and reply. + +The structure is the same as json, but: +- arrays are written as lists: ("foo" "bar" "baaz" 1 2 3) +- maps are written as p-lists: (:key "value" :other-key "other-value") +- true is written as t +- false is written as nil +- null is written as nil +--- + Makefile.local | 1 + + sprinter-sexp.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + sprinter.h | 4 + + 3 files changed, 243 insertions(+) + create mode 100644 sprinter-sexp.c + +diff --git a/Makefile.local b/Makefile.local +index 2b91946..0db1713 100644 +--- a/Makefile.local ++++ b/Makefile.local +@@ -270,6 +270,7 @@ notmuch_client_srcs = \ + notmuch-tag.c \ + notmuch-time.c \ + sprinter-json.c \ ++ sprinter-sexp.c \ + sprinter-text.c \ + query-string.c \ + mime-node.c \ +diff --git a/sprinter-sexp.c b/sprinter-sexp.c +new file mode 100644 +index 0000000..8f84eed +--- /dev/null ++++ b/sprinter-sexp.c +@@ -0,0 +1,238 @@ ++/* notmuch - Not much of an email program, (just index and search) ++ * ++ * Copyright © 2012 Peter Feigl ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see http://www.gnu.org/licenses/ . ++ * ++ * Author: Peter Feigl ++ */ ++ ++#include ++#include ++#include ++#include "sprinter.h" ++ ++struct sprinter_sexp { ++ struct sprinter vtable; ++ FILE *stream; ++ /* Top of the state stack, or NULL if the printer is not currently ++ * inside any aggregate types. */ ++ struct sexp_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 sexp_state { ++ struct sexp_state *parent; ++ ++ /* True if nothing has been printed in this aggregate yet. ++ * Suppresses the space before a value. */ ++ notmuch_bool_t first; ++}; ++ ++/* Helper function to set up the stream to print a value. If this ++ * value follows another value, prints a space. */ ++static struct sprinter_sexp * ++sexp_begin_value (struct sprinter *sp) ++{ ++ struct sprinter_sexp *sps = (struct sprinter_sexp *) sp; ++ ++ if (sps->state) { ++ if (! sps->state->first) { ++ if (sps->insert_separator) { ++ fputc ('\n', sps->stream); ++ sps->insert_separator = FALSE; ++ } else { ++ fputc (' ', sps->stream); ++ } ++ } else { ++ sps->state->first = FALSE; ++ } ++ } ++ return sps; ++} ++ ++/* Helper function to begin an aggregate type. Prints the open ++ * character and pushes a new state frame. */ ++static void ++sexp_begin_aggregate (struct sprinter *sp) ++{ ++ struct sprinter_sexp *sps = sexp_begin_value (sp); ++ struct sexp_state *state = talloc (sps, struct sexp_state); ++ fputc ('(', sps->stream); ++ state->parent = sps->state; ++ state->first = TRUE; ++ sps->state = state; ++} ++ ++static void ++sexp_begin_map (struct sprinter *sp) ++{ ++ sexp_begin_aggregate (sp); ++} ++ ++static void ++sexp_begin_list (struct sprinter *sp) ++{ ++ sexp_begin_aggregate (sp); ++} ++ ++static void ++sexp_end (struct sprinter *sp) ++{ ++ struct sprinter_sexp *sps = (struct sprinter_sexp *) sp; ++ struct sexp_state *state = sps->state; ++ ++ fputc (')', sps->stream); ++ sps->state = state->parent; ++ talloc_free (state); ++ if (sps->state == NULL) ++ fputc ('\n', sps->stream); ++} ++ ++static void ++sexp_string_len (struct sprinter *sp, const char *val, size_t len) ++{ ++ /* Some characters need escaping. " and \ work fine in all Lisps, ++ * \n is not supported in CL, but all others work fine. ++ * Characters below 32 are printed as \123o (three-digit ++ * octals), which work fine in most Schemes and Emacs. */ ++ static const char *const escapes[] = { ++ ['\"'] = "\\\"", ['\\'] = "\\\\", ['\n'] = "\\n" ++ }; ++ struct sprinter_sexp *sps = sexp_begin_value (sp); ++ ++ fputc ('"', sps->stream); ++ for (; len; ++val, --len) { ++ unsigned char ch = *val; ++ if (ch < ARRAY_SIZE (escapes) && escapes[ch]) ++ fputs (escapes[ch], sps->stream); ++ else if (ch >= 32) ++ fputc (ch, sps->stream); ++ else ++ fprintf (sps->stream, "\\%03oo", ch); ++ } ++ fputc ('"', sps->stream); ++} ++ ++static void ++sexp_string (struct sprinter *sp, const char *val) ++{ ++ if (val == NULL) ++ val = ""; ++ sexp_string_len (sp, val, strlen (val)); ++} ++ ++/* Prints a symbol, i.e. the name preceded by a colon. This should work ++ * in all Lisps, at least as a symbol, if not as a proper keyword */ ++static void ++sexp_symbol (struct sprinter *sp, const char *val) ++{ ++ static const char illegal_characters[] = { ++ ' ', '\t', '\n' ++ }; ++ unsigned int i = 0; ++ struct sprinter_sexp *sps = (struct sprinter_sexp *) sp; ++ ++ if (val == NULL) ++ INTERNAL_ERROR ("illegal symbol NULL"); ++ ++ for(i = 0; i < ARRAY_SIZE (illegal_characters); i++) { ++ if(strchr(val, illegal_characters[i]) != NULL) { ++ INTERNAL_ERROR ("illegal character in symbol %s", val); ++ } ++ } ++ fputc (':', sps->stream); ++ fputs (val, sps->stream); ++} ++ ++static void ++sexp_integer (struct sprinter *sp, int val) ++{ ++ struct sprinter_sexp *sps = sexp_begin_value (sp); ++ ++ fprintf (sps->stream, "%d", val); ++} ++ ++static void ++sexp_boolean (struct sprinter *sp, notmuch_bool_t val) ++{ ++ struct sprinter_sexp *sps = sexp_begin_value (sp); ++ ++ fputs (val ? "t" : "nil", sps->stream); ++} ++ ++static void ++sexp_null (struct sprinter *sp) ++{ ++ struct sprinter_sexp *sps = sexp_begin_value (sp); ++ ++ fputs ("nil", sps->stream); ++} ++ ++static void ++sexp_map_key (struct sprinter *sp, const char *key) ++{ ++ struct sprinter_sexp *sps = (struct sprinter_sexp *) sp; ++ if (sps->state && ! sps->state->first) ++ fputc (' ', sps->stream); ++ ++ sps->state->first = FALSE; ++ sexp_symbol (sp, key); ++} ++ ++static void ++sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name)) ++{ ++} ++ ++static void ++sexp_separator (struct sprinter *sp) ++{ ++ struct sprinter_sexp *sps = (struct sprinter_sexp *) sp; ++ ++ sps->insert_separator = TRUE; ++} ++ ++struct sprinter * ++sprinter_sexp_create (const void *ctx, FILE *stream) ++{ ++ static const struct sprinter_sexp template = { ++ .vtable = { ++ .begin_map = sexp_begin_map, ++ .begin_list = sexp_begin_list, ++ .end = sexp_end, ++ .string = sexp_string, ++ .string_len = sexp_string_len, ++ .integer = sexp_integer, ++ .boolean = sexp_boolean, ++ .null = sexp_null, ++ .map_key = sexp_map_key, ++ .separator = sexp_separator, ++ .set_prefix = sexp_set_prefix, ++ .is_text_printer = FALSE, ++ } ++ }; ++ struct sprinter_sexp *res; ++ ++ res = talloc (ctx, struct sprinter_sexp); ++ if (! res) ++ return NULL; ++ ++ *res = template; ++ res->stream = stream; ++ return &res->vtable; ++} +diff --git a/sprinter.h b/sprinter.h +index 912a526..59776a9 100644 +--- a/sprinter.h ++++ b/sprinter.h +@@ -70,4 +70,8 @@ sprinter_text_create (const void *ctx, FILE *stream); + struct sprinter * + sprinter_json_create (const void *ctx, FILE *stream); + ++/* Create a new structure printer that emits S-Expressions. */ ++struct sprinter * ++sprinter_sexp_create (const void *ctx, FILE *stream); ++ + #endif // NOTMUCH_SPRINTER_H +-- +1.8.0 + -- 2.26.2