]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.c
cli: fix use of uninitialized variable in "notmuch reply"
[notmuch] / notmuch.c
index b392c1c207833bc98d60499896a4b57eb162c559..c0ce026a14ff904d29fbf53ca53b40975855bf52 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -32,11 +32,23 @@ typedef struct command {
     const char *documentation;
 } command_t;
 
+#define MAX_ALIAS_SUBSTITUTIONS 3
+
+typedef struct alias {
+    const char *name;
+    const char *substitutions[MAX_ALIAS_SUBSTITUTIONS];
+} alias_t;
+
+alias_t aliases[] = {
+    { "part", { "show", "--format=raw"}},
+    { "search-tags", {"search", "--output=tags", "*"}}
+};
+
 static int
 notmuch_help_command (void *ctx, int argc, char *argv[]);
 
 static const char search_terms_help[] =
-    "\tSeveral notmuch commands accept a comman syntax for search\n"
+    "\tSeveral notmuch commands accept a common syntax for search\n"
     "\tterms.\n"
     "\n"
     "\tThe search terms can consist of free-form text (and quoted\n"
@@ -44,6 +56,9 @@ static const char search_terms_help[] =
     "\tthe given terms/phrases in the body, the subject, or any of\n"
     "\tthe sender or recipient headers.\n"
     "\n"
+    "\tAs a special case, a search string consisting of exactly a\n"
+    "\tsingle asterisk (\"*\") will match all messages.\n"
+    "\n"
     "\tIn addition to free text, the following prefixes can be used\n"
     "\tto force terms to match against specific portions of an email,\n"
     "\t(where <brackets> indicate user-supplied values):\n"
@@ -55,6 +70,7 @@ static const char search_terms_help[] =
     "\t\ttag:<tag> (or is:<tag>)\n"
     "\t\tid:<message-id>\n"
     "\t\tthread:<thread-id>\n"
+    "\t\tfolder:<directory-path>\n"
     "\n"
     "\tThe from: prefix is used to match the name or address of\n"
     "\tthe sender of an email message.\n"
@@ -79,6 +95,11 @@ static const char search_terms_help[] =
     "\tmessages). These thread ID values can be seen in the first\n"
     "\tcolumn of output from \"notmuch search\".\n"
     "\n"
+    "\tThe folder: prefix can be used to search for email message\n"
+    "\tfiles that are contained within particular directories within\n"
+    "\tthe mail store. Only the directory components below the top-level\n"
+    "\tmail database path are available to be searched.\n"
+    "\n"
     "\tIn addition to individual terms, multiple terms can be\n"
     "\tcombined with Boolean operators (\"and\", \"or\", \"not\", etc.).\n"
     "\tEach term in the query will be implicitly connected by a\n"
@@ -106,7 +127,33 @@ static const char search_terms_help[] =
     "\n"
     "\t\t$(date +%%s -d 2009-10-01)..$(date +%%s)\n\n";
 
