]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch-show.c
show: Convert do_show to use sprinter
[notmuch] / notmuch-show.c
index 9852119f77338f3a8a2190b1ed838449f25d3f28..89bf2e7681fd6f66a6c05cc03e1746e3d29f831f 100644 (file)
@@ -109,35 +109,45 @@ _get_one_line_summary (const void *ctx, notmuch_message_t *message)
                            from, relative_date, tags);
 }
 
+/* Emit a sequence of key/value pairs for the metadata of message.
+ * The caller should begin a map before calling this. */
 static void
-format_message_json (const void *ctx, notmuch_message_t *message)
+format_message_json (sprinter_t *sp, notmuch_message_t *message)
 {
+    void *local = talloc_new (NULL);
     notmuch_tags_t *tags;
-    int first = 1;
-    void *ctx_quote = talloc_new (ctx);
     time_t date;
     const char *relative_date;
 
+    sp->map_key (sp, "id");
+    sp->string (sp, notmuch_message_get_message_id (message));
+
+    sp->map_key (sp, "match");
+    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH));
+
+    sp->map_key (sp, "excluded");
+    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED));
+
+    sp->map_key (sp, "filename");
+    sp->string (sp, notmuch_message_get_filename (message));
+
+    sp->map_key (sp, "timestamp");
     date = notmuch_message_get_date (message);
-    relative_date = notmuch_time_relative_date (ctx, date);
+    sp->integer (sp, date);
 
-    printf ("\"id\": %s, \"match\": %s, \"excluded\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [",
-           json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
-           notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
-           notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? "true" : "false",
-           json_quote_str (ctx_quote, notmuch_message_get_filename (message)),
-           date, relative_date);
+    sp->map_key (sp, "date_relative");
+    relative_date = notmuch_time_relative_date (local, date);
+    sp->string (sp, relative_date);
 
+    sp->map_key (sp, "tags");
+    sp->begin_list (sp);
     for (tags = notmuch_message_get_tags (message);
         notmuch_tags_valid (tags);
         notmuch_tags_move_to_next (tags))
-    {
-         printf("%s%s", first ? "" : ",",
-               json_quote_str (ctx_quote, notmuch_tags_get (tags)));
-         first = 0;
-    }
-    printf("], ");
-    talloc_free (ctx_quote);
+       sp->string (sp, notmuch_tags_get (tags));
+    sp->end (sp);
+
+    talloc_free (local);
 }
 
 /* Extract just the email address from the contents of a From:
@@ -337,134 +347,138 @@ signer_status_to_string (GMimeSignerStatus x)
 
 #ifdef GMIME_ATLEAST_26
 static void
-format_part_sigstatus_json (mime_node_t *node)
+format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
 {
     GMimeSignatureList *siglist = node->sig_list;
 
-    printf ("[");
+    sp->begin_list (sp);
 
     if (!siglist) {
-       printf ("]");
+       sp->end (sp);
        return;
     }
 
-    void *ctx_quote = talloc_new (NULL);
     int i;
     for (i = 0; i < g_mime_signature_list_length (siglist); i++) {
        GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i);
 
-       if (i > 0)
-           printf (", ");
-
-       printf ("{");
+       sp->begin_map (sp);
 
        /* status */
        GMimeSignatureStatus status = g_mime_signature_get_status (signature);
-       printf ("\"status\": %s",
-               json_quote_str (ctx_quote,
-                               signature_status_to_string (status)));
+       sp->map_key (sp, "status");
+       sp->string (sp, signature_status_to_string (status));
 
        GMimeCertificate *certificate = g_mime_signature_get_certificate (signature);
        if (status == GMIME_SIGNATURE_STATUS_GOOD) {
-           if (certificate)
-               printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, g_mime_certificate_get_fingerprint (certificate)));
+           if (certificate) {
+               sp->map_key (sp, "fingerprint");
+               sp->string (sp, g_mime_certificate_get_fingerprint (certificate));
+           }
            /* these dates are seconds since the epoch; should we
             * provide a more human-readable format string? */
            time_t created = g_mime_signature_get_created (signature);
