! $split &&
case "${cur}" in
-*)
- local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate="
+ local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= --filter-by="
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;
COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
return
;;
+ --filter-by)
+ COMPREPLY=( $( compgen -W "nameaddr name addr addrfold nameaddrfold" -- "${cur}" ) )
+ return
+ ;;
esac
! $split &&
_arguments -s : \
'--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
'--output=[select what to output]:output:((sender recipients count))'
+ '--filter-by=[filter out duplicate addresses]:filter-by:((nameaddr\:"both name and address part" name\:"name part" addr\:"address part" addrfold\:"case-insensitive address part" nameaddrfold\:"name and case-insensitive address part"))'
}
_notmuch()
===========
Search for messages matching the given search terms, and display the
-addresses from them. Duplicate addresses are filtered out.
+addresses from them. Duplicate addresses are filtered out. Filtering
+can be configured with the --filter-by option.
See **notmuch-search-terms(7)** for details of the supported syntax for
<search-terms>.
**false** allows excluded messages to match search terms and
appear in displayed results.
+ ``--filter-by=``\ (**nameaddr**\ \|\ **name** \|\ **addr**\ \|\ **addrfold**\ \|\ **nameaddrfold**\)
+
+ Controls how to filter out duplicate addresses. The filtering
+ algorithm receives a sequence of email addresses and outputs
+ the same sequence without the addresses that are considered a
+ duplicate of a previously output address. What is considered a
+ duplicate depends on how the two addresses are compared:
+
+ **nameaddr** means that both name and address parts are
+ compared in case-sensitive manner. Therefore, all same looking
+ addresses strings are considered duplicate. This is the
+ default.
+
+ **name** means that only the name part is compared (in
+ case-sensitive manner). For example, the addresses "John Doe
+ <me@example.com>" and "John Doe <john@doe.name>" will be
+ considered duplicate.
+
+ **addr** means that only the address part is compared (in
+ case-sensitive manner). For example, the addresses "John Doe
+ <john@example.com>" and "Dr. John Doe <john@example.com>" will
+ be considered duplicate.
+
+ **addrfold** is like **addr**, but comparison is done in
+ canse-insensitive manner. For example, the addresses "John Doe
+ <john@example.com>" and "Dr. John Doe <JOHN@EXAMPLE.COM>" will
+ be considered duplicate.
+
+ **nameaddrfold** is like **nameaddr**, but address comparison
+ is done in canse-insensitive manner. For example, the
+ addresses "John Doe <john@example.com>" and "John Doe
+ <JOHN@EXAMPLE.COM>" will be considered duplicate.
+
EXIT STATUS
===========
NOTMUCH_FORMAT_SEXP
} format_sel_t;
+typedef enum {
+ FILTER_BY_NAMEADDR = 0,
+ FILTER_BY_NAME,
+ FILTER_BY_ADDR,
+ FILTER_BY_ADDRFOLD,
+ FILTER_BY_NAMEADDRFOLD,
+} filter_by_t;
+
typedef struct {
notmuch_database_t *notmuch;
format_sel_t format_sel;
int limit;
int dupe;
GHashTable *addresses;
+ filter_by_t filter_by;
} search_context_t;
typedef struct {
return 0;
}
-/* Returns TRUE iff name and addr is duplicate. If not, stores the
- * name/addr pair in order to detect subsequent duplicates. */
+/* Returns TRUE iff name and/or addr is considered duplicate. If not,
+ * stores the name/addr pair in order to detect subsequent
+ * duplicates. */
static notmuch_bool_t
is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
{
notmuch_bool_t duplicate;
char *key;
+ gchar *addrfold = NULL;
mailbox_t *mailbox;
- key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
+ if (ctx->filter_by == FILTER_BY_ADDRFOLD ||
+ ctx->filter_by == FILTER_BY_NAMEADDRFOLD)
+ addrfold = g_utf8_casefold (addr, -1);
+
+ switch (ctx->filter_by) {
+ case FILTER_BY_NAMEADDR:
+ key = talloc_asprintf (ctx->format, "%s <%s>", name, addr);
+ break;
+ case FILTER_BY_NAMEADDRFOLD:
+ key = talloc_asprintf (ctx->format, "%s <%s>", name, addrfold);
+ break;
+ case FILTER_BY_NAME:
+ key = talloc_strdup (ctx->format, name); /* !name results in !key */
+ break;
+ case FILTER_BY_ADDR:
+ key = talloc_strdup (ctx->format, addr);
+ break;
+ case FILTER_BY_ADDRFOLD:
+ key = talloc_strdup (ctx->format, addrfold);
+ break;
+ default:
+ INTERNAL_ERROR("invalid --filter-by flags");
+ }
+
+ if (addrfold)
+ g_free (addrfold);
+
if (! key)
return FALSE;
(notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
{ "false", NOTMUCH_EXCLUDE_FALSE },
{ 0, 0 } } },
+ { NOTMUCH_OPT_KEYWORD, &ctx->filter_by, "filter-by", 'b',
+ (notmuch_keyword_t []){ { "nameaddr", FILTER_BY_NAMEADDR },
+ { "name", FILTER_BY_NAME },
+ { "addr", FILTER_BY_ADDR },
+ { "addrfold", FILTER_BY_ADDRFOLD },
+ { "nameaddrfold", FILTER_BY_NAMEADDRFOLD },
+ { 0, 0 } } },
{ NOTMUCH_OPT_INHERIT, &common_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
+ ctx->filter_by = FILTER_BY_NAMEADDR,
opt_index = parse_arguments (argc, argv, options, 1);
if (opt_index < 0)
return EXIT_FAILURE;
--- /dev/null
+#!/usr/bin/env bash
+test_description='duplicite address filtering in "notmuch address"'
+. ./test-lib.sh
+
+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 address --output=recipients "*" >OUTPUT
+cat <<EOF >EXPECTED
+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=nameaddr"
+notmuch address --output=recipients --filter-by=nameaddr "*" >OUTPUT
+# The same as above
+cat <<EOF >EXPECTED
+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 address --output=recipients --filter-by=name "*" >OUTPUT
+cat <<EOF >EXPECTED
+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 address --output=recipients --filter-by=addr "*" >OUTPUT
+cat <<EOF >EXPECTED
+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 address --output=recipients --filter-by=addrfold "*" >OUTPUT
+cat <<EOF >EXPECTED
+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 address --output=recipients --filter-by=nameaddrfold "*" >OUTPUT
+cat <<EOF >EXPECTED
+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 address --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
+
+test_done