New part output handling as option to notmuch-show.

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.
This commit is contained in:
Jameson Graef Rollins 2011-05-23 15:31:32 -07:00
parent ecae541031
commit 2f8871df6e
6 changed files with 134 additions and 218 deletions

View file

@ -78,6 +78,7 @@ typedef struct notmuch_show_format {
typedef struct notmuch_show_params { typedef struct notmuch_show_params {
int entire_thread; int entire_thread;
int raw; int raw;
int part;
} notmuch_show_params_t; } notmuch_show_params_t;
/* There's no point in continuing when we've detected that we've done /* 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 int
notmuch_cat_command (void *ctx, int argc, char *argv[]); notmuch_cat_command (void *ctx, int argc, char *argv[]);
int
notmuch_part_command (void *ctx, int argc, char *argv[]);
int int
notmuch_config_command (void *ctx, int argc, char *argv[]); 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 notmuch_status_t
show_message_body (const char *filename, 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 notmuch_status_t
show_one_part (const char *filename, int part); show_one_part (const char *filename, int part);

View 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 *subject, *from_addr = NULL;
const char *in_reply_to, *orig_references, *references; const char *in_reply_to, *orig_references, *references;
const notmuch_show_format_t *format = &format_reply; const notmuch_show_format_t *format = &format_reply;
notmuch_show_params_t params;
params.part = -1;
for (messages = notmuch_query_search_messages (query); for (messages = notmuch_query_search_messages (query);
notmuch_messages_valid (messages); 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")); notmuch_message_get_header (message, "from"));
show_message_body (notmuch_message_get_filename (message), show_message_body (notmuch_message_get_filename (message),
format); format, &params);
notmuch_message_destroy (message); notmuch_message_destroy (message);
} }

View 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 * static const char *
_get_tags_as_string (const void *ctx, notmuch_message_t *message) _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; GMimeDataWrapper *wrapper;
const char *charset; 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"); charset = g_mime_object_get_content_type_parameter (part, "charset");
if (stream_out) { if (stream_out) {
@ -489,31 +506,46 @@ 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 static void
show_message (void *ctx, show_message (void *ctx,
const notmuch_show_format_t *format, const notmuch_show_format_t *format,
notmuch_message_t *message, notmuch_message_t *message,
int indent) int indent,
notmuch_show_params_t *params)
{ {
fputs (format->message_start, stdout); if (params->part <= 0) {
if (format->message) fputs (format->message_start, stdout);
format->message(ctx, message, indent); if (format->message)
format->message(ctx, message, indent);
fputs (format->header_start, stdout); fputs (format->header_start, stdout);
if (format->header) if (format->header)
format->header(ctx, message); format->header(ctx, message);
fputs (format->header_end, stdout); fputs (format->header_end, stdout);
fputs (format->body_start, stdout);
}
fputs (format->body_start, stdout);
if (format->part) if (format->part)
show_message_body (notmuch_message_get_filename (message), show_message_body (notmuch_message_get_filename (message),
format); format, params);
fputs (format->body_end, stdout);
fputs (format->message_end, stdout); if (params->part <= 0) {
fputs (format->body_end, stdout);
fputs (format->message_end, stdout);
}
} }
static void static void
show_messages (void *ctx, show_messages (void *ctx,
const notmuch_show_format_t *format, const notmuch_show_format_t *format,
@ -545,7 +577,7 @@ show_messages (void *ctx,
next_indent = indent; next_indent = indent;
if (match || params->entire_thread) { if (match || params->entire_thread) {
show_message (ctx, format, message, indent); show_message (ctx, format, message, indent, params);
next_indent = indent + 1; next_indent = indent + 1;
fputs (format->message_set_sep, stdout); 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; const char *filename;
FILE *file; 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.entire_thread = 0;
params.raw = 0; params.raw = 0;
params.part = -1;
for (i = 0; i < argc && argv[i][0] == '-'; i++) { for (i = 0; i < argc && argv[i][0] == '-'; i++) {
if (strcmp (argv[i], "--") == 0) { 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) { } else if (strcmp (opt, "mbox") == 0) {
format = &format_mbox; format = &format_mbox;
} else if (strcmp (opt, "raw") == 0) { } else if (strcmp (opt, "raw") == 0) {
format = &format_raw;
params.raw = 1; params.raw = 1;
} else { } else {
fprintf (stderr, "Invalid value for --format: %s\n", opt); fprintf (stderr, "Invalid value for --format: %s\n", opt);
return 1; 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) { } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
params.entire_thread = 1; params.entire_thread = 1;
} else { } 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 /* If --format=raw specified without specifying part, we can only
* output single message, so set part=0 */ * 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); return do_show_single (ctx, query, format, &params);
else else
return do_show (ctx, query, format, &params); 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;
}