-           if (created != -1)
-               printf (", \"created\": %d", (int) created);
+           if (created != -1) {
+               sp->map_key (sp, "created");
+               sp->integer (sp, created);
+           }
            time_t expires = g_mime_signature_get_expires (signature);
-           if (expires > 0)
-               printf (", \"expires\": %d", (int) expires);
+           if (expires > 0) {
+               sp->map_key (sp, "expires");
+               sp->integer (sp, expires);
+           }
            /* output user id only if validity is FULL or ULTIMATE. */
            /* note that gmime is using the term "trust" here, which
             * is WRONG.  It's actually user id "validity". */
            if (certificate) {
                const char *name = g_mime_certificate_get_name (certificate);
                GMimeCertificateTrust trust = g_mime_certificate_get_trust (certificate);
-               if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE))
-                   printf (", \"userid\": %s", json_quote_str (ctx_quote, name));
+               if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)) {
+                   sp->map_key (sp, "userid");
+                   sp->string (sp, name);
+               }
            }
        } else if (certificate) {
            const char *key_id = g_mime_certificate_get_key_id (certificate);
-           if (key_id)
-               printf (", \"keyid\": %s", json_quote_str (ctx_quote, key_id));
+           if (key_id) {
+               sp->map_key (sp, "keyid");
+               sp->string (sp, key_id);
+           }
        }
 
        GMimeSignatureError errors = g_mime_signature_get_errors (signature);
        if (errors != GMIME_SIGNATURE_ERROR_NONE) {
-           printf (", \"errors\": %d", errors);
+           sp->map_key (sp, "errors");
+           sp->integer (sp, errors);
        }
 
-       printf ("}");
+       sp->end (sp);
      }
 
-    printf ("]");
-
-    talloc_free (ctx_quote);
+    sp->end (sp);
 }
 #else
 static void
-format_part_sigstatus_json (mime_node_t *node)
+format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
 {
     const GMimeSignatureValidity* validity = node->sig_validity;
 
-    printf ("[");
+    sp->begin_list (sp);
 
     if (!validity) {
-       printf ("]");
+       sp->end (sp);
        return;
     }
 
     const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);
-    int first = 1;
-    void *ctx_quote = talloc_new (NULL);
-
     while (signer) {
-       if (first)
-           first = 0;
-       else
-           printf (", ");
-
-       printf ("{");
+       sp->begin_map (sp);
 
        /* status */
-       printf ("\"status\": %s",
-               json_quote_str (ctx_quote,
-                               signer_status_to_string (signer->status)));
+       sp->map_key (sp, "status");
+       sp->string (sp, signer_status_to_string (signer->status));
 
        if (signer->status == GMIME_SIGNER_STATUS_GOOD)
        {
-           if (signer->fingerprint)
-               printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, signer->fingerprint));
+           if (signer->fingerprint) {
+               sp->map_key (sp, "fingerprint");
+               sp->string (sp, signer->fingerprint);
+           }
            /* these dates are seconds since the epoch; should we
             * provide a more human-readable format string? */
-           if (signer->created)
-               printf (", \"created\": %d", (int) signer->created);
-           if (signer->expires)
-               printf (", \"expires\": %d", (int) signer->expires);
+           if (signer->created) {
+               sp->map_key (sp, "created");
+               sp->integer (sp, signer->created);
+           }
+           if (signer->expires) {
+               sp->map_key (sp, "expires");
+               sp->integer (sp, signer->expires);
+           }
            /* output user id only if validity is FULL or ULTIMATE. */
            /* note that gmime is using the term "trust" here, which
             * is WRONG.  It's actually user id "validity". */
            if ((signer->name) && (signer->trust)) {
-               if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE))
-                   printf (", \"userid\": %s", json_quote_str (ctx_quote, signer->name));
+               if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE)) {
+                   sp->map_key (sp, "userid");
+                   sp->string (sp, signer->name);
+               }
            }
        } else {
-           if (signer->keyid)
-               printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid));
+           if (signer->keyid) {
+              sp->map_key (sp, "keyid");
+              sp->string (sp, signer->keyid);
+          }
        }
        if (signer->errors != GMIME_SIGNER_ERROR_NONE) {
-           printf (", \"errors\": %d", signer->errors);
+          sp->map_key (sp, "errors");
+          sp->integer (sp, signer->errors);
        }
 
