[PATCH v6 0/7] notmuch search --output=sender/recipients

Subject: [PATCH v6 0/7] notmuch search --output=sender/recipients

Date: Fri, 31 Oct 2014 22:53:54 +0100

To: notmuch@notmuchmail.org

Cc:

From: Michal Sojka


Hi all,

this is v6 of the search --output=address series. It obsoletes v5
(id:1414713573-21461-1-git-send-email-sojkam1@fel.cvut.cz).

Changes from v5 (full diff below):

- Added quoting of name parts if that is necessary (pointed out by
  Mark Walters). Structured formats contain both full address
  (possibly with quoted name) and unquoted individual fields.
- Fixed bug in --output=count --filter-by=*fold (reported by Jesse
  Rosenthal). New test was added for this case. Fixing the bug also
  resulted in simpler code :)
- Added missing unreferencing of InternetAddressList.

Changes from v4:

- patch changed to commit in commit messages
- opt->format changed to format
- Added comments to process_* functions
- duplicite changed to duplicate
- check_duplicate changed to is_duplicate
- Deduplication was split into two commits: basic deduplication
  without a command line option and configurable deduplication with
  --fiter-by.

Changes from v3:

- `o' renamed to `opt'.
- Conversion of --output from keyword to keyword-flags is now a
  separate patch.
- Structured output formats print name and address separately.
- Added test for --format=json.
- Changed --filter-by default to nameaddr. In v2, the default was
  addrfold, in v3 the default was no filtering at all. I believe that
  Mark's suggestion to make nameaddr the default is good trade off.
- Added new --output=count
- Minor style fixes
- Few typos fixed
- There is no way to output unfiltered (duplicite) addresses.
  Hopefully, the introduction of --output=count is sufficient
  replacement for this "feature".

Cheers,
-Michal


Jani Nikula (1):
  cli: Add support for parsing keyword-flag arguments

Michal Sojka (6):
  cli: search: Refactor passing of command line options
  cli: search: Convert --output to keyword-flag argument
  cli: search: Add --output={sender,recipients}
  cli: search: Do not output duplicate addresses
  cli: search: Add --output=count
  cli: search: Add --filter-by option to configure address filtering

 command-line-arguments.c           |   6 +-
 command-line-arguments.h           |   1 +
 completion/notmuch-completion.bash |   8 +-
 completion/notmuch-completion.zsh  |   4 +-
 doc/man1/notmuch-search.rst        |  66 ++++++-
 notmuch-search.c                   | 390 +++++++++++++++++++++++++++++--------
 test/T090-search-output.sh         |  87 +++++++++
 test/T095-search-filter-by.sh      |  73 +++++++
 test/T410-argument-parsing.sh      |   3 +-
 test/arg-test.c                    |   9 +
 10 files changed, 565 insertions(+), 82 deletions(-)
 create mode 100755 test/T095-search-filter-by.sh

-- 
2.1.1