-command_t commands[] = {
+static const char hooks_help[] =
+    "\tHooks are scripts (or arbitrary executables or symlinks to such) that\n"
+    "\tnotmuch invokes before and after certain actions. These scripts reside\n"
+    "\tin the .notmuch/hooks directory within the database directory and must\n"
+    "\thave executable permissions.\n"
+    "\n"
+    "\tThe currently available hooks are described below.\n"
+    "\n"
+    "\tpre-new\n"
+    "\t\tThis hook is invoked by the new command before scanning or\n"
+    "\t\timporting new messages into the database. If this hook exits\n"
+    "\t\twith a non-zero status, notmuch will abort further processing\n"
+    "\t\tof the new command.\n"
+    "\n"
+    "\t\tTypically this hook is used for fetching or delivering new\n"
+    "\t\tmail to be imported into the database.\n"
+    "\n"
+    "\tpost-new\n"
+    "\t\tThis hook is invoked by the new command after new messages\n"
+    "\t\thave been imported into the database and initial tags have\n"
+    "\t\tbeen applied. The hook will not be run if there have been any\n"
+    "\t\terrors during the scan or import.\n"
+    "\n"
+    "\t\tTypically this hook is used to perform additional query-based\n"
+    "\t\ttagging on the imported messages.\n\n";
+
+static command_t commands[] = {
     { "setup", notmuch_setup_command,
       NULL,
       "Interactively setup notmuch for first use.",
@@ -123,7 +170,7 @@ command_t commands[] = {
       "\tInvoking notmuch with no command argument will run setup if\n"
       "\tthe setup command has not previously been completed." },
     { "new", notmuch_new_command,
-      "[--verbose]",
+      "[options...]",
       "Find and import new messages to the notmuch database.",
       "\tScans all sub-directories of the mail directory, performing\n"
       "\tfull-text indexing on new messages that are found. Each new\n"
@@ -138,8 +185,15 @@ command_t commands[] = {
       "\tis delivered and you wish to incorporate it into the database.\n"
       "\tThese subsequent runs will be much quicker than the initial run.\n"
       "\n"
+      "\tThe new command supports hooks. See \"notmuch help hooks\" for\n"
+      "\tmore details on hooks.\n"
+      "\n"
       "\tSupported options for new include:\n"
       "\n"
+      "\t--no-hooks\n"
+      "\n"
+      "\t\tPrevent hooks from being run.\n"
+      "\n"
       "\t--verbose\n"
       "\n"
       "\t\tVerbose operation. Shows paths of message files as\n"
@@ -162,12 +216,54 @@ command_t commands[] = {
       "\t\tPresents the results in either JSON or\n"
       "\t\tplain-text (default)\n"
       "\n"
+      "\t--output=(summary|threads|messages|files|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\tfiles\n"
+      "\n"
+      "\t\tOutput the filenames 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"
       "\n"
       "\t\tPresent results in either chronological order\n"
       "\t\t(oldest-first) or reverse chronological order\n"
       "\t\t(newest-first), which is the default.\n"
       "\n"
+      "\t--offset=[-]N\n"
+      "\n"
+      "\t\tSkip displaying the first N results. With the leading '-',\n"
+      "\t\tstart at the Nth result from the end.\n"
+      "\n"
+      "\t--limit=N\n"
+      "\n"
+      "\t\tLimit the number of displayed results to N.\n"
+      "\n"
       "\tSee \"notmuch help search-terms\" for details of the search\n"
       "\tterms syntax." },
     { "show", notmuch_show_command,
@@ -186,23 +282,76 @@ command_t commands[] = {
       "\t\tall messages in the same thread as any matched\n"
       "\t\tmessage will be displayed.\n"
       "\n"
-      "\t--format=(json|text)\n"
+      "\t--format=(text|json|mbox|raw)\n"
       "\n"
-      "\t\ttext\t(default)\n"
+      "\t\ttext (default for messages)\n"
       "\n"
-      "\t\tThe plain-text has all text-content MIME parts decoded.\n"
-      "\t\tVarious components in the output, ('message', 'header',\n"
+      "\t\tThe default plain-text format has all text-content MIME parts\n"
+      "\t\tdecoded. Various components in the output, ('message', 'header',\n"
       "\t\t'body', 'attachment', and MIME 'part') are delimited by\n"
       "\t\teasily-parsed markers. Each marker consists of a Control-L\n"
       "\t\tcharacter (ASCII decimal 12), the name of the marker, and\n"
       "\t\tthen either an opening or closing brace, '{' or '}' to\n"
-      "\t\teither open or close the component.\n"
+      "\t\teither open or close the component. For a multipart MIME\n"
+      "\t\tmessage, these parts will be nested.\n"
       "\n"
       "\t\tjson\n"
       "\n"
-      "\t\tFormat output as Javascript Object Notation (JSON).\n"
-      "\t\tJSON output always includes all messages in a matching,\n"
-      "\t\tthread i.e. '--output=json' implies '--entire-thread'\n"
+      "\t\tThe output is formatted with Javascript Object Notation\n"
+      "\t\t(JSON). This format is more robust than the text format\n"
+      "\t\tfor automated processing. The nested structure of multipart\n"
+      "\t\tMIME messages is reflected in nested JSON output. JSON\n"
+      "\t\toutput always includes all messages in a matching thread;\n"
+      "\t\tin effect '--format=json' implies '--entire-thread'\n"
+      "\n"
+      "\t\tmbox\n"
+      "\n"
+      "\t\tAll matching messages are output in the traditional, Unix\n"
+      "\t\tmbox format with each message being prefixed by a line\n"
+      "\t\tbeginning with 'From ' and a blank line separating each\n"
+      "\t\tmessage. Lines in the message content beginning with 'From '\n"
+      "\t\t(preceded by zero or more '>' characters) have an additional\n"
+      "\t\t'>' character added. This reversible escaping is termed\n"
+      "\t\t\"mboxrd\" format and described in detail here:\n"
+      "\n"
+      "\t\thttp://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html\n"
+      "\n"
+      "\t\traw (default for a single part, see --part)\n"
+      "\n"
+      "\t\tFor a message, the original, raw content of the email\n"
+      "\t\tmessage is output. Consumers of this format should\n"
+      "\t\texpect to implement MIME decoding and similar functions.\n"
+      "\n"
+      "\t\tFor a single part (--part) the raw part content is output\n"
+      "\t\tafter performing any necessary MIME decoding.\n"
+      "\n"
+      "\t\tThe raw format must only be used with search terms matching\n"
+      "\t\tsingle message.\n"
+      "\n"
+      "\t--part=N\n"
+      "\n"
+      "\t\tOutput the single decoded MIME part N of a single message.\n"
+      "\t\tThe search terms must match only a single message.\n"
+      "\t\tMessage parts are numbered in a depth-first walk of the\n"
+      "\t\tmessage MIME structure, and are identified in the 'json' or\n"
+      "\t\t'text' output formats.\n"
+      "\n"
+      "\t--verify\n"
+      "\n"
+      "\t\tCompute and report the validity of any MIME cryptographic\n"
+      "\t\tsignatures found in the selected content (ie.\n"
+      "\t\t\"multipart/signed\" parts). Status of the signature will be\n"
+      "\t\treported (currently only supported with --format=json) and\n"
+      "\t\tthe multipart/signed part will be replaced by the signed data.\n"
+      "\n"
+      "\t--decrypt\n"
+      "\n"
+      "\t\tDecrypt any MIME encrypted parts found in the selected content\n"
+      "\t\t(ie. \"multipart/encrypted\" parts). Status of the decryption\n"
+      "\t\twill be reported (currently only supported with --format=json)\n"
+      "\t\tand the multipart/encrypted part will be replaced by the\n"
+      "\t\tdecrypted content.\n"
+      "\n"
       "\n"
       "\tA common use of \"notmuch show\" is to display a single\n"
       "\tthread of email messages. For this, use a search term of\n"
@@ -212,16 +361,27 @@ command_t commands[] = {
       "\tSee \"notmuch help search-terms\" for details of the search\n"
       "\tterms syntax." },
     { "count", notmuch_count_command,
-      "<search-terms> [...]",
+      "[options...] <search-terms> [...]",
       "Count messages matching the search terms.",
-      "\tThe number of matching messages is output to stdout.\n"
+      "\tThe number of matching messages (or threads) is output to stdout.\n"
+      "\n"
+      "\tWith no search terms, a count of all messages (or threads) in\n"
+      "\tthe database will be displayed.\n"
+      "\n"
+      "\tSupported options for count include:\n"
+      "\n"
+      "\t--output=(messages|threads)\n"
+      "\n"
+      "\t\tmessages (default)\n"
       "\n"
-      "\tA common use of \"notmuch count\" is to display the count\n"
-      "\tof messages matching both a specific tag and either inbox\n"
-      "\tor unread\n"
+      "\t\tOutput the number of matching messages.\n"
+      "\n"
+      "\t\tthreads\n"
+      "\n"
+      "\t\tOutput the number of matching threads.\n"
       "\n"
       "\tSee \"notmuch help search-terms\" for details of the search\n"
-      "\t\tterms syntax." },
+      "\tterms syntax." },
     { "reply", notmuch_reply_command,
       "[options...] <search-terms> [...]",
       "Construct a reply template for a set of messages.",
@@ -269,31 +429,60 @@ command_t commands[] = {
       "\tSee \"notmuch help search-terms\" for details of the search\n"
       "\tterms syntax." },
     { "dump", notmuch_dump_command,
-      "[<filename>]",
+      "[<filename>] [--] [<search-terms>]",
       "Create a plain-text dump of the tags for each message.",
       "\tOutput is to the given filename, if any, or to stdout.\n"
+      "\tNote that using the filename argument is deprecated.\n"
+      "\n"
       "\tThese tags are the only data in the notmuch database\n"
       "\tthat can't be recreated from the messages themselves.\n"
       "\tThe output of notmuch dump is therefore the only\n"
       "\tcritical thing to backup (and much more friendly to\n"
-      "\tincremental backup than the native database files.)" },
+      "\tincremental backup than the native database files.)\n" 
+      "\n"
+      "\tWith no search terms, a dump of all messages in the\n"
+      "\tdatabase will be generated. A \"--\" argument instructs\n"
+      "\tnotmuch that the remaining arguments are search terms.\n"
+      "\n"
+      "\tSee \"notmuch help search-terms\" for the search-term syntax.\n"      
+ },
     { "restore", notmuch_restore_command,
-      "<filename>",
+      "[--accumulate] [<filename>]",
       "Restore the tags from the given dump file (see 'dump').",
+      "\tInput is read from the given filename, if any, or from stdin.\n"
       "\tNote: The dump file format is specifically chosen to be\n"
       "\tcompatible with the format of files produced by sup-dump.\n"
       "\tSo if you've previously been using sup for mail, then the\n"
       "\t\"notmuch restore\" command provides you a way to import\n"
-      "\tall of your tags (or labels as sup calls them)." },
-    { "search-tags", notmuch_search_tags_command,
-      "[<search-terms> [...] ]",
-      "List all tags found in the database or matching messages.",
-      "\tRun this command without any search-term(s) to obtain a list\n"
-      "\tof all tags found in the database. If you provide one or more\n"
-      "\tsearch-terms as argument(s) then the resulting list will\n"
-      "\tcontain tags only from messages that match the search-term(s).\n"
-      "\n"
-      "\tIn both cases the list will be alphabetically sorted." },
+      "\tall of your tags (or labels as sup calls them).\n"
+      "\tThe --accumulate switch causes the union of the existing and new\n"
+      "\ttags to be applied, instead of replacing each message's tags as\n"
+      "\tthey are read in from the dump file."},
+    { "config", notmuch_config_command,
+      "[get|set] <section>.<item> [value ...]",
+      "Get or set settings in the notmuch configuration file.",
+      "    config get <section>.<item>\n"
+      "\n"
+      "\tThe value of the specified configuration item is printed\n"
+      "\tto stdout. If the item has multiple values, each value\n"
+      "\tis separated by a newline character.\n"
+      "\n"
+      "\tAvailable configuration items include at least\n"
+      "\n"
+      "\t\tdatabase.path\n"
+      "\t\tuser.name\n"
+      "\t\tuser.primary_email\n"
+      "\t\tuser.other_email\n"
+      "\t\tnew.tags\n"
+      "\n"
+      "    config set <section>.<item> [value ...]\n"
+      "\n"
+      "\tThe specified configuration item is set to the given value.\n"
+      "\tTo specify a multiple-value item, provide each value as\n"
+      "\ta separate command-line argument.\n"
+      "\n"
+      "\tIf no values are provided, the specified configuration item\n"
+      "\twill be removed from the configuration file." },
     { "help", notmuch_help_command,
       "[<command>]",
       "This message, or more detailed help for the named command.",
@@ -334,6 +523,8 @@ notmuch_help_command (unused (void *ctx), int argc, char *argv[])
     command_t *command;
     unsigned int i;
 
+    argc--; argv++; /* Ignore "help" */
+
     if (argc == 0) {
        printf ("The notmuch mail system.\n\n");
        usage (stdout);
@@ -371,6 +562,10 @@ notmuch_help_command (unused (void *ctx), int argc, char *argv[])
        printf ("\n");
        printf (search_terms_help);
        return 0;
+    } else if (strcmp (argv[0], "hooks") == 0) {
+       printf ("Help for <%s>\n\n", argv[0]);
+       printf (hooks_help);
+       return 0;
     }
 
     fprintf (stderr,
@@ -448,28 +643,67 @@ main (int argc, char *argv[])
 {
     void *local;
     command_t *command;
-    unsigned int i;
+    alias_t *alias;
+    unsigned int i, j;
+    const char **argv_local;
+
+    talloc_enable_null_tracking ();
 
     local = talloc_new (NULL);
 
     g_mime_init (0);
+    g_type_init ();
 
     if (argc == 1)
        return notmuch (local);
 
     if (STRNCMP_LITERAL (argv[1], "--help") == 0)
-       return notmuch_help_command (NULL, 0, NULL);
+       return notmuch_help_command (NULL, argc - 1, &argv[1]);
 
     if (STRNCMP_LITERAL (argv[1], "--version") == 0) {
-       printf ("notmuch version " STRINGIFY(NOTMUCH_VERSION) "\n");
+       printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
        return 0;
     }
 
+    for (i = 0; i < ARRAY_SIZE (aliases); i++) {
+       alias = &aliases[i];
+
+       if (strcmp (argv[1], alias->name) == 0)
+       {
+           int substitutions;
+
+           argv_local = talloc_size (local, sizeof (char *) *
+                                     (argc + MAX_ALIAS_SUBSTITUTIONS - 1));
+           if (argv_local == NULL) {
+               fprintf (stderr, "Out of memory.\n");
+               return 1;
+           }
+
+           /* Copy all substution arguments from the alias. */
+           argv_local[0] = argv[0];
+           for (j = 0; j < MAX_ALIAS_SUBSTITUTIONS; j++) {
+               if (alias->substitutions[j] == NULL)
+                   break;
+               argv_local[j+1] = alias->substitutions[j];
+           }
+           substitutions = j;
+
+           /* And copy all original arguments (skipping the argument
+            * that matched the alias of course. */
+           for (j = 2; j < (unsigned) argc; j++) {
+               argv_local[substitutions+j-1] = argv[j];
+           }
+
+           argc += substitutions - 1;
+           argv = (char **) argv_local;
+       }
+    }
+
     for (i = 0; i < ARRAY_SIZE (commands); i++) {
        command = &commands[i];
 
        if (strcmp (argv[1], command->name) == 0)
-           return (command->function) (local, argc - 2, &argv[2]);
+           return (command->function) (local, argc - 1, &argv[1]);
     }
 
     fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",