-       printf ("}");
+       sp->end (sp);
        signer = signer->next;
     }
 
-    printf ("]");
-
-    talloc_free (ctx_quote);
+    sp->end (sp);
 }
 #endif
 
@@ -569,22 +583,22 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
      * devel/schemata. */
 
     if (node->envelope_file) {
-       printf ("{");
-       format_message_json (ctx, node->envelope_file);
+       sp->begin_map (sp);
+       format_message_json (sp, node->envelope_file);
 
-       printf ("\"headers\": ");
+       sp->map_key (sp, "headers");
        format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
 
        if (output_body) {
-           printf (", \"body\": [");
+           sp->map_key (sp, "body");
+           sp->begin_list (sp);
            format_part_json (ctx, sp, mime_node_child (node, 0), first, TRUE);
-           printf ("]");
+           sp->end (sp);
        }
-       printf ("}");
+       sp->end (sp);
        return;
     }
 
-    void *local = talloc_new (ctx);
     /* The disposition and content-type metadata are associated with
      * the envelope for message parts */
     GMimeObject *meta = node->envelope_part ?
@@ -593,31 +607,41 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
     const char *cid = g_mime_object_get_content_id (meta);
     const char *filename = GMIME_IS_PART (node->part) ?
        g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
-    const char *terminator = "";
+    int nclose = 0;
     int i;
 
-    if (!first)
-       printf (", ");
-
-    printf ("{\"id\": %d", node->part_num);
+    sp->begin_map (sp);
 
-    if (node->decrypt_attempted)
-       printf (", \"encstatus\": [{\"status\": \"%s\"}]",
-               node->decrypt_success ? "good" : "bad");
+    sp->map_key (sp, "id");
+    sp->integer (sp, node->part_num);
+
+    if (node->decrypt_attempted) {
+       sp->map_key (sp, "encstatus");
+       sp->begin_list (sp);
+       sp->begin_map (sp);
+       sp->map_key (sp, "status");
+       sp->string (sp, node->decrypt_success ? "good" : "bad");
+       sp->end (sp);
+       sp->end (sp);
+    }
 
     if (node->verify_attempted) {
-       printf (", \"sigstatus\": ");
-       format_part_sigstatus_json (node);
+       sp->map_key (sp, "sigstatus");
+       format_part_sigstatus_json (sp, node);
     }
 
-    printf (", \"content-type\": %s",
-           json_quote_str (local, g_mime_content_type_to_string (content_type)));
+    sp->map_key (sp, "content-type");
+    sp->string (sp, g_mime_content_type_to_string (content_type));
 
-    if (cid)
-       printf (", \"content-id\": %s", json_quote_str (local, cid));
+    if (cid) {
+       sp->map_key (sp, "content-id");
+       sp->string (sp, cid);
+    }
 
-    if (filename)
-       printf (", \"filename\": %s", json_quote_str (local, filename));
+    if (filename) {
+       sp->map_key (sp, "filename");
+       sp->string (sp, filename);
+    }
 
     if (GMIME_IS_PART (node->part)) {
        /* For non-HTML text parts, we include the content in the
@@ -632,35 +656,44 @@ format_part_json (const void *ctx, sprinter_t *sp, mime_node_t *node,
        if (g_mime_content_type_is_type (content_type, "text", "html")) {
            const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset");
 
-           if (content_charset != NULL)
-               printf (", \"content-charset\": %s", json_quote_str (local, content_charset));
+           if (content_charset != NULL) {
+               sp->map_key (sp, "content-charset");
+               sp->string (sp, content_charset);
+           }
        } else if (g_mime_content_type_is_type (content_type, "text", "*")) {
            GMimeStream *stream_memory = g_mime_stream_mem_new ();
            GByteArray *part_content;
            show_text_part_content (node->part, stream_memory, 0);
            part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
-
-           printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len));
+           sp->map_key (sp, "content");
+           sp->string_len (sp, (char *) part_content->data, part_content->len);
            g_object_unref (stream_memory);
        }
     } else if (GMIME_IS_MULTIPART (node->part)) {
-       printf (", \"content\": [");
-       terminator = "]";
+       sp->map_key (sp, "content");
+       sp->begin_list (sp);
+       nclose = 1;
     } else if (GMIME_IS_MESSAGE (node->part)) {
-       printf (", \"content\": [{");
-       printf ("\"headers\": ");
+       sp->map_key (sp, "content");
+       sp->begin_list (sp);
+       sp->begin_map (sp);
+
+       sp->map_key (sp, "headers");
        format_headers_json (sp, GMIME_MESSAGE (node->part), FALSE);
 
-       printf (", \"body\": [");
-       terminator = "]}]";
+       sp->map_key (sp, "body");
+       sp->begin_list (sp);
+       nclose = 3;
     }
 
-    talloc_free (local);
-
     for (i = 0; i < node->nchildren; i++)
        format_part_json (ctx, sp, mime_node_child (node, i), i == 0, TRUE);
 
-    printf ("%s}", terminator);
+    /* Close content structures */
+    for (i = 0; i < nclose; i++)
+       sp->end (sp);
+    /* Close part map */
+    sp->end (sp);
 }
 
 static notmuch_status_t