diff --git a/notmuch-search.c b/notmuch-search.c
index 8bc80d3..a350f06 100644
--- a/notmuch-search.c
+++ b/notmuch-search.c
@@ -246,33 +246,35 @@ is_duplicate (const search_options_t *opt, GHashTable *addrs, const char *name,
 {
     notmuch_bool_t duplicate;
     char *key;
+    gchar *addrfold = NULL;
     mailbox_t *mailbox;
 
     if (opt->filter_by == FILTER_BY_ADDRFOLD ||
-	opt->filter_by == FILTER_BY_NAMEADDRFOLD) {
-	gchar *folded = g_utf8_casefold (addr, -1);
-	addr = talloc_strdup (opt->format, folded);
-	g_free (folded);
-    }
+	opt->filter_by == FILTER_BY_NAMEADDRFOLD)
+	addrfold = g_utf8_casefold (addr, -1);
+
     switch (opt->filter_by) {
     case FILTER_BY_NAMEADDR:
-    case FILTER_BY_NAMEADDRFOLD:
 	key = talloc_asprintf (opt->format, "%s <%s>", name, addr);
 	break;
+    case FILTER_BY_NAMEADDRFOLD:
+	key = talloc_asprintf (opt->format, "%s <%s>", name, addrfold);
+	break;
     case FILTER_BY_NAME:
 	key = talloc_strdup (opt->format, name); /* !name results in !key */
 	break;
     case FILTER_BY_ADDR:
-    case FILTER_BY_ADDRFOLD:
 	key = talloc_strdup (opt->format, addr);
 	break;
+    case FILTER_BY_ADDRFOLD:
+	key = talloc_strdup (opt->format, addrfold);
+	break;
     default:
 	INTERNAL_ERROR("invalid --filter-by flags");
     }
 
-    if (opt->filter_by == FILTER_BY_ADDRFOLD ||
-	opt->filter_by == FILTER_BY_NAMEADDRFOLD)
-	talloc_free ((char*)addr);
+    if (addrfold)
+	g_free (addrfold);
 
     if (! key)
 	return FALSE;
@@ -300,33 +302,28 @@ print_mailbox (const search_options_t *opt, const mailbox_t *mailbox)
     const char *addr = mailbox->addr;
     int count = mailbox->count;
     sprinter_t *format = opt->format;
+    InternetAddress *ia = internet_address_mailbox_new (name, addr);
+    char *name_addr;
 
-    if (format->is_text_printer) {
-	char *mailbox_str;
+    /* name_addr has the name part quoted if necessary. Compare
+     * 'John Doe <john@doe.com>' vs. '"Doe, John" <john@doe.com>' */
+    name_addr = internet_address_to_string (ia, FALSE);
 
-	if (name && *name)
-	    mailbox_str = talloc_asprintf (format, "%s <%s>", name, addr);
-	else
-	    mailbox_str = talloc_strdup (format, addr);
-
-	if (! mailbox_str) {
-	    fprintf (stderr, "Error: out of memory\n");
-	    return;
-	}
+    if (format->is_text_printer) {
 	if (count > 0) {
 	    format->integer (format, count);
 	    format->string (format, "\t");
 	}
-	format->string (format, mailbox_str);
+	format->string (format, name_addr);
 	format->separator (format);
-
-	talloc_free (mailbox_str);
     } else {
 	format->begin_map (format);
 	format->map_key (format, "name");
 	format->string (format, name);
 	format->map_key (format, "address");
 	format->string (format, addr);
+	format->map_key (format, "name-addr");
+	format->string (format, name_addr);
 	if (count > 0) {
 	    format->map_key (format, "count");
 	    format->integer (format, count);
@@ -334,6 +331,9 @@ print_mailbox (const search_options_t *opt, const mailbox_t *mailbox)
 	format->end (format);
 	format->separator (format);
     }
+
+    g_object_unref (ia);
+    g_free (name_addr);
 }
 
 /* Print or prepare for printing addresses from InternetAddressList. */
@@ -389,6 +389,8 @@ process_address_header (const search_options_t *opt, GHashTable *addrs, const ch
 	return;
 
     process_address_list (opt, addrs, list);
+
+    g_object_unref (list);
 }
 
 static void
diff --git a/test/T090-search-output.sh b/test/T090-search-output.sh
index 5a9bbc9..82380ac 100755
--- a/test/T090-search-output.sh
+++ b/test/T090-search-output.sh
@@ -413,73 +413,23 @@ test_expect_equal_file OUTPUT EXPECTED
 test_begin_subtest "--output=sender --format=json"
 notmuch search --output=sender --format=json '*' >OUTPUT
 cat <<EOF >EXPECTED
-[{"name": "François Boulogne", "address": "boulogne.f@gmail.com"},
-{"name": "Olivier Berger", "address": "olivier.berger@it-sudparis.eu"},
-{"name": "Chris Wilson", "address": "chris@chris-wilson.co.uk"},
-{"name": "Carl Worth", "address": "cworth@cworth.org"},
-{"name": "Alexander Botero-Lowry", "address": "alex.boterolowry@gmail.com"},
-{"name": "Keith Packard", "address": "keithp@keithp.com"},
-{"name": "Jjgod Jiang", "address": "gzjjgod@gmail.com"},
-{"name": "Rolland Santimano", "address": "rollandsantimano@yahoo.com"},
-{"name": "Jan Janak", "address": "jan@ryngle.com"},
-{"name": "Stewart Smith", "address": "stewart@flamingspork.com"},
-{"name": "Lars Kellogg-Stedman", "address": "lars@seas.harvard.edu"},
-{"name": "Alex Botero-Lowry", "address": "alex.boterolowry@gmail.com"},
-{"name": "Ingmar Vanhassel", "address": "ingmar@exherbo.org"},
-{"name": "Aron Griffis", "address": "agriffis@n01se.net"},
-{"name": "Adrian Perez de Castro", "address": "aperez@igalia.com"},
-{"name": "Israel Herraiz", "address": "isra@herraiz.org"},
-{"name": "Mikhail Gusarov", "address": "dottedmag@dottedmag.net"}]
-EOF
-test_expect_equal_file OUTPUT EXPECTED
-
-test_begin_subtest "--output=sender --output=count"
-notmuch search --output=sender --output=count '*' | sort -n >OUTPUT
-cat <<EOF >EXPECTED
-1	Adrian Perez de Castro <aperez@igalia.com>
-1	Aron Griffis <agriffis@n01se.net>
-1	Chris Wilson <chris@chris-wilson.co.uk>
-1	François Boulogne <boulogne.f@gmail.com>
-1	Ingmar Vanhassel <ingmar@exherbo.org>
-1	Israel Herraiz <isra@herraiz.org>
-1	Olivier Berger <olivier.berger@it-sudparis.eu>
-1	Rolland Santimano <rollandsantimano@yahoo.com>
-2	Alex Botero-Lowry <alex.boterolowry@gmail.com>
-2	Jjgod Jiang <gzjjgod@gmail.com>
-3	Stewart Smith <stewart@flamingspork.com>
-4	Alexander Botero-Lowry <alex.boterolowry@gmail.com>
-4	Jan Janak <jan@ryngle.com>
-5	Lars Kellogg-Stedman <lars@seas.harvard.edu>
-5	Mikhail Gusarov <dottedmag@dottedmag.net>
-7	Keith Packard <keithp@keithp.com>
-12	Carl Worth <cworth@cworth.org>
-EOF
-test_expect_equal_file OUTPUT EXPECTED
-
-test_begin_subtest "--output=sender --output=count --format=json"
-# Since the iteration order of GHashTable is not specified, we
-# preprocess and sort the results to keep the order stable here.
-notmuch search --output=sender --output=count --format=json '*' | \
-    sed -e 's/^\[//' -e 's/]$//' -e 's/,$//' | \
-    sort --field-separator=":" --key=4n --key=2 >OUTPUT
-cat <<EOF >EXPECTED
-{"name": "Adrian Perez de Castro", "address": "aperez@igalia.com", "count": 1}
-{"name": "Aron Griffis", "address": "agriffis@n01se.net", "count": 1}
-{"name": "Chris Wilson", "address": "chris@chris-wilson.co.uk", "count": 1}
-{"name": "François Boulogne", "address": "boulogne.f@gmail.com", "count": 1}
-{"name": "Ingmar Vanhassel", "address": "ingmar@exherbo.org", "count": 1}
-{"name": "Israel Herraiz", "address": "isra@herraiz.org", "count": 1}
-{"name": "Olivier Berger", "address": "olivier.berger@it-sudparis.eu", "count": 1}
-{"name": "Rolland Santimano", "address": "rollandsantimano@yahoo.com", "count": 1}
-{"name": "Alex Botero-Lowry", "address": "alex.boterolowry@gmail.com", "count": 2}
-{"name": "Jjgod Jiang", "address": "gzjjgod@gmail.com", "count": 2}
-{"name": "Stewart Smith", "address": "stewart@flamingspork.com", "count": 3}
-{"name": "Alexander Botero-Lowry", "address": "alex.boterolowry@gmail.com", "count": 4}
-{"name": "Jan Janak", "address": "jan@ryngle.com", "count": 4}
-{"name": "Lars Kellogg-Stedman", "address": "lars@seas.harvard.edu", "count": 5}
-{"name": "Mikhail Gusarov", "address": "dottedmag@dottedmag.net", "count": 5}
-{"name": "Keith Packard", "address": "keithp@keithp.com", "count": 7}
-{"name": "Carl Worth", "address": "cworth@cworth.org", "count": 12}
+[{"name": "François Boulogne", "address": "boulogne.f@gmail.com", "name-addr": "François Boulogne <boulogne.f@gmail.com>"},
+{"name": "Olivier Berger", "address": "olivier.berger@it-sudparis.eu", "name-addr": "Olivier Berger <olivier.berger@it-sudparis.eu>"},
+{"name": "Chris Wilson", "address": "chris@chris-wilson.co.uk", "name-addr": "Chris Wilson <chris@chris-wilson.co.uk>"},
+{"name": "Carl Worth", "address": "cworth@cworth.org", "name-addr": "Carl Worth <cworth@cworth.org>"},
+{"name": "Alexander Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alexander Botero-Lowry <alex.boterolowry@gmail.com>"},
+{"name": "Keith Packard", "address": "keithp@keithp.com", "name-addr": "Keith Packard <keithp@keithp.com>"},
+{"name": "Jjgod Jiang", "address": "gzjjgod@gmail.com", "name-addr": "Jjgod Jiang <gzjjgod@gmail.com>"},
+{"name": "Rolland Santimano", "address": "rollandsantimano@yahoo.com", "name-addr": "Rolland Santimano <rollandsantimano@yahoo.com>"},
+{"name": "Jan Janak", "address": "jan@ryngle.com", "name-addr": "Jan Janak <jan@ryngle.com>"},
+{"name": "Stewart Smith", "address": "stewart@flamingspork.com", "name-addr": "Stewart Smith <stewart@flamingspork.com>"},
+{"name": "Lars Kellogg-Stedman", "address": "lars@seas.harvard.edu", "name-addr": "Lars Kellogg-Stedman <lars@seas.harvard.edu>"},
+{"name": "Alex Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alex Botero-Lowry <alex.boterolowry@gmail.com>"},
+{"name": "Ingmar Vanhassel", "address": "ingmar@exherbo.org", "name-addr": "Ingmar Vanhassel <ingmar@exherbo.org>"},
+{"name": "Aron Griffis", "address": "agriffis@n01se.net", "name-addr": "Aron Griffis <agriffis@n01se.net>"},
+{"name": "Adrian Perez de Castro", "address": "aperez@igalia.com", "name-addr": "Adrian Perez de Castro <aperez@igalia.com>"},
+{"name": "Israel Herraiz", "address": "isra@herraiz.org", "name-addr": "Israel Herraiz <isra@herraiz.org>"},
+{"name": "Mikhail Gusarov", "address": "dottedmag@dottedmag.net", "name-addr": "Mikhail Gusarov <dottedmag@dottedmag.net>"}]
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
@@ -487,7 +437,7 @@ test_begin_subtest "--output=recipients"
 notmuch search --output=recipients '*' >OUTPUT
 cat <<EOF >EXPECTED
 Allan McRae <allan@archlinux.org>
-Discussion about the Arch User Repository (AUR) <aur-general@archlinux.org>
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
 olivier.berger@it-sudparis.eu
 notmuch@notmuchmail.org
 notmuch <notmuch@notmuchmail.org>
@@ -501,7 +451,7 @@ notmuch search --output=sender --output=recipients '*' >OUTPUT
 cat <<EOF >EXPECTED
 François Boulogne <boulogne.f@gmail.com>
 Allan McRae <allan@archlinux.org>
-Discussion about the Arch User Repository (AUR) <aur-general@archlinux.org>
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
 Olivier Berger <olivier.berger@it-sudparis.eu>
 olivier.berger@it-sudparis.eu
 Chris Wilson <chris@chris-wilson.co.uk>
diff --git a/test/T095-search-filter-by.sh b/test/T095-search-filter-by.sh
index 97d9a9b..15c9f77 100755
--- a/test/T095-search-filter-by.sh
+++ b/test/T095-search-filter-by.sh
@@ -2,17 +2,17 @@
 test_description='duplicite address filtering in "notmuch search --output=recipients"'
 . ./test-lib.sh
 
-add_message '[to]="Real Name <foo@example.com>, Real Name <bar@example.com>"'
-add_message '[to]="Nickname <foo@example.com>"' '[cc]="Real Name <Bar@Example.COM>"'
-add_message '[to]="Nickname <foo@example.com>"' '[bcc]="Real Name <Bar@Example.COM>"'
+add_message '[to]="John Doe <foo@example.com>, John Doe <bar@example.com>"'
+add_message '[to]="\"Doe, John\" <foo@example.com>"' '[cc]="John Doe <Bar@Example.COM>"'
+add_message '[to]="\"Doe, John\" <foo@example.com>"' '[bcc]="John Doe <Bar@Example.COM>"'
 
 test_begin_subtest "--output=recipients"
 notmuch search --output=recipients "*" >OUTPUT
 cat <<EOF >EXPECTED
-Real Name <foo@example.com>
-Real Name <bar@example.com>
-Nickname <foo@example.com>
-Real Name <Bar@Example.COM>
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+"Doe, John" <foo@example.com>
+John Doe <Bar@Example.COM>
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
@@ -20,44 +20,53 @@ test_begin_subtest "--output=recipients --filter-by=nameaddr"
 notmuch search --output=recipients --filter-by=nameaddr "*" >OUTPUT
 # The same as above
 cat <<EOF >EXPECTED
-Real Name <foo@example.com>
-Real Name <bar@example.com>
-Nickname <foo@example.com>
-Real Name <Bar@Example.COM>
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+"Doe, John" <foo@example.com>
+John Doe <Bar@Example.COM>
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "--output=recipients --filter-by=name"
 notmuch search --output=recipients --filter-by=name "*" >OUTPUT
 cat <<EOF >EXPECTED
-Real Name <foo@example.com>
-Nickname <foo@example.com>
+John Doe <foo@example.com>
+"Doe, John" <foo@example.com>
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "--output=recipients --filter-by=addr"
 notmuch search --output=recipients --filter-by=addr "*" >OUTPUT
 cat <<EOF >EXPECTED
-Real Name <foo@example.com>
-Real Name <bar@example.com>
-Real Name <Bar@Example.COM>
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+John Doe <Bar@Example.COM>
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "--output=recipients --filter-by=addrfold"
 notmuch search --output=recipients --filter-by=addrfold "*" >OUTPUT
 cat <<EOF >EXPECTED
-Real Name <foo@example.com>
-Real Name <bar@example.com>
+John Doe <foo@example.com>
+John Doe <bar@example.com>
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 
 test_begin_subtest "--output=recipients --filter-by=nameaddrfold"
 notmuch search --output=recipients --filter-by=nameaddrfold "*" >OUTPUT
 cat <<EOF >EXPECTED
-Real Name <foo@example.com>
-Real Name <bar@example.com>
-Nickname <foo@example.com>
+John Doe <foo@example.com>
+John Doe <bar@example.com>
+"Doe, John" <foo@example.com>
+EOF
+test_expect_equal_file OUTPUT EXPECTED
+
+test_begin_subtest "--output=recipients --filter-by=nameaddrfold --output=count"
+notmuch search --output=recipients --filter-by=nameaddrfold --output=count "*" | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+1	John Doe <foo@example.com>
+2	"Doe, John" <foo@example.com>
+3	John Doe <bar@example.com>
 EOF
 test_expect_equal_file OUTPUT EXPECTED
 

Thread: