The rationale for this feature is twofold: 1. It is useful to be able to view messages in as human-friendly format as possible. 2. The same format should still be machine-readable, too. The email format is mostly human-readable as is. The things difficult for a human eye are the huge amount of headers that are common these days and telling different messages apart when there are many. This commit adds the display format `pretty`. It recognizes if the output is a TTY and if so, applies coloring to headers. This turns out to be a good visual message separator. Additionally it only shows the most important headers, similarly to `--format=text`, and only displays plaintext parts (ie. text/* but not text/html). While human readability is the main goal, another design goal was that piping the output to `git am` works, at least for individual messages sent with `git send-email`. --- I wrote a v2 of this patch. I've been dogfooding for a while now and wanted a couple of enhancements, and also had learned about the notmuch test harness. The differences to the first version are: - add a unit test - show Message-ID, making replying etc. much easier - print a newline after each part, which helps a lot with messages that do not end in a newline I'm using this as a daily driver and am happy with it. NEWS | 7 ++++ completion/notmuch-completion.bash | 2 +- completion/zsh/_notmuch | 2 +- doc/man1/notmuch-show.rst | 8 +++- notmuch-show.c | 67 ++++++++++++++++++++++++++++++ test/T520-show.sh | 32 ++++++++++++++ 6 files changed, 115 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 3e776009..f5142ff1 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,13 @@ Notmuch 0.33 (UNRELEASED) ========================= +CLI +--- + +`notmuch show` now has `--format=pretty`, optimized for reading plain +text emails on the command line. It only shows the most important +headers and plain text parts and uses colors for headers. + Emacs ----- diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash index 15425697..86cbbcdc 100644 --- a/completion/notmuch-completion.bash +++ b/completion/notmuch-completion.bash @@ -514,7 +514,7 @@ _notmuch_show() return ;; --format) - COMPREPLY=( $( compgen -W "text json sexp mbox raw" -- "${cur}" ) ) + COMPREPLY=( $( compgen -W "text pretty json sexp mbox raw" -- "${cur}" ) ) return ;; --exclude|--body) diff --git a/completion/zsh/_notmuch b/completion/zsh/_notmuch index e920f10b..5cc386e2 100644 --- a/completion/zsh/_notmuch +++ b/completion/zsh/_notmuch @@ -237,7 +237,7 @@ _notmuch_search() { _notmuch_show() { _arguments -S \ '--entire-thread=[output entire threads]:show thread:(true false)' \ - '--format=[set output format]:output format:(text json sexp mbox raw)' \ + '--format=[set output format]:output format:(text pretty json sexp mbox raw)' \ '--format-version=[set output format version]:format version: ' \ '--part=[output a single decoded mime part]:part number: ' \ '--verify[verify signed MIME parts]' \ diff --git a/doc/man1/notmuch-show.rst b/doc/man1/notmuch-show.rst index fc6bec62..1fe4dcc7 100644 --- a/doc/man1/notmuch-show.rst +++ b/doc/man1/notmuch-show.rst @@ -34,7 +34,7 @@ Supported options for **show** include the matching messages. For ``--format=json`` and ``--format=sexp`` this defaults to true. For other formats, this defaults to false. -.. option:: --format=(text|json|sexp|mbox|raw) +.. option:: --format=(text|pretty|json|sexp|mbox|raw) **text** (default for messages) The default plain-text format has all text-content MIME parts @@ -46,6 +46,12 @@ Supported options for **show** include '}'), to either open or close the component. For a multipart MIME message, these parts will be nested. + **pretty** + The plain-text parts of all matching messages are printed in a + format optimized for readability. Only the most important + headers are displayed. If the output is to a TTY, the headers + are colored. + **json** The output is formatted with Javascript Object Notation (JSON). This format is more robust than the text format for diff --git a/notmuch-show.c b/notmuch-show.c index 232557d5..c417ec00 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -606,6 +606,65 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, return NOTMUCH_STATUS_SUCCESS; } +static notmuch_status_t +format_part_pretty (const void *ctx, sprinter_t *sp, mime_node_t *node, + int indent, const notmuch_show_params_t *params) +{ + /* The disposition and content-type metadata are associated with + * the envelope for message parts */ + GMimeObject *meta = node->envelope_part ? ( + GMIME_OBJECT (node->envelope_part) ) : node->part; + GMimeContentType *content_type = g_mime_object_get_content_type (meta); + GMimeStream *stream = params->out_stream; + int i; + bool color = isatty (fileno (stdout)); + + if (GMIME_IS_MESSAGE (node->part)) { + GMimeMessage *message = GMIME_MESSAGE (node->part); + char *recipients_string; + char *date_string; + + if (color) + g_mime_stream_printf (stream, "\e[36m"); + g_mime_stream_printf (stream, "Subject: %s\n", g_mime_message_get_subject (message)); + if (color) + g_mime_stream_printf (stream, "\e[33m"); + g_mime_stream_printf (stream, "From: %s\n", g_mime_message_get_from_string (message)); + if (color) + g_mime_stream_printf (stream, "\e[31m"); + recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_TO); + if (recipients_string) + g_mime_stream_printf (stream, "To: %s\n", recipients_string); + g_free (recipients_string); + recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_CC); + if (recipients_string) + g_mime_stream_printf (stream, "Cc: %s\n", recipients_string); + g_free (recipients_string); + date_string = g_mime_message_get_date_string (node, message); + if (color) + g_mime_stream_printf (stream, "\e[35m"); + g_mime_stream_printf (stream, "Date: %s\n", date_string); + if (color) + g_mime_stream_printf (stream, "\e[32m"); + g_mime_stream_printf (stream, "Message-ID: <%s>\n\n", g_mime_message_get_message_id ( + message)); + if (color) + g_mime_stream_printf (stream, "\e[0m"); + } + + if (GMIME_IS_PART (node->part) && + g_mime_content_type_is_type (content_type, "text", "*") && + ! g_mime_content_type_is_type (content_type, "text", "html")) { + show_text_part_content (node->part, stream, 0); + g_mime_stream_printf (stream, "\n"); + } + + for (i = 0; i < node->nchildren; i++) + format_part_pretty (ctx, sp, mime_node_child (node, i), indent, params); + + return NOTMUCH_STATUS_SUCCESS; +} + static void format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart *part) { @@ -1192,6 +1251,7 @@ enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_SEXP, NOTMUCH_FORMAT_TEXT, + NOTMUCH_FORMAT_PRETTY, NOTMUCH_FORMAT_MBOX, NOTMUCH_FORMAT_RAW }; @@ -1211,6 +1271,11 @@ static const notmuch_show_format_t format_text = { .part = format_part_text, }; +static const notmuch_show_format_t format_pretty = { + .new_sprinter = sprinter_text_create, + .part = format_part_pretty, +}; + static const notmuch_show_format_t format_mbox = { .new_sprinter = sprinter_text_create, .part = format_part_mbox, @@ -1225,6 +1290,7 @@ static const notmuch_show_format_t *formatters[] = { [NOTMUCH_FORMAT_JSON] = &format_json, [NOTMUCH_FORMAT_SEXP] = &format_sexp, [NOTMUCH_FORMAT_TEXT] = &format_text, + [NOTMUCH_FORMAT_PRETTY] = &format_pretty, [NOTMUCH_FORMAT_MBOX] = &format_mbox, [NOTMUCH_FORMAT_RAW] = &format_raw, }; @@ -1254,6 +1320,7 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[]) { .opt_keyword = &format, .name = "format", .keywords = (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON }, { "text", NOTMUCH_FORMAT_TEXT }, + { "pretty", NOTMUCH_FORMAT_PRETTY }, { "sexp", NOTMUCH_FORMAT_SEXP }, { "mbox", NOTMUCH_FORMAT_MBOX }, { "raw", NOTMUCH_FORMAT_RAW }, diff --git a/test/T520-show.sh b/test/T520-show.sh index 16222650..e555b284 100755 --- a/test/T520-show.sh +++ b/test/T520-show.sh @@ -10,4 +10,36 @@ notmuch show foo.. exit_code=$? test_expect_equal 1 $exit_code +test_begin_subtest "--format=pretty" +output=$(notmuch show --format=pretty id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com 2>&1 && echo OK) +test_expect_equal "$output" "Subject: [notmuch] preliminary FreeBSD support +From: Alex Botero-Lowry <alex.boterolowry@gmail.com> +To: notmuch@notmuchmail.org +Date: Tue, 17 Nov 2009 11:36:14 -0800 +Message-ID: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com> + +I saw the announcement this morning, and was very excited, as I had been +hoping sup would be turned into a library, +since I like the concept more than the UI (I'd rather an emacs interface). + +I did a preliminary compile which worked out fine, but +sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns -1 on +FreeBSD, so notmuch_config_open segfaulted. + +Attached is a patch that supplies a default buffer size of 64 in cases where +-1 is returned. + +http://www.opengroup.org/austin/docs/austin_328.txt - seems to indicate this +is acceptable behavior, +and http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.htmlspecifically +uses 64 as the +buffer size. + +_______________________________________________ +notmuch mailing list +notmuch@notmuchmail.org +http://notmuchmail.org/mailman/listinfo/notmuch + +OK" + test_done -- 2.32.0 _______________________________________________ notmuch mailing list -- notmuch@notmuchmail.org To unsubscribe send an email to notmuch-leave@notmuchmail.org