python: refactor the python bindings
[notmuch] / notmuch.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  * Copyright © 2009 Keith Packard
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see http://www.gnu.org/licenses/ .
18  *
19  * Authors: Carl Worth <cworth@cworth.org>
20  *          Keith Packard <keithp@keithp.com>
21  */
22
23 #include "notmuch-client.h"
24
25 typedef int (*command_function_t) (void *ctx, int argc, char *argv[]);
26
27 typedef struct command {
28     const char *name;
29     command_function_t function;
30     const char *arguments;
31     const char *summary;
32 } command_t;
33
34 #define MAX_ALIAS_SUBSTITUTIONS 3
35
36 typedef struct alias {
37     const char *name;
38     const char *substitutions[MAX_ALIAS_SUBSTITUTIONS];
39 } alias_t;
40
41 alias_t aliases[] = {
42     { "part", { "show", "--format=raw"}},
43     { "search-tags", {"search", "--output=tags", "*"}}
44 };
45
46 static int
47 notmuch_help_command (void *ctx, int argc, char *argv[]);
48
49 static command_t commands[] = {
50     { "setup", notmuch_setup_command,
51       NULL,
52       "Interactively setup notmuch for first use." },
53     { "new", notmuch_new_command,
54       "[options...]",
55       "Find and import new messages to the notmuch database." },
56     { "search", notmuch_search_command,
57       "[options...] <search-terms> [...]",
58       "Search for messages matching the given search terms." },
59     { "show", notmuch_show_command,
60       "<search-terms> [...]",
61       "Show all messages matching the search terms." },
62     { "count", notmuch_count_command,
63       "[options...] <search-terms> [...]",
64       "Count messages matching the search terms." },
65     { "reply", notmuch_reply_command,
66       "[options...] <search-terms> [...]",
67       "Construct a reply template for a set of messages." },
68     { "tag", notmuch_tag_command,
69       "+<tag>|-<tag> [...] [--] <search-terms> [...]" ,
70       "Add/remove tags for all messages matching the search terms." },
71     { "dump", notmuch_dump_command,
72       "[<filename>] [--] [<search-terms>]",
73       "Create a plain-text dump of the tags for each message." },
74     { "restore", notmuch_restore_command,
75       "[--accumulate] [<filename>]",
76       "Restore the tags from the given dump file (see 'dump')." },
77     { "config", notmuch_config_command,
78       "[get|set] <section>.<item> [value ...]",
79       "Get or set settings in the notmuch configuration file." },
80     { "help", notmuch_help_command,
81       "[<command>]",
82       "This message, or more detailed help for the named command." }
83 };
84
85 static void
86 usage (FILE *out)
87 {
88     command_t *command;
89     unsigned int i;
90
91     fprintf (out,
92              "Usage: notmuch --help\n"
93              "       notmuch --version\n"
94              "       notmuch <command> [args...]\n");
95     fprintf (out, "\n");
96     fprintf (out, "The available commands are as follows:\n");
97     fprintf (out, "\n");
98
99     for (i = 0; i < ARRAY_SIZE (commands); i++) {
100         command = &commands[i];
101
102         fprintf (out, "  %-11s  %s\n",
103                  command->name, command->summary);
104     }
105
106     fprintf (out, "\n");
107     fprintf (out,
108     "Use \"notmuch help <command>\" for more details on each command\n"
109     "and \"notmuch help search-terms\" for the common search-terms syntax.\n\n");
110 }
111
112 static void
113 exec_man (const char *page)
114 {
115     if (execlp ("man", "man", page, (char *) NULL)) {
116         perror ("exec man");
117         exit (1);
118     }
119 }
120
121 static int
122 notmuch_help_command (void *ctx, int argc, char *argv[])
123 {
124     command_t *command;
125     unsigned int i;
126
127     argc--; argv++; /* Ignore "help" */
128
129     if (argc == 0) {
130         printf ("The notmuch mail system.\n\n");
131         usage (stdout);
132         return 0;
133     }
134
135     if (strcmp (argv[0], "help") == 0) {
136         printf ("The notmuch help system.\n\n"
137                 "\tNotmuch uses the man command to display help. In case\n"
138                 "\tof difficulties check that MANPATH includes the pages\n"
139                 "\tinstalled by notmuch.\n\n"
140                 "\tTry \"notmuch help\" for a list of topics.\n");
141         return 0;
142     }
143
144     for (i = 0; i < ARRAY_SIZE (commands); i++) {
145         command = &commands[i];
146
147         if (strcmp (argv[0], command->name) == 0) {
148             char *page = talloc_asprintf (ctx, "notmuch-%s", command->name);
149             exec_man (page);
150         }
151     }
152
153     if (strcmp (argv[0], "search-terms") == 0) {
154         exec_man ("notmuch-search-terms");
155     } else if (strcmp (argv[0], "hooks") == 0) {
156         exec_man ("notmuch-hooks");
157     }
158
159     fprintf (stderr,
160              "\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
161              argv[0]);
162     return 1;
163 }
164
165 /* Handle the case of "notmuch" being invoked with no command
166  * argument. For now we just call notmuch_setup_command, but we plan
167  * to be more clever about this in the future.
168  */
169 static int
170 notmuch (void *ctx)
171 {
172     notmuch_config_t *config;
173     notmuch_bool_t is_new;
174     char *db_path;
175     struct stat st;
176
177     config = notmuch_config_open (ctx, NULL, &is_new);
178
179     /* If the user has never configured notmuch, then run
180      * notmuch_setup_command which will give a nice welcome message,
181      * and interactively guide the user through the configuration. */
182     if (is_new) {
183         notmuch_config_close (config);
184         return notmuch_setup_command (ctx, 0, NULL);
185     }
186
187     /* Notmuch is already configured, but is there a database? */
188     db_path = talloc_asprintf (ctx, "%s/%s",
189                                notmuch_config_get_database_path (config),
190                                ".notmuch");
191     if (stat (db_path, &st)) {
192         notmuch_config_close (config);
193         if (errno != ENOENT) {
194             fprintf (stderr, "Error looking for notmuch database at %s: %s\n",
195                      db_path, strerror (errno));
196             return 1;
197         }
198         printf ("Notmuch is configured, but there's not yet a database at\n\n\t%s\n\n",
199                 db_path);
200         printf ("You probably want to run \"notmuch new\" now to create that database.\n\n"
201                 "Note that the first run of \"notmuch new\" can take a very long time\n"
202                 "and that the resulting database will use roughly the same amount of\n"
203                 "storage space as the email being indexed.\n\n");
204         return 0;
205     }
206
207     printf ("Notmuch is configured and appears to have a database. Excellent!\n\n"
208             "At this point you can start exploring the functionality of notmuch by\n"
209             "using commands such as:\n\n"
210             "\tnotmuch search tag:inbox\n\n"
211             "\tnotmuch search to:\"%s\"\n\n"
212             "\tnotmuch search from:\"%s\"\n\n"
213             "\tnotmuch search subject:\"my favorite things\"\n\n"
214             "See \"notmuch help search\" for more details.\n\n"
215             "You can also use \"notmuch show\" with any of the thread IDs resulting\n"
216             "from a search. Finally, you may want to explore using a more sophisticated\n"
217             "interface to notmuch such as the emacs interface implemented in notmuch.el\n"
218             "or any other interface described at http://notmuchmail.org\n\n"
219             "And don't forget to run \"notmuch new\" whenever new mail arrives.\n\n"
220             "Have fun, and may your inbox never have much mail.\n\n",
221             notmuch_config_get_user_name (config),
222             notmuch_config_get_user_primary_email (config));
223
224     notmuch_config_close (config);
225
226     return 0;
227 }
228
229 int
230 main (int argc, char *argv[])
231 {
232     void *local;
233     command_t *command;
234     alias_t *alias;
235     unsigned int i, j;
236     const char **argv_local;
237
238     talloc_enable_null_tracking ();
239
240     local = talloc_new (NULL);
241
242     g_mime_init (0);
243     g_type_init ();
244
245     if (argc == 1)
246         return notmuch (local);
247
248     if (STRNCMP_LITERAL (argv[1], "--help") == 0)
249         return notmuch_help_command (NULL, argc - 1, &argv[1]);
250
251     if (STRNCMP_LITERAL (argv[1], "--version") == 0) {
252         printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
253         return 0;
254     }
255
256     for (i = 0; i < ARRAY_SIZE (aliases); i++) {
257         alias = &aliases[i];
258
259         if (strcmp (argv[1], alias->name) == 0)
260         {
261             int substitutions;
262
263             argv_local = talloc_size (local, sizeof (char *) *
264                                       (argc + MAX_ALIAS_SUBSTITUTIONS - 1));
265             if (argv_local == NULL) {
266                 fprintf (stderr, "Out of memory.\n");
267                 return 1;
268             }
269
270             /* Copy all substution arguments from the alias. */
271             argv_local[0] = argv[0];
272             for (j = 0; j < MAX_ALIAS_SUBSTITUTIONS; j++) {
273                 if (alias->substitutions[j] == NULL)
274                     break;
275                 argv_local[j+1] = alias->substitutions[j];
276             }
277             substitutions = j;
278
279             /* And copy all original arguments (skipping the argument
280              * that matched the alias of course. */
281             for (j = 2; j < (unsigned) argc; j++) {
282                 argv_local[substitutions+j-1] = argv[j];
283             }
284
285             argc += substitutions - 1;
286             argv = (char **) argv_local;
287         }
288     }
289
290     for (i = 0; i < ARRAY_SIZE (commands); i++) {
291         command = &commands[i];
292
293         if (strcmp (argv[1], command->name) == 0)
294             return (command->function) (local, argc - 1, &argv[1]);
295     }
296
297     fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
298              argv[1]);
299
300     talloc_free (local);
301
302     return 1;
303 }