@@ -808,15 +841,6 @@ format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
     return NOTMUCH_STATUS_SUCCESS;
 }
 
-static notmuch_status_t
-show_null_message (const notmuch_show_format_t *format)
-{
-    /* Output a null message. Currently empty for all formats except Json */
-    if (format->null_message)
-       printf ("%s", format->null_message);
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
 static notmuch_status_t
 show_message (void *ctx,
              const notmuch_show_format_t *format,
@@ -851,23 +875,16 @@ show_messages (void *ctx,
     notmuch_message_t *message;
     notmuch_bool_t match;
     notmuch_bool_t excluded;
-    int first_set = 1;
     int next_indent;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
 
-    if (format->message_set_start)
-       fputs (format->message_set_start, stdout);
+    sp->begin_list (sp);
 
     for (;
         notmuch_messages_valid (messages);
         notmuch_messages_move_to_next (messages))
     {
-       if (!first_set && format->message_set_sep)
-           fputs (format->message_set_sep, stdout);
-       first_set = 0;
-
-       if (format->message_set_start)
-           fputs (format->message_set_start, stdout);
+       sp->begin_list (sp);
 
        message = notmuch_messages_get (messages);
 
@@ -882,12 +899,9 @@ show_messages (void *ctx,
                res = status;
            next_indent = indent + 1;
        } else {
-           status = show_null_message (format);
+           sp->null (sp);
        }
 
-       if (!status && format->message_set_sep)
-           fputs (format->message_set_sep, stdout);
-
        status = show_messages (ctx,
                                format, sp,
                                notmuch_message_get_replies (message),
@@ -898,12 +912,10 @@ show_messages (void *ctx,
 
        notmuch_message_destroy (message);
 
-       if (format->message_set_end)
-           fputs (format->message_set_end, stdout);
+       sp->end (sp);
     }
 
-    if (format->message_set_end)
-       fputs (format->message_set_end, stdout);
+    sp->end (sp);
 
     return res;
 }
@@ -949,11 +961,9 @@ do_show (void *ctx,
     notmuch_threads_t *threads;
     notmuch_thread_t *thread;
     notmuch_messages_t *messages;
-    int first_toplevel = 1;
     notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
 
-    if (format->message_set_start)
-       fputs (format->message_set_start, stdout);
+    sp->begin_list (sp);
 
     for (threads = notmuch_query_search_threads (query);
         notmuch_threads_valid (threads);
@@ -967,10 +977,6 @@ do_show (void *ctx,
            INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
                            notmuch_thread_get_thread_id (thread));
 
-       if (!first_toplevel && format->message_set_sep)
-           fputs (format->message_set_sep, stdout);
-       first_toplevel = 0;
-
        status = show_messages (ctx, format, sp, messages, 0, params);
        if (status && !res)
            res = status;
@@ -979,8 +985,7 @@ do_show (void *ctx,
 
     }
 
-    if (format->message_set_end)
-       fputs (format->message_set_end, stdout);
+    sp->end (sp);
 
     return res != NOTMUCH_STATUS_SUCCESS;
 }