notmuch search: Add a new --output=(summary|threads|messages|tags) option.

This can be handy for scripts which need to extract just a thread ID from
a search term, for example.
This commit is contained in:
Carl Worth 2010-10-28 11:19:53 -07:00
parent e83b40138e
commit 6dcb7592e3
4 changed files with 267 additions and 54 deletions

9
TODO
View file

@ -89,15 +89,6 @@ option (or similar) to "notmuch show".) For now, this is being worked
around in the emacs interface by noticing that "notmuch show" returns around in the emacs interface by noticing that "notmuch show" returns
nothing and re-rerunning the command without the extra arguments. nothing and re-rerunning the command without the extra arguments.
Teach "notmuch search" to return many different kinds of results. Some
ideas:
notmuch search --output=threads # Default if no --output is given
notmuch search --output=messages
notmuch search --output=tags
notmuch search --output=addresses
notmuch search --output=terms
Add a "--format" option to "notmuch search", (something printf-like Add a "--format" option to "notmuch search", (something printf-like
for selecting what gets printed). for selecting what gets printed).

View file

@ -20,10 +20,20 @@
#include "notmuch-client.h" #include "notmuch-client.h"
typedef enum {
OUTPUT_SUMMARY,
OUTPUT_THREADS,
OUTPUT_MESSAGES,
OUTPUT_TAGS
} output_t;
typedef struct search_format { typedef struct search_format {
const char *results_start; const char *results_start;
const char *thread_start; const char *item_start;
void (*thread) (const void *ctx, void (*item_id) (const void *ctx,
const char *item_type,
const char *item_id);
void (*thread_summary) (const void *ctx,
const char *thread_id, const char *thread_id,
const time_t date, const time_t date,
const int matched, const int matched,
@ -34,11 +44,16 @@ typedef struct search_format {
const char *tag; const char *tag;
const char *tag_sep; const char *tag_sep;
const char *tag_end; const char *tag_end;
const char *thread_sep; const char *item_sep;
const char *thread_end; const char *item_end;
const char *results_end; const char *results_end;
} search_format_t; } search_format_t;
static void
format_item_id_text (const void *ctx,
const char *item_type,
const char *item_id);
static void static void
format_thread_text (const void *ctx, format_thread_text (const void *ctx,
const char *thread_id, const char *thread_id,
@ -50,14 +65,20 @@ format_thread_text (const void *ctx,
static const search_format_t format_text = { static const search_format_t format_text = {
"", "",
"", "",
format_item_id_text,
format_thread_text, format_thread_text,
" (", " (",
"%s", " ", "%s", " ",
")", "", ")", "\n",
"\n",
"", "",
"\n",
}; };
static void
format_item_id_json (const void *ctx,
const char *item_type,
const char *item_id);
static void static void
format_thread_json (const void *ctx, format_thread_json (const void *ctx,
const char *thread_id, const char *thread_id,
@ -69,6 +90,7 @@ format_thread_json (const void *ctx,
static const search_format_t format_json = { static const search_format_t format_json = {
"[", "[",
"{", "{",
format_item_id_json,
format_thread_json, format_thread_json,
"\"tags\": [", "\"tags\": [",
"\"%s\"", ", ", "\"%s\"", ", ",
@ -77,6 +99,14 @@ static const search_format_t format_json = {
"]\n", "]\n",
}; };
static void
format_item_id_text (unused (const void *ctx),
const char *item_type,
const char *item_id)
{
printf ("%s%s", item_type, item_id);
}
static void static void
format_thread_text (const void *ctx, format_thread_text (const void *ctx,
const char *thread_id, const char *thread_id,
@ -95,6 +125,19 @@ format_thread_text (const void *ctx,
subject); subject);
} }
static void
format_item_id_json (const void *ctx,
unused (const char *item_type),
const char *item_id)
{
void *ctx_quote = talloc_new (ctx);
printf ("%s", json_quote_str (ctx_quote, item_id));
talloc_free (ctx_quote);
}
static void static void
format_thread_json (const void *ctx, format_thread_json (const void *ctx,
const char *thread_id, const char *thread_id,
@ -126,7 +169,8 @@ static int
do_search_threads (const void *ctx, do_search_threads (const void *ctx,
const search_format_t *format, const search_format_t *format,
notmuch_query_t *query, notmuch_query_t *query,
notmuch_sort_t sort) notmuch_sort_t sort,
output_t output)
{ {
notmuch_thread_t *thread; notmuch_thread_t *thread;
notmuch_threads_t *threads; notmuch_threads_t *threads;
@ -134,8 +178,6 @@ do_search_threads (const void *ctx,
time_t date; time_t date;
int first_thread = 1; int first_thread = 1;
fputs (format->results_start, stdout);
threads = notmuch_query_search_threads (query); threads = notmuch_query_search_threads (query);
if (threads == NULL) if (threads == NULL)
return 1; return 1;
@ -147,18 +189,22 @@ do_search_threads (const void *ctx,
int first_tag = 1; int first_tag = 1;
if (! first_thread) if (! first_thread)
fputs (format->thread_sep, stdout); fputs (format->item_sep, stdout);
thread = notmuch_threads_get (threads); thread = notmuch_threads_get (threads);
if (output == OUTPUT_THREADS) {
format->item_id (ctx, "thread:",
notmuch_thread_get_thread_id (thread));
} else { /* output == OUTPUT_SUMMARY */
fputs (format->item_start, stdout);
if (sort == NOTMUCH_SORT_OLDEST_FIRST) if (sort == NOTMUCH_SORT_OLDEST_FIRST)
date = notmuch_thread_get_oldest_date (thread); date = notmuch_thread_get_oldest_date (thread);
else else
date = notmuch_thread_get_newest_date (thread); date = notmuch_thread_get_newest_date (thread);
fputs (format->thread_start, stdout); format->thread_summary (ctx,
format->thread (ctx,
notmuch_thread_get_thread_id (thread), notmuch_thread_get_thread_id (thread),
date, date,
notmuch_thread_get_matched_messages (thread), notmuch_thread_get_matched_messages (thread),
@ -179,14 +225,94 @@ do_search_threads (const void *ctx,
} }
fputs (format->tag_end, stdout); fputs (format->tag_end, stdout);
fputs (format->thread_end, stdout);
fputs (format->item_end, stdout);
}
first_thread = 0; first_thread = 0;
notmuch_thread_destroy (thread); notmuch_thread_destroy (thread);
} }
fputs (format->results_end, stdout); return 0;
}
static int
do_search_messages (const void *ctx,
const search_format_t *format,
notmuch_query_t *query)
{
notmuch_message_t *message;
notmuch_messages_t *messages;
int first_message = 1;
messages = notmuch_query_search_messages (query);
if (messages == NULL)
return 1;
for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages))
{
message = notmuch_messages_get (messages);
if (! first_message)
fputs (format->item_sep, stdout);
format->item_id (ctx, "id:", notmuch_message_get_message_id (message));
first_message = 0;
notmuch_message_destroy (message);
}
notmuch_messages_destroy (messages);
return 0;
}
static int
do_search_tags (const void *ctx,
notmuch_database_t *notmuch,
const search_format_t *format,
notmuch_query_t *query)
{
notmuch_messages_t *messages = NULL;
notmuch_tags_t *tags;
const char *tag;
int first_tag = 1;
/* Special-case query of "*" for better performance. */
if (strcmp (notmuch_query_get_query_string (query), "*") == 0) {
tags = notmuch_database_get_all_tags (notmuch);
} else {
messages = notmuch_query_search_messages (query);
if (messages == NULL)
return 1;
tags = notmuch_messages_collect_tags (messages);
}
if (tags == NULL)
return 1;
for (;
notmuch_tags_valid (tags);
notmuch_tags_move_to_next (tags))
{
tag = notmuch_tags_get (tags);
if (! first_tag)
fputs (format->item_sep, stdout);
format->item_id (ctx, "", tag);
first_tag = 0;
}
notmuch_tags_destroy (tags);
if (messages)
notmuch_messages_destroy (messages);
return 0; return 0;
} }
@ -202,6 +328,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST; notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;
const search_format_t *format = &format_text; const search_format_t *format = &format_text;
int i, ret; int i, ret;
output_t output = OUTPUT_SUMMARY;
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) {
@ -228,6 +355,20 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
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], "--output=") == 0) {
opt = argv[i] + sizeof ("--output=") - 1;
if (strcmp (opt, "summary") == 0) {
output = OUTPUT_SUMMARY;
} else if (strcmp (opt, "threads") == 0) {
output = OUTPUT_THREADS;
} else if (strcmp (opt, "messages") == 0) {
output = OUTPUT_MESSAGES;
} else if (strcmp (opt, "tags") == 0) {
output = OUTPUT_TAGS;
} else {
fprintf (stderr, "Invalid value for --output: %s\n", opt);
return 1;
}
} else { } else {
fprintf (stderr, "Unrecognized option: %s\n", argv[i]); fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
return 1; return 1;
@ -264,7 +405,23 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
notmuch_query_set_sort (query, sort); notmuch_query_set_sort (query, sort);
ret = do_search_threads (ctx, format, query, sort); fputs (format->results_start, stdout);
switch (output) {
default:
case OUTPUT_SUMMARY:
case OUTPUT_THREADS:
ret = do_search_threads (ctx, format, query, sort, output);
break;
case OUTPUT_MESSAGES:
ret = do_search_messages (ctx, format, query);
break;
case OUTPUT_TAGS:
ret = do_search_tags (ctx, notmuch, format, query);
break;
}
fputs (format->results_end, stdout);
notmuch_query_destroy (query); notmuch_query_destroy (query);
notmuch_database_close (notmuch); notmuch_database_close (notmuch);

View file

@ -150,6 +150,46 @@ include
Presents the results in either JSON or plain-text (default). Presents the results in either JSON or plain-text (default).
.RE .RE
.RS 4
.TP 4
.B \-\-output=(summary|threads|messages|tags)
.RS 4
.TP 4
.B summary
Output a summary of each thread with any message matching the search
terms. The summary includes the thread ID, date, the number of
messages in the thread (both the number matched and the total number),
the authors of the thread and the subject.
.RE
.RS 4
.TP 4
.B threads
Output the thread IDs of all threads with any message matching the
search terms, either one per line (--format=text) or as a JSON array
(--format=json).
.RE
.RS 4
.TP 4
.B messages
Output the message IDs of all messages matching the search terms,
either one per line (--format=text) or as a JSON array
(--format=json).
.RE
.RS 4
.TP 4
.B tags
Output all tags that appear on any message matching the search terms,
either one per line (--format=text) or as a JSON array
(--format=json).
.RE
.RE
.RS 4 .RS 4
.TP 4 .TP 4
.BR \-\-sort= ( newest\-first | oldest\-first ) .BR \-\-sort= ( newest\-first | oldest\-first )
@ -214,7 +254,6 @@ will be delimited by easily-parsed markers. Each marker consists of a
Control-L character (ASCII decimal 12), the name of the marker, and Control-L character (ASCII decimal 12), the name of the marker, and
then either an opening or closing brace, ('{' or '}'), to either open then either an opening or closing brace, ('{' or '}'), to either open
or close the component. or close the component.
.RE .RE
.RS 4 .RS 4
.TP 4 .TP 4
@ -240,7 +279,6 @@ beginning with "From " (preceded by zero or more '>' characters) have
an additional '>' character added. This reversible escaping an additional '>' character added. This reversible escaping
is termed "mboxrd" format and described in detail here: is termed "mboxrd" format and described in detail here:
http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html
.RE .RE
A common use of A common use of
.B notmuch show .B notmuch show

View file

@ -165,6 +165,33 @@ command_t commands[] = {
"\t\tPresents the results in either JSON or\n" "\t\tPresents the results in either JSON or\n"
"\t\tplain-text (default)\n" "\t\tplain-text (default)\n"
"\n" "\n"
"\t--output=(summary|threads|messages|tags)\n"
"\n"
"\t\tsummary (default)\n"
"\n"
"\t\tOutput a summary of each thread with any message matching the\n"
"\t\tsearch terms. The summary includes the thread ID, date, the\n"
"\t\tnumber of messages in the thread (both the number matched and\n"
"\t\tthe total number), the authors of the thread and the subject.\n"
"\n"
"\t\tthreads\n"
"\n"
"\t\tOutput the thread IDs of all threads with any message matching\n"
"\t\tthe search terms, either one per line (--format=text) or as a\n"
"\t\tJSON array (--format=json).\n"
"\n"
"\t\tmessages\n"
"\n"
"\t\tOutput the message IDs of all messages matching the search\n"
"\t\tterms, either one per line (--format=text) or as a JSON array\n"
"\t\t(--format=json).\n"
"\n"
"\t\ttags\n"
"\n"
"\t\tOutput all tags that appear on any message matching the search\n"
"\t\tterms, either one per line (--format=text) or as a JSON array\n"
"\t\t(--format=json).\n"
"\n"
"\t--sort=(newest-first|oldest-first)\n" "\t--sort=(newest-first|oldest-first)\n"
"\n" "\n"
"\t\tPresent results in either chronological order\n" "\t\tPresent results in either chronological order\n"