]> git.notmuchmail.org Git - notmuch/commitdiff
New part output handling as option to notmuch-show.
authorJameson Graef Rollins <jrollins@finestructure.net>
Mon, 23 May 2011 22:31:32 +0000 (15:31 -0700)
committerJameson Graef Rollins <jrollins@finestructure.net>
Mon, 23 May 2011 22:31:32 +0000 (15:31 -0700)
Outputting of single MIME parts is moved to an option of notmuch show,
instead of being handled in it's own sub-command.  The recent rework
of multipart mime allowed for this change but consolidating part
handling into a single recursive function (show_message_part) that
includes formatting.  This allows for far simpler handling single
output of a single part, including formatting.

notmuch-client.h
notmuch-reply.c
notmuch-show.c
notmuch.1
notmuch.c
show-message.c

index 47a4e570abb312e164ada57499e9ccd2ae5a30dc..7221c68108a59174b83835f61943df91928b7a50 100644 (file)
@@ -78,6 +78,7 @@ typedef struct notmuch_show_format {
 typedef struct notmuch_show_params {
     int entire_thread;
     int raw;
 typedef struct notmuch_show_params {
     int entire_thread;
     int raw;
+    int part;
 } notmuch_show_params_t;
 
 /* There's no point in continuing when we've detected that we've done
 } notmuch_show_params_t;
 
 /* There's no point in continuing when we've detected that we've done
@@ -139,9 +140,6 @@ notmuch_search_tags_command (void *ctx, int argc, char *argv[]);
 int
 notmuch_cat_command (void *ctx, int argc, char *argv[]);
 
 int
 notmuch_cat_command (void *ctx, int argc, char *argv[]);
 
-int
-notmuch_part_command (void *ctx, int argc, char *argv[]);
-
 int
 notmuch_config_command (void *ctx, int argc, char *argv[]);
 
 int
 notmuch_config_command (void *ctx, int argc, char *argv[]);
 
@@ -159,7 +157,8 @@ query_string_from_args (void *ctx, int argc, char *argv[]);
 
 notmuch_status_t
 show_message_body (const char *filename,
 
 notmuch_status_t
 show_message_body (const char *filename,
-                  const notmuch_show_format_t *format);
+                  const notmuch_show_format_t *format,
+                  notmuch_show_params_t *params);
 
 notmuch_status_t
 show_one_part (const char *filename, int part);
 
 notmuch_status_t
 show_one_part (const char *filename, int part);
index b5ca19c46b6500eb6681acabff51a7d6c9d93aba..ab15650827f0a30c0c9aaf89b0734f398e8b5b21 100644 (file)
@@ -461,6 +461,8 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_
     const char *subject, *from_addr = NULL;
     const char *in_reply_to, *orig_references, *references;
     const notmuch_show_format_t *format = &format_reply;
     const char *subject, *from_addr = NULL;
     const char *in_reply_to, *orig_references, *references;
     const notmuch_show_format_t *format = &format_reply;
+    notmuch_show_params_t params;
+    params.part = -1;
 
     for (messages = notmuch_query_search_messages (query);
         notmuch_messages_valid (messages);
 
     for (messages = notmuch_query_search_messages (query);
         notmuch_messages_valid (messages);
@@ -520,7 +522,7 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_
                notmuch_message_get_header (message, "from"));
 
        show_message_body (notmuch_message_get_filename (message),
                notmuch_message_get_header (message, "from"));
 
        show_message_body (notmuch_message_get_filename (message),
-                          format);
+                          format, &params);
 
        notmuch_message_destroy (message);
     }
 
        notmuch_message_destroy (message);
     }
index 27587f8799291d79e5a47342beb7bea3226202fc..e128be541b9f36b1144192e1f094f8db3d85b50b 100644 (file)
@@ -82,6 +82,19 @@ static const notmuch_show_format_t format_mbox = {
     ""
 };
 
     ""
 };
 
+static void
+format_part_raw (GMimeObject *part,
+                unused (int *part_count));
+
+static const notmuch_show_format_t format_raw = {
+    "",
+       "", NULL,
+           "", NULL, "",
+            "", format_part_raw, NULL, "", "",
+       "", "",
+    ""
+};
+
 static const char *
 _get_tags_as_string (const void *ctx, notmuch_message_t *message)
 {
 static const char *
 _get_tags_as_string (const void *ctx, notmuch_message_t *message)
 {
@@ -329,6 +342,10 @@ show_part_content (GMimeObject *part, GMimeStream *stream_out)
     GMimeDataWrapper *wrapper;
     const char *charset;
 
     GMimeDataWrapper *wrapper;
     const char *charset;
 
+    /* do nothing if this is a multipart */
+    if (GMIME_IS_MULTIPART (part) || GMIME_IS_MESSAGE_PART (part))
+       return;
+
     charset = g_mime_object_get_content_type_parameter (part, "charset");
 
     if (stream_out) {
     charset = g_mime_object_get_content_type_parameter (part, "charset");
 
     if (stream_out) {
@@ -489,30 +506,45 @@ format_part_end_json (GMimeObject *part)
     printf ("}");
 }
 
     printf ("}");
 }
 
+static void
+format_part_raw (GMimeObject *part, unused (int *part_count))
+{
+    GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
+    show_part_content (part, stream_stdout);
+    g_object_unref(stream_stdout);
+}
+
 static void
 show_message (void *ctx,
              const notmuch_show_format_t *format,
              notmuch_message_t *message,
 static void
 show_message (void *ctx,
              const notmuch_show_format_t *format,
              notmuch_message_t *message,
-             int indent)
+             int indent,
+             notmuch_show_params_t *params)
 {
 {
-    fputs (format->message_start, stdout);
-    if (format->message)
-       format->message(ctx, message, indent);
+    if (params->part <= 0) {
+       fputs (format->message_start, stdout);
+       if (format->message)
+           format->message(ctx, message, indent);
+
+       fputs (format->header_start, stdout);
+       if (format->header)
+           format->header(ctx, message);
+       fputs (format->header_end, stdout);
 
 
-    fputs (format->header_start, stdout);
-    if (format->header)
-       format->header(ctx, message);
-    fputs (format->header_end, stdout);
+       fputs (format->body_start, stdout);
+    }
 
 
-    fputs (format->body_start, stdout);
     if (format->part)
        show_message_body (notmuch_message_get_filename (message),
     if (format->part)
        show_message_body (notmuch_message_get_filename (message),
-                          format);
-    fputs (format->body_end, stdout);
+                          format, params);
 
 
-    fputs (format->message_end, stdout);
-}
+    if (params->part <= 0) {
+       fputs (format->body_end, stdout);
 
 
+       fputs (format->message_end, stdout);
+    }
+}
 
 static void
 show_messages (void *ctx,
 
 static void
 show_messages (void *ctx,
@@ -545,7 +577,7 @@ show_messages (void *ctx,
        next_indent = indent;
 
        if (match || params->entire_thread) {
        next_indent = indent;
 
        if (match || params->entire_thread) {
-           show_message (ctx, format, message, indent);
+           show_message (ctx, format, message, indent, params);
            next_indent = indent + 1;
 
            fputs (format->message_set_sep, stdout);
            next_indent = indent + 1;
 
            fputs (format->message_set_sep, stdout);
@@ -588,8 +620,10 @@ do_show_single (void *ctx,
        return 1;
     }
 
        return 1;
     }
 
+    notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1);
+
     /* Special case for --format=raw of full single message, just cat out file */
     /* Special case for --format=raw of full single message, just cat out file */
-    if (params->raw) {
+    if (params->raw && 0 == params->part) {
 
        const char *filename;
        FILE *file;
 
        const char *filename;
        FILE *file;
@@ -615,6 +649,10 @@ do_show_single (void *ctx,
 
        fclose (file);
 
 
        fclose (file);
 
+    } else {
+
+       show_message (ctx, format, message, 0, params);
+
     }
 
     return 0;
     }
 
     return 0;
@@ -675,6 +713,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 
     params.entire_thread = 0;
     params.raw = 0;
 
     params.entire_thread = 0;
     params.raw = 0;
+    params.part = -1;
 
     for (i = 0; i < argc && argv[i][0] == '-'; i++) {
        if (strcmp (argv[i], "--") == 0) {
 
     for (i = 0; i < argc && argv[i][0] == '-'; i++) {
        if (strcmp (argv[i], "--") == 0) {
@@ -691,11 +730,14 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
            } else if (strcmp (opt, "mbox") == 0) {
                format = &format_mbox;
            } else if (strcmp (opt, "raw") == 0) {
            } else if (strcmp (opt, "mbox") == 0) {
                format = &format_mbox;
            } else if (strcmp (opt, "raw") == 0) {
+               format = &format_raw;
                params.raw = 1;
            } else {
                fprintf (stderr, "Invalid value for --format: %s\n", opt);
                return 1;
            }
                params.raw = 1;
            } else {
                fprintf (stderr, "Invalid value for --format: %s\n", opt);
                return 1;
            }
+       } else if (STRNCMP_LITERAL (argv[i], "--part=") == 0) {
+           params.part = atoi(argv[i] + sizeof ("--part=") - 1);
        } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
            params.entire_thread = 1;
        } else {
        } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
            params.entire_thread = 1;
        } else {
@@ -735,7 +777,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 
     /* If --format=raw specified without specifying part, we can only
      * output single message, so set part=0 */
 
     /* If --format=raw specified without specifying part, we can only
      * output single message, so set part=0 */
-    if (params.raw)
+    if (params.raw && params.part < 0)
+       params.part = 0;
+
+    if (params.part >= 0)
        return do_show_single (ctx, query, format, &params);
     else
        return do_show (ctx, query, format, &params);
        return do_show_single (ctx, query, format, &params);
     else
        return do_show (ctx, query, format, &params);
@@ -745,78 +790,3 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 
     return 0;
 }
 
     return 0;
 }
-
-int
-notmuch_part_command (void *ctx, unused (int argc), unused (char *argv[]))
-{
-       notmuch_config_t *config;
-       notmuch_database_t *notmuch;
-       notmuch_query_t *query;
-       notmuch_messages_t *messages;
-       notmuch_message_t *message;
-       char *query_string;
-       int i;
-       int part = 0;
-
-       for (i = 0; i < argc && argv[i][0] == '-'; i++) {
-               if (strcmp (argv[i], "--") == 0) {
-                       i++;
-                       break;
-               }
-               if (STRNCMP_LITERAL (argv[i], "--part=") == 0) {
-                       part = atoi(argv[i] + sizeof ("--part=") - 1);
-               } else {
-                       fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
-                       return 1;
-               }
-       }
-
-       argc -= i;
-       argv += i;
-
-       config = notmuch_config_open (ctx, NULL, NULL);
-       if (config == NULL)
-               return 1;
-
-       query_string = query_string_from_args (ctx, argc, argv);
-       if (query_string == NULL) {
-               fprintf (stderr, "Out of memory\n");
-               return 1;
-       }
-
-       if (*query_string == '\0') {
-               fprintf (stderr, "Error: notmuch part requires at least one search term.\n");
-               return 1;
-       }
-
-       notmuch = notmuch_database_open (notmuch_config_get_database_path (config),
-                                        NOTMUCH_DATABASE_MODE_READ_ONLY);
-       if (notmuch == NULL)
-               return 1;
-
-       query = notmuch_query_create (notmuch, query_string);
-       if (query == NULL) {
-               fprintf (stderr, "Out of memory\n");
-               return 1;
-       }
-
-       if (notmuch_query_count_messages (query) != 1) {
-               fprintf (stderr, "Error: search term did not match precisely one message.\n");
-               return 1;
-       }
-
-       messages = notmuch_query_search_messages (query);
-       message = notmuch_messages_get (messages);
-
-       if (message == NULL) {
-               fprintf (stderr, "Error: cannot find matching message.\n");
-               return 1;
-       }
-
-       show_one_part (notmuch_message_get_filename (message), part);
-
-       notmuch_query_destroy (query);
-       notmuch_database_close (notmuch);
-
-       return 0;
-}
index 2912fcfda67d456ba5002d9cbfdf1ccf8150b2e6..76222981789d60ff001aa5d1317f2e8d347b6854 100644 (file)
--- a/notmuch.1
+++ b/notmuch.1
@@ -297,8 +297,20 @@ The original, raw content of the email message is displayed.
 Consumers of this format should expect to implement MIME decoding and
 similar functions. This format must only be used with search terms
 matching a single message.
 Consumers of this format should expect to implement MIME decoding and
 similar functions. This format must only be used with search terms
 matching a single message.
+.RE
+.RE
+
+.RS 4
+.TP 4
+.B \-\-part=N
 
 
+Output the single decoded MIME part N of a single message.  The search
+terms must match only a single message.  Message parts are numbered in
+a depth-first walk of the message MIME structure, and are identified
+in the 'json' or
+'text' output formats.
 .RE
 .RE
+
 A common use of
 .B notmuch show
 is to display a single thread of email messages. For this, use a
 A common use of
 .B notmuch show
 is to display a single thread of email messages. For this, use a
@@ -311,7 +323,8 @@ See the
 .B "SEARCH SYNTAX"
 section below for details of the supported syntax for <search-terms>.
 .RE
 .B "SEARCH SYNTAX"
 section below for details of the supported syntax for <search-terms>.
 .RE
-.TP
+.RS 4
+.TP 4
 .BR count " <search-term>..."
 
 Count messages matching the search terms.
 .BR count " <search-term>..."
 
 Count messages matching the search terms.
index 098f73357e4ec43ae922fed50d0663deb1e65c1b..2e98f25d1e2aa9fea9e870099dcf2d96939e6dad 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -269,6 +269,15 @@ command_t commands[] = {
       "\t\tdecoding and similar functions. This format must only\n"
       "\t\tbe used with search terms matching a single message.\n"
       "\n"
       "\t\tdecoding and similar functions. This format must only\n"
       "\t\tbe used with search terms matching a single message.\n"
       "\n"
+      "\t--part=N\n"
+      "\n"
+      "\t\tOutput the single decoded MIME part N of a single message.\n"
+      "\t\tThe search terms must match only a single message.\n"
+      "\t\tMessage parts are numbered in a depth-first walk of the\n"
+      "\t\tmessage MIME structure, and are identified in the 'json' or\n"
+      "\t\t'text' output formats.\n"
+      "\n"
+      "\n"
       "\tA common use of \"notmuch show\" is to display a single\n"
       "\tthread of email messages. For this, use a search term of\n"
       "\t\"thread:<thread-id>\" as can be seen in the first column\n"
       "\tA common use of \"notmuch show\" is to display a single\n"
       "\tthread of email messages. For this, use a search term of\n"
       "\t\"thread:<thread-id>\" as can be seen in the first column\n"
@@ -358,17 +367,6 @@ command_t commands[] = {
       "\tcontain tags only from messages that match the search-term(s).\n"
       "\n"
       "\tIn both cases the list will be alphabetically sorted." },
       "\tcontain tags only from messages that match the search-term(s).\n"
       "\n"
       "\tIn both cases the list will be alphabetically sorted." },
-    { "part", notmuch_part_command,
-      "--part=<num> <search-terms>",
-      "Output a single MIME part of a message.",
-      "\tA single decoded MIME part, with no encoding or framing,\n"
-      "\tis output to stdout. The search terms must match only a single\n"
-      "\tmessage, otherwise this command will fail.\n"
-      "\n"
-      "\tThe part number should match the part \"id\" field output\n"
-      "\tby the \"--format=json\" option of \"notmuch show\". If the\n"
-      "\tmessage specified by the search terms does not include a\n"
-      "\tpart with the specified \"id\" there will be no output." },
     { "config", notmuch_config_command,
       "[get|set] <section>.<item> [value ...]",
       "Get or set settings in the notmuch configuration file.",
     { "config", notmuch_config_command,
       "[get|set] <section>.<item> [value ...]",
       "Get or set settings in the notmuch configuration file.",
index 4ccd449018850c417c5a324b3952e1767389aa4e..32bb860f63dd8c86d54389d3d4333fde46956386 100644 (file)
 
 #include "notmuch-client.h"
 
 
 #include "notmuch-client.h"
 
+typedef struct show_message_state {
+    int part_count;
+    int in_zone;
+} show_message_state_t;
+
 static void
 show_message_part (GMimeObject *part,
 static void
 show_message_part (GMimeObject *part,
-                  int *part_count,
+                  show_message_state_t *state,
                   const notmuch_show_format_t *format,
                   const notmuch_show_format_t *format,
+                  notmuch_show_params_t *params,
                   int first)
 {
                   int first)
 {
-    *part_count += 1;
+    int selected;
+    state->part_count += 1;
 
     if (! (GMIME_IS_PART (part) || GMIME_IS_MULTIPART (part) || GMIME_IS_MESSAGE_PART (part))) {
        fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
 
     if (! (GMIME_IS_PART (part) || GMIME_IS_MULTIPART (part) || GMIME_IS_MESSAGE_PART (part))) {
        fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
@@ -36,43 +43,63 @@ show_message_part (GMimeObject *part,
        return;
     }
 
        return;
     }
 
-    if (!first)
-       fputs (format->part_sep, stdout);
+    selected = (params->part <= 0 || state->part_count == params->part);
+
+    if (selected || state->in_zone) {
+       if (!first && (params->part <= 0 || state->in_zone))
+           fputs (format->part_sep, stdout);
 
 
-    format->part (part, part_count);
+       format->part (part, &(state->part_count));
+    }
 
     if (GMIME_IS_MULTIPART (part)) {
        GMimeMultipart *multipart = GMIME_MULTIPART (part);
        int i;
 
 
     if (GMIME_IS_MULTIPART (part)) {
        GMimeMultipart *multipart = GMIME_MULTIPART (part);
        int i;
 
+       if (selected)
+           state->in_zone = 1;
+
        for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
            show_message_part (g_mime_multipart_get_part (multipart, i),
        for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
            show_message_part (g_mime_multipart_get_part (multipart, i),
-                              part_count, format, i == 0);
+                              state, format, params, i == 0);
        }
 
        }
 
+       if (selected)
+           state->in_zone = 0;
+
     } else if (GMIME_IS_MESSAGE_PART (part)) {
     } else if (GMIME_IS_MESSAGE_PART (part)) {
-       GMimeMessage *mime_message;
+       GMimeMessage *mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
 
 
-       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
+       if (selected)
+           state->in_zone = 1;
 
        show_message_part (g_mime_message_get_mime_part (mime_message),
 
        show_message_part (g_mime_message_get_mime_part (mime_message),
-                          part_count, format, first);
+                          state, format, params, TRUE);
+
+       if (selected)
+           state->in_zone = 0;
     }
 
     }
 
-    if (format->part_end)
-       format->part_end (part);
+    if (selected || state->in_zone) {
+       if (format->part_end)
+           format->part_end (part);
+    }
 }
 
 notmuch_status_t
 show_message_body (const char *filename,
 }
 
 notmuch_status_t
 show_message_body (const char *filename,
-                  const notmuch_show_format_t *format)
+                  const notmuch_show_format_t *format,
+                  notmuch_show_params_t *params)
 {
     GMimeStream *stream = NULL;
     GMimeParser *parser = NULL;
     GMimeMessage *mime_message = NULL;
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
     FILE *file = NULL;
 {
     GMimeStream *stream = NULL;
     GMimeParser *parser = NULL;
     GMimeMessage *mime_message = NULL;
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
     FILE *file = NULL;
-    int part_count = 0;
+    show_message_state_t state;
+
+    state.part_count = 0;
+    state.in_zone = 0;
 
     file = fopen (filename, "r");
     if (! file) {
 
     file = fopen (filename, "r");
     if (! file) {
@@ -89,8 +116,9 @@ show_message_body (const char *filename,
     mime_message = g_mime_parser_construct_message (parser);
 
     show_message_part (g_mime_message_get_mime_part (mime_message),
     mime_message = g_mime_parser_construct_message (parser);
 
     show_message_part (g_mime_message_get_mime_part (mime_message),
-                      &part_count,
+                      &state,
                       format,
                       format,
+                      params,
                       TRUE);
 
   DONE:
                       TRUE);
 
   DONE:
@@ -108,97 +136,3 @@ show_message_body (const char *filename,
 
     return ret;
 }
 
     return ret;
 }
-
-static void
-show_one_part_output (GMimeObject *part)
-{
-    GMimeStream *stream_filter = NULL;
-    GMimeDataWrapper *wrapper;
-    GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
-
-    stream_filter = g_mime_stream_filter_new(stream_stdout);
-    wrapper = g_mime_part_get_content_object (GMIME_PART (part));
-    if (wrapper && stream_filter)
-       g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
-    if (stream_filter)
-       g_object_unref(stream_filter);
-}
-
-static void
-show_one_part_worker (GMimeObject *part, int *part_count, int desired_part)
-{
-    if (GMIME_IS_MULTIPART (part)) {
-       GMimeMultipart *multipart = GMIME_MULTIPART (part);
-       int i;
-
-       *part_count = *part_count + 1;
-
-       for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
-               show_one_part_worker (g_mime_multipart_get_part (multipart, i),
-                                     part_count, desired_part);
-       }
-       return;
-    }
-
-    if (GMIME_IS_MESSAGE_PART (part)) {
-       GMimeMessage *mime_message;
-
-       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
-
-       show_one_part_worker (g_mime_message_get_mime_part (mime_message),
-                             part_count, desired_part);
-
-       return;
-    }
-
-    if (! (GMIME_IS_PART (part)))
-       return;
-
-    *part_count = *part_count + 1;
-
-    if (*part_count == desired_part)
-           show_one_part_output (part);
-}
-
-notmuch_status_t
-show_one_part (const char *filename, int part)
-{
-       GMimeStream *stream = NULL;
-       GMimeParser *parser = NULL;
-       GMimeMessage *mime_message = NULL;
-       notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-       FILE *file = NULL;
-       int part_count = 0;
-
-       file = fopen (filename, "r");
-       if (! file) {
-               fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
-               ret = NOTMUCH_STATUS_FILE_ERROR;
-               goto DONE;
-       }
-
-       stream = g_mime_stream_file_new (file);
-       g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
-
-       parser = g_mime_parser_new_with_stream (stream);
-
-       mime_message = g_mime_parser_construct_message (parser);
-
-       show_one_part_worker (g_mime_message_get_mime_part (mime_message),
-                             &part_count, part);
-
- DONE:
-       if (mime_message)
-               g_object_unref (mime_message);
-
-       if (parser)
-               g_object_unref (parser);
-
-       if (stream)
-               g_object_unref (stream);
-
-       if (file)
-               fclose (file);
-
-       return ret;
-}