From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1 ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id 0OXJAt9332CmUQAAgWs5BA (envelope-from ) for ; Fri, 02 Jul 2021 22:32:31 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1 with LMTPS id QA4JOt5332AeHAAAbx9fmQ (envelope-from ) for ; Fri, 02 Jul 2021 20:32:30 +0000 Received: from mail.notmuchmail.org (nmbug.tethera.net [IPv6:2607:5300:201:3100::1657]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 2195696A9 for ; Fri, 2 Jul 2021 22:32:30 +0200 (CEST) Received: from nmbug.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id 5A4432757D; Fri, 2 Jul 2021 16:32:24 -0400 (EDT) Received: from mail-lf1-f50.google.com (mail-lf1-f50.google.com [209.85.167.50]) by mail.notmuchmail.org (Postfix) with ESMTPS id 30190271CA for ; Fri, 2 Jul 2021 16:32:21 -0400 (EDT) Received: by mail-lf1-f50.google.com with SMTP id d16so20209822lfn.3 for ; Fri, 02 Jul 2021 13:32:21 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=qSTdRsDxa3viENrfXLPsmnfKMzrgOxEL+wHMjX8NyUA=; b=eeyADBgB2yxHcqlyCx2hxnORqMVFKuNxL810u2MzsavVF2MKy8bbgO9VxWoWLYfwi8 PHA8NypNgw61awk6ulGPgIl6W3VxGyEqRZ0fSX52VAFxw1gT4s+EiKAK2q3v/1tSUZYr jRb9jr44OOYPajFU5fDXCEldEQQhZx3Yk5uy0RLUCzwZBpsASzCbHCzmLLREMPO2k3X/ WUjxphHHbcOB4QOrdahV6kyCexPjsOZh0OFYrWTYvU0vz5hFSr/h4q6nZZcJpsLYM34O GX2fCmXi4TyQjxx0yEJYn5uanntES1EojrIQBkSI2MjfuWr4Bzrwmn618CrogyTv9ha6 tHXQ== X-Gm-Message-State: AOAM532Rl2zxbs6e4NrcdelPT9pkZrsOZXnwaeit9d9HVJBUTSwHLvqJ aW2Y7N2elgPCLHrmJMC3Q7IVfoA+/Arc5w== X-Google-Smtp-Source: ABdhPJy6LWYN4gKDfO7gz2BUGXxG4iX2je6HcEBpnGqnUe/1gYBbqO2nYBY59ltE5Nahl8fwSzfGOQ== X-Received: by 2002:ac2:55b4:: with SMTP id y20mr1043406lfg.142.1625257939185; Fri, 02 Jul 2021 13:32:19 -0700 (PDT) Received: from localhost (88-112-11-80.elisa-laajakaista.fi. [88.112.11.80]) by smtp.gmail.com with ESMTPSA id q4sm466030ljh.13.2021.07.02.13.32.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 02 Jul 2021 13:32:18 -0700 (PDT) From: Hannu Hartikainen To: notmuch@notmuchmail.org Subject: [PATCH v2] cli/show: add --format=pretty Date: Fri, 2 Jul 2021 23:31:53 +0300 Message-Id: <20210702203153.47289-1-hannu@hrtk.in> X-Mailer: git-send-email 2.32.0 In-Reply-To: <20210619203838.275767-1-hannu@hrtk.in> References: <20210619203838.275767-1-hannu@hrtk.in> MIME-Version: 1.0 Message-ID-Hash: RKUJZ2H645GRE7XVJSUX642GKFH7UXKN X-Message-ID-Hash: RKUJZ2H645GRE7XVJSUX642GKFH7UXKN X-MailFrom: hannu.hartikainen@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-notmuch.notmuchmail.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header CC: Hannu Hartikainen X-Mailman-Version: 3.2.1 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Help: List-Post: List-Subscribe: List-Unsubscribe: Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-Migadu-Flow: FLOW_IN ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1625257950; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=pjYDkLxPYh92R1y292fLqumnJabk7bDZWqrz5FyHQFw=; b=cxFpyPczycY75ie2GHcgGK0XvQVEaGbNM9CBZxnIf+3qvpTyVCD5mWiAvam5hS1zOXaHpu fBzuirTeK2qqeZ5snZpcoOQDDkyqhPAqNrBZGMREws2HVI6kLM1TzJsBY2C82FjLI7kOqE GiyWBEh47aGQCPRGWMfQr0UXDTjoUkSXf/bNt7jo75GFhFQ7cgwL6tl116+OXzeO1WygFK rz3dpo3XAON0pj6x6mtgEqSAlf2zpg1dyVS10VT6eM/k35+NF5wVi/ETsniTXuYatT2uCz AmlKkgVjN1MZwRy61wUq7S3t9Qafy3HCZvT8DGk8csx2GHBnT4JjnUnrXwReTw== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1625257950; a=rsa-sha256; cv=none; b=r77k5WSE+z84ukW/08NNyuSKzPpoQkGcJA9MrANoJxbd09qg1ERVXJFGvzlcZNesj8KeVj VziNAJWc4pIB2K0uj/+gW0TjKIz1ZwGJonjrjymI2SsZz+XaXjnBLvA9FFrxJlrB+bHx+6 dyI1XzTMGOcI+5P5iTuZBfFz/qpu4LXMwm1jeg2wZVVjGDTjlvmsnuy4OO/Yx5Zkd1ZfPj mq2AKu1g6dUfQPvv9FkseMwXJPIc9pFdBnCk5t+ZuEy759SpXY6kNY6SH/tBvt1KHHCpw5 canzORwdM7FPyrJ1zqG0rv+hmlCPEN77F5reU7ixUh2N/cZRox/STOMpkrqWjQ== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 2607:5300:201:3100::1657 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Spam-Score: -1.02 Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 2607:5300:201:3100::1657 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Migadu-Queue-Id: 2195696A9 X-Spam-Score: -1.02 X-Migadu-Scanner: scn0.migadu.com X-TUID: N+l3C27Iq9wz 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 +To: notmuch@notmuchmail.org +Date: Tue, 17 Nov 2009 11:36:14 -0800 +Message-ID: + +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