From 6539ae4bd7a0ac0ce440bef5cc7fc2b12fbed93f Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Wed, 5 Jan 2011 20:35:50 +0200 Subject: [PATCH] * initial plumbing and some documentation for xml/json/sexp output through --format= parameter. also, add --format=links, --format=xquery --- man/mu-find.1 | 100 ++++++++++++++++++++++++++++++++++++---------- src/mu-cmd-find.c | 93 +++++++++++++++++++++++++++++++++++++----- src/mu-config.c | 42 +++++++++---------- src/mu-config.h | 14 ++++++- src/mu-output.c | 30 +++++++++++++- src/mu-output.h | 35 ++++++++++++++++ 6 files changed, 258 insertions(+), 56 deletions(-) diff --git a/man/mu-find.1 b/man/mu-find.1 index ee79ed4e..8a531f4d 100644 --- a/man/mu-find.1 +++ b/man/mu-find.1 @@ -1,4 +1,4 @@ -.TH MU FIND 1 "November 2010" "User Manuals" +.TH MU FIND 1 "January 2011" "User Manuals" .SH NAME @@ -12,9 +12,9 @@ database .SH DESCRIPTION -\fBmu find\fR is the \fBmu\fR sub-command for searching e-mail message that +\fBmu find\fR is the \fBmu\fR command for searching e-mail message that were stored earlier using -\fBmu index(1)\bR. +\fBmu index(1)\fR. .SH SEARCHING MAIL @@ -25,7 +25,18 @@ search pattern. For example: $ mu find subject:snow from:john .fi -would find all messages from John with 'snow' in the subject field. +would find all messages from John with 'snow' in the subject field, something +like: + +.nf + 2009-03-05 17:57:33 EET Lucia running in the snow + 2009-03-05 18:38:24 EET Marius Re: running in the snow +.fi + +Note, this the default, plain-text output, which is the default, so you don't +have to use \fB--format=plain\fR. For other types of output (such as symlinks, +XML, JSON or s-expressions), see the discussion in the \fBOPTIONS\fR-section +below about \fB--format\fR. The search pattern is taken as a command-line parameter. If the search parameter consists of multiple parts (as in the example) they are treated as @@ -104,6 +115,7 @@ extra discusion. First, the message flags field describes certain properties of the message, as listed in the following table: + .nf d,draft Draft Message f,flagged Flagged @@ -162,16 +174,21 @@ at 23:59. To get all messages between (inclusive) the 5th of May 2009 and the 2nd of June 2010, you could use: + .nf $ mu find date:20090505..20100602 .fi + Characters like ':', '/', '-' and single '.' are ignored, so the following is equivalent but more readable: + .nf $ mu find date:2009-05-05..2010-06-02 .fi + Precision is up to the minute and 24-hour notation for times is used, so another example would be: + .nf $ mu find date:2009-05-05/12:23..2010-06-02/17:18 .fi @@ -193,6 +210,7 @@ examples will explain this: Using this notation, you can for example match messages between two and three weeks old: + .nf $ mu find date:3w..2w .fi @@ -222,9 +240,11 @@ from), which will replace with the actual field in the output. Fields that are not known will be output as-is, allowing for some simple formatting. For example: + .nf $ mu find subject:snow --fields "d f s" .fi + would list the date, subject and sender of all messages with 'snow' in the their subject. @@ -278,15 +298,33 @@ choice, but for dates it may be more useful to sort in the opposite direction. .TP \fB\-\-xquery\fR -shows the Xapian query corresponding to your search terms. This is primarily -meant for for debugging purposes. .TP \fB\-k\fR, \fB\-\-summary\-len\fR=\fI\fR -output a summary based on up to \fI\len\fR lines of the message. The default is -.B 0 -, or no summary. +output a summary based on up to \fI\len\fR lines of the message. The default +is \fB0\fR: no summary at all. +.TP +\fB\-\-format\fR=\fIplain|links|xquery|xml|json|sexp\fR +output results in the specified format. + +The default is \fBplain\fR, i.e normal output with one line per message. + +\fBlinks\fR outputs the results as a maildir with symbolic links to the found +messages. This enables easy integration with mail-clients (see below for more +information). See \fB\-\-linksdir\fR and \fB\-\-clearlinks\fR below. + +\fBxml\fR formats the search results as XML. + +\fBjson\fR formats the search results as JSON (\fIJavascript Object +Notation\fR). + +\fBsexp\fR formats the search results as an s-expression as used in Lisp +programming environments. + +\fBxquery\fR shows the Xapian query corresponding to your search terms. This +is meant for for debugging purposes. + .TP \fB\-\-linksdir\fR \fR=\fI\fR and \fB\-c\fR, \fB\-\-clearlinks\fR output the results as a maildir with symbolic links to the found @@ -364,51 +402,69 @@ Find all messages with attachments: .TP \fBmutt\fR + For \fBmutt\fR you can use the following in your \fImuttrc\fR; pressing the F8 key will start a search, and F9 will take you to the results. .nf # mutt macros for mu -macro index "mu find -c -l ~/Maildir/search " \ +macro index "mu find -c -l ~/Maildir/search " \\ "mu find" -macro index "~/Maildir/search" \ - "display mu find results" +macro index "~/Maildir/search" \\ + "mu find results" .fi .TP \fBWanderlust\fR + If you use the Wanderlust e-mail client for \fBemacs\fR, the following definitions can be used; typing 'Q' will start a query. .nf -;; mu integration for Wanderlust -(defvar mu-wl-mu-program "mu") +(defvar mu-wl-mu-program "/usr/local/bin/mu") (defvar mu-wl-search-folder "search") (defun mu-wl-search () "search for messages with `mu', and jump to the results" - (interactive) - (let* ((muexpr (read-string "Find messages matching: ")) + (let* ((muexpr (read-string "Find messages matching: ")) (sfldr (concat elmo-maildir-folder-path "/" mu-wl-search-folder)) (cmdline (concat mu-wl-mu-program " find " - "--clearlinks --linksdir='" sfldr "' " - muexpr))) - (= 0 (shell-command cmdline)))) + "--clearlinks --format=links --linksdir='" sfldr "' " + muexpr)) + (rv (shell-command cmdline))) + (cond + ((= rv 0) (message "Query succeeded")) + ((= rv 2) (message "No matches found")) + (t (message "Error running query"))) + (= rv 0))) (defun mu-wl-search-and-goto () "search and jump to the folder with the results" (interactive) - - (if (mu-wl-search) + (when (mu-wl-search) (wl-summary-goto-folder-subr (concat "." mu-wl-search-folder) 'force-update nil nil t) - (message "Query failed"))) + (wl-summary-sort-by-date))) + +;; querying both in summary and folder +(define-key wl-summary-mode-map (kbd "Q") ;; => query + '(lambda()(interactive)(mu-wl-search-and-goto))) +(define-key wl-folder-mode-map (kbd "Q") ;; => query + '(lambda()(interactive)(mu-wl-search-and-goto))) + .fi + +.SH RETURN VALUE + +\fBmu\fR returns 0 for searches with at least one matching message, 2 for +searches that do not match anything. In case of errors 1 or any number greater +than 2 will be returned. + .SH BUGS Please report bugs if you find them: diff --git a/src/mu-cmd-find.c b/src/mu-cmd-find.c index 35e73662..2ab2f297 100644 --- a/src/mu-cmd-find.c +++ b/src/mu-cmd-find.c @@ -37,10 +37,46 @@ #include "mu-runtime.h" #include "mu-util.h" -#include "mu-util-db.h" #include "mu-cmd.h" #include "mu-output.h" + +enum _OutputFormat { + FORMAT_JSON, + FORMAT_LINKS, + FORMAT_PLAIN, + FORMAT_SEXP, + FORMAT_XML, + FORMAT_XQUERY, + + FORMAT_NONE +}; +typedef enum _OutputFormat OutputFormat; + +static OutputFormat +get_output_format (const char *formatstr) +{ + int i; + struct { + const char* name; + OutputFormat format; + } formats [] = { + {MU_CONFIG_FORMAT_JSON, FORMAT_JSON}, + {MU_CONFIG_FORMAT_LINKS, FORMAT_LINKS}, + {MU_CONFIG_FORMAT_PLAIN, FORMAT_PLAIN}, + {MU_CONFIG_FORMAT_SEXP, FORMAT_SEXP}, + {MU_CONFIG_FORMAT_XML, FORMAT_XML}, + {MU_CONFIG_FORMAT_XQUERY, FORMAT_XQUERY} + }; + + for (i = 0; i != G_N_ELEMENTS(formats); i++) + if (strcmp (formats[i].name, formatstr) == 0) + return formats[i].format; + + return FORMAT_NONE; +} + + static void update_warning (void) { @@ -55,7 +91,7 @@ print_xapian_query (MuQuery *xapian, const gchar *query) { char *querystr; GError *err; - + err = NULL; querystr = mu_query_as_string (xapian, query, &err); if (!querystr) { @@ -93,7 +129,7 @@ sort_field_from_string (const char* fieldstr) static gboolean run_query (MuQuery *xapian, const gchar *query, MuConfig *opts, - size_t *count) + OutputFormat format, size_t *count) { GError *err; MuMsgIter *iter; @@ -116,14 +152,29 @@ run_query (MuQuery *xapian, const gchar *query, MuConfig *opts, return FALSE; } - if (opts->linksdir) + switch (format) { + case FORMAT_LINKS: rv = mu_output_links (iter, opts->linksdir, opts->clearlinks, count); - else + break; + case FORMAT_PLAIN: rv = mu_output_plain (iter, opts->fields, opts->summary_len, count); - - + break; + case FORMAT_XML: + rv = mu_output_xml (iter, count); + break; + case FORMAT_JSON: + rv = mu_output_json (iter, count); + break; + case FORMAT_SEXP: + rv = mu_output_sexp (iter, count); + break; + default: + g_assert_not_reached (); + return FALSE; + } + if (count && *count == 0) g_warning ("no matches found"); @@ -136,6 +187,7 @@ run_query (MuQuery *xapian, const gchar *query, MuConfig *opts, static gboolean query_params_valid (MuConfig *opts) { + OutputFormat format; const gchar *xpath; if (opts->linksdir) @@ -144,6 +196,23 @@ query_params_valid (MuConfig *opts) return FALSE; } + format = get_output_format (opts->formatstr); + if (format == FORMAT_NONE) { + g_warning ("invalid output format %s", + opts->formatstr ? opts->formatstr : ""); + return FALSE; + } + + if (format == FORMAT_LINKS && !opts->linksdir) { + g_warning ("missing --linksdir argument"); + return FALSE; + } + + if (opts->linksdir && format != FORMAT_LINKS) { + g_warning ("--linksdir is only valid with --format=links"); + return FALSE; + } + xpath = mu_runtime_xapian_dir(); if (mu_util_check_dir (xpath, TRUE, FALSE)) @@ -261,12 +330,15 @@ mu_cmd_find (MuConfig *opts) gboolean rv; gchar *query; size_t count; + OutputFormat format; g_return_val_if_fail (opts, FALSE); g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_FIND, FALSE); if (!query_params_valid (opts)) return MU_EXITCODE_ERROR; + + format = get_output_format (opts->formatstr); xapian = get_query_obj (); if (!xapian) @@ -276,11 +348,11 @@ mu_cmd_find (MuConfig *opts) query = get_query (opts); if (!query) return MU_EXITCODE_ERROR; - - if (opts->xquery) + + if (format == FORMAT_XQUERY) rv = print_xapian_query (xapian, query); else - rv = run_query (xapian, query, opts, &count); + rv = run_query (xapian, query, opts, format, &count); mu_query_destroy (xapian); g_free (query); @@ -291,4 +363,3 @@ mu_cmd_find (MuConfig *opts) return (count == 0) ? MU_EXITCODE_NO_MATCHES : MU_EXITCODE_OK; } - diff --git a/src/mu-config.c b/src/mu-config.c index 1ad86a61..01462e70 100644 --- a/src/mu-config.c +++ b/src/mu-config.c @@ -49,15 +49,15 @@ config_options_group_mu (MuConfig *opts) GOptionGroup *og; GOptionEntry entries[] = { {"debug", 'd', 0, G_OPTION_ARG_NONE, &opts->debug, - "print debug output to standard error", NULL}, + "print debug output to standard error (false)", NULL}, {"quiet", 'q', 0, G_OPTION_ARG_NONE, &opts->quiet, - "don't give any progress information", NULL}, + "don't give any progress information (false)", NULL}, {"version", 'v', 0, G_OPTION_ARG_NONE, &opts->version, - "display version and copyright information", NULL}, + "display version and copyright information (false)", NULL}, {"muhome", 0, 0, G_OPTION_ARG_FILENAME, &opts->muhome, "specify an alternative mu directory", NULL}, {"log-stderr", 0, 0, G_OPTION_ARG_NONE, &opts->log_stderr, - "log to standard error", NULL}, + "log to standard error (false)", NULL}, {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &opts->params, "parameters", NULL}, {NULL, 0, 0, 0, NULL, NULL, NULL} @@ -94,16 +94,16 @@ config_options_group_index (MuConfig * opts) {"maildir", 'm', 0, G_OPTION_ARG_FILENAME, &opts->maildir, "top of the maildir", NULL}, {"reindex", 0, 0, G_OPTION_ARG_NONE, &opts->reindex, - "index even already indexed messages", NULL}, + "index even already indexed messages (false)", NULL}, {"rebuild", 0, 0, G_OPTION_ARG_NONE, &opts->rebuild, - "rebuild the database from scratch", NULL}, + "rebuild the database from scratch (false)", NULL}, {"autoupgrade", 0, 0, G_OPTION_ARG_NONE, &opts->autoupgrade, - "automatically upgrade the database with new mu versions", + "auto-upgrade the database with new mu versions (false)", NULL}, {"nocleanup", 0, 0, G_OPTION_ARG_NONE, &opts->nocleanup, - "don't clean up the database after indexing", NULL}, + "don't clean up the database after indexing (false)", NULL}, {"xbatchsize", 0, 0, G_OPTION_ARG_INT, &opts->xbatchsize, - "set a custom batchsize for committing to xapian or 0 for default", NULL}, + "set transaction batchsize for xapian commits (0)", NULL}, {NULL, 0, 0, 0, NULL, NULL, NULL} }; @@ -129,6 +129,9 @@ set_group_find_defaults (MuConfig *opts) } } + if (!opts->formatstr) /* by default, use plain output */ + opts->formatstr = MU_CONFIG_FORMAT_PLAIN; + if (opts->linksdir) { gchar *old = opts->linksdir; opts->linksdir = mu_util_dir_expand(opts->linksdir); @@ -138,6 +141,7 @@ set_group_find_defaults (MuConfig *opts) g_free(old); } + /* FIXME: some warning when summary_len < 0? */ if (opts->summary_len < 1) opts->summary_len = 0; @@ -148,8 +152,6 @@ config_options_group_find (MuConfig *opts) { GOptionGroup *og; GOptionEntry entries[] = { - {"xquery", 0, 0, G_OPTION_ARG_NONE, &opts->xquery, - "print the Xapian query (for debugging)", NULL}, {"fields", 'f', 0, G_OPTION_ARG_STRING, &opts->fields, "fields to display in the output", NULL}, {"sortfield", 's', 0, G_OPTION_ARG_STRING, &opts->sortfield, @@ -159,13 +161,14 @@ config_options_group_find (MuConfig *opts) {"descending", 'z', 0, G_OPTION_ARG_NONE, &opts->descending, "sort in descending order (z -> a)", NULL}, {"summary-len", 'k', 0, G_OPTION_ARG_INT, &opts->summary_len, - "max number of lines for summary", NULL}, + "max number of lines for summary (0)", NULL}, {"linksdir", 0, 0, G_OPTION_ARG_STRING, &opts->linksdir, "output as symbolic links to a target maildir", NULL}, {"clearlinks", 0, 0, G_OPTION_ARG_NONE, &opts->clearlinks, - "clear old links before filling a linksdir", NULL}, - /* {"output", 'o', 0, G_OPTION_ARG_STRING, &opts->output, */ - /* "output type ('plain'(*), 'links', 'xml', 'json', 'sexp')", NULL}, */ + "clear old links before filling a linksdir (false)", NULL}, + {"format", 'o', 0, G_OPTION_ARG_STRING, &opts->formatstr, + "output format ('plain'(*), 'links', 'xml'," + "'json', 'sexp', 'xquery') (plain)", NULL}, {NULL, 0, 0, 0, NULL, NULL, NULL} }; @@ -190,8 +193,7 @@ config_options_group_mkdir (MuConfig *opts) /* set dirmode before, because '0000' is a valid mode */ opts->dirmode = 0755; - og = g_option_group_new("mkdir", - "options for the 'mkdir' command", + og = g_option_group_new("mkdir", "options for the 'mkdir' command", "", NULL, NULL); g_option_group_add_entries(og, entries); @@ -205,15 +207,15 @@ config_options_group_extract(MuConfig *opts) GOptionEntry entries[] = { {"save-attachments", 'a', 0, G_OPTION_ARG_NONE, &opts->save_attachments, - "save all attachments", NULL}, + "save all attachments (false)", NULL}, {"save-all", 0, 0, G_OPTION_ARG_NONE, &opts->save_all, - "save all parts (incl. non-attachments)", NULL}, + "save all parts (incl. non-attachments) (false)", NULL}, {"parts", 0, 0, G_OPTION_ARG_STRING, &opts->parts, "save specific parts (comma-separated list)", NULL}, {"target-dir", 0, 0, G_OPTION_ARG_FILENAME, &opts->targetdir, "target directory for saving", NULL}, {"overwrite", 0, 0, G_OPTION_ARG_NONE, &opts->overwrite, - "overwrite existing files", NULL}, + "overwrite existing files (false)", NULL}, {NULL, 0, 0, 0, NULL, NULL, NULL} }; diff --git a/src/mu-config.h b/src/mu-config.h index a77a4737..65592999 100644 --- a/src/mu-config.h +++ b/src/mu-config.h @@ -27,6 +27,13 @@ G_BEGIN_DECLS +#define MU_CONFIG_FORMAT_PLAIN "plain" /* plain text output */ +#define MU_CONFIG_FORMAT_LINKS "links" /* output as symlinks */ +#define MU_CONFIG_FORMAT_XML "xml" /* output xml */ +#define MU_CONFIG_FORMAT_JSON "json" /* output json */ +#define MU_CONFIG_FORMAT_SEXP "sexp" /* output sexps */ +#define MU_CONFIG_FORMAT_XQUERY "xquery" /* output the xapian query */ + enum _MuConfigCmd { MU_CONFIG_CMD_INDEX, MU_CONFIG_CMD_FIND, @@ -79,6 +86,9 @@ struct _MuConfig { unsigned summary_len; /* max # of lines of msg in summary */ char *bookmark; /* use bookmark */ + char *formatstr; /* output type + * (plain*,links,xml,json,sexp) */ + /* output to a maildir with symlinks */ char *linksdir; /* maildir to output symlinks */ gboolean clearlinks; /* clear a linksdir before filling */ @@ -103,8 +113,8 @@ typedef struct _MuConfig MuConfig; * * @param opts options */ -MuConfig *mu_config_new (int *argcp, char ***argvp); - +MuConfig *mu_config_new (int *argcp, char ***argvp) + G_GNUC_WARN_UNUSED_RESULT; /** * free the MuOptionsConfig structure; the the muhome and maildir * members are heap-allocated, so must be freed. diff --git a/src/mu-output.c b/src/mu-output.c index 485726b0..ef056d92 100644 --- a/src/mu-output.c +++ b/src/mu-output.c @@ -100,7 +100,6 @@ mu_output_links (MuMsgIter *iter, const char* linksdir, g_return_val_if_fail (iter, FALSE); g_return_val_if_fail (linksdir, FALSE); - g_return_val_if_fail (!mu_msg_iter_is_done (iter), FALSE); /* note: we create the linksdir even if there are no search results */ if (!create_linksdir_maybe (linksdir, clearlinks)) @@ -230,3 +229,32 @@ mu_output_plain (MuMsgIter *iter, const char *fields, size_t summary_len, return TRUE; } + +gboolean +mu_output_xml (MuMsgIter *iter, size_t *count) +{ + g_print ("\n"); + g_print ("%s\n", __FUNCTION__); + + return TRUE; + +} + +gboolean +mu_output_json (MuMsgIter *iter, size_t *count) +{ + g_print ("{\n"); + g_print ("\t%s\n", __FUNCTION__); + g_print ("}\n"); + + return TRUE; +} + + +gboolean +mu_output_sexp (MuMsgIter *iter, size_t *count) +{ + g_print ("(%s)\n", __FUNCTION__); + + return TRUE; +} diff --git a/src/mu-output.h b/src/mu-output.h index b2554b7c..2620850b 100644 --- a/src/mu-output.h +++ b/src/mu-output.h @@ -52,6 +52,41 @@ gboolean mu_output_plain (MuMsgIter *iter, const char *fields, gboolean mu_output_links (MuMsgIter *iter, const char *linksdir, gboolean clearlinks, size_t *count); +/** + * output the search results (MsgIter) as XML to standard + * output + * + * @param iter iterator pointing to a message row + * @param count output param to receive the number of messages found, or NULL + * + * @return TRUE if the printing succeeded, FALSE in case of error + */ +gboolean mu_output_xml (MuMsgIter *iter, size_t *count); + +/** + * output the search results (MsgIter) as JSON to standard + * output + * + * @param iter iterator pointing to a message row + * @param count output param to receive the number of messages found, or NULL + * + * @return TRUE if the printing succeeded, FALSE in case of error + */ +gboolean mu_output_json (MuMsgIter *iter, size_t *count); + +/** + * output the search results (MsgIter) as s-expressions to standard + * output + * + * @param iter iterator pointing to a message row + * @param count output param to receive the number of messages found, or NULL + * + * @return TRUE if the printing succeeded, FALSE in case of error + */ +gboolean mu_output_sexp (MuMsgIter *iter, size_t *count); + + + G_END_DECLS #endif /*__MU_OUTPUT_H__*/