View file

@ -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 Consumers of this format should expect to implement MIME decoding and
similar functions. This format must only be used with search terms similar functions. This format must only be used with search terms
matching a single message. matching a single message.
.RE .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
A common use of A common use of
.B notmuch show .B notmuch show
is to display a single thread of email messages. For this, use a is to display a single thread of email messages. For this, use a
@ -311,7 +323,8 @@ See the
.B "SEARCH SYNTAX" .B "SEARCH SYNTAX"
section below for details of the supported syntax for <search-terms>. section below for details of the supported syntax for <search-terms>.
.RE .RE
.TP .RS 4
.TP 4
.BR count " <search-term>..." .BR count " <search-term>..."
Count messages matching the search terms. Count messages matching the search terms.

View file

@ -269,6 +269,15 @@ command_t commands[] = {
"\t\tdecoding and similar functions. This format must only\n" "\t\tdecoding and similar functions. This format must only\n"
"\t\tbe used with search terms matching a single message.\n" "\t\tbe used with search terms matching a single message.\n"
"\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" "\tA common use of \"notmuch show\" is to display a single\n"
"\tthread of email messages. For this, use a search term of\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" "\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" "\tcontain tags only from messages that match the search-term(s).\n"
"\n" "\n"
"\tIn both cases the list will be alphabetically sorted." }, "\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, { "config", notmuch_config_command,
"[get|set] <section>.<item> [value ...]", "[get|set] <section>.<item> [value ...]",
"Get or set settings in the notmuch configuration file.", "Get or set settings in the notmuch configuration file.",

View file

@ -22,13 +22,20 @@
#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 static void
show_message_part (GMimeObject *part, 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))) { if (! (GMIME_IS_PART (part) || GMIME_IS_MULTIPART (part) || GMIME_IS_MESSAGE_PART (part))) {
fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n", fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
@ -36,43 +43,63 @@ show_message_part (GMimeObject *part,
return; return;
} }
if (!first) selected = (params->part <= 0 || state->part_count == params->part);
fputs (format->part_sep, stdout);
format->part (part, part_count); if (selected || state->in_zone) {
if (!first && (params->part <= 0 || state->in_zone))
fputs (format->part_sep, stdout);
format->part (part, &(state->part_count));
}
if (GMIME_IS_MULTIPART (part)) { if (GMIME_IS_MULTIPART (part)) {
GMimeMultipart *multipart = GMIME_MULTIPART (part); GMimeMultipart *multipart = GMIME_MULTIPART (part);
int i; int i;
if (selected)
state->in_zone = 1;
for (i = 0; i < g_mime_multipart_get_count (multipart); i++) { for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
show_message_part (g_mime_multipart_get_part (multipart, i), show_message_part (g_mime_multipart_get_part (multipart, i),
part_count, format, i == 0); state, format, params, i == 0);
} }
} else if (GMIME_IS_MESSAGE_PART (part)) { if (selected)
GMimeMessage *mime_message; state->in_zone = 0;
mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part)); } else if (GMIME_IS_MESSAGE_PART (part)) {
GMimeMessage *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) if (selected || state->in_zone) {
format->part_end (part); if (format->part_end)
format->part_end (part);
}
} }
notmuch_status_t notmuch_status_t
show_message_body (const char *filename, 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; GMimeStream *stream = NULL;
GMimeParser *parser = NULL; GMimeParser *parser = NULL;
GMimeMessage *mime_message = NULL; GMimeMessage *mime_message = NULL;
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
FILE *file = NULL; FILE *file = NULL;
int part_count = 0; show_message_state_t state;
state.part_count = 0;
state.in_zone = 0;
file = fopen (filename, "r"); file = fopen (filename, "r");
if (! file) { if (! file) {
@ -89,8 +116,9 @@ show_message_body (const char *filename,
mime_message = g_mime_parser_construct_message (parser); mime_message = g_mime_parser_construct_message (parser);
show_message_part (g_mime_message_get_mime_part (mime_message), show_message_part (g_mime_message_get_mime_part (mime_message),
&part_count, &state,
format, format,
params,
TRUE); TRUE);
DONE: 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;
}