pick.c 11 KB
/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 2003, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
   Foundation, Inc.

   GNU Mailutils is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   GNU Mailutils is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */

/* MH pick command */

#include <mh.h>
#include <regex.h>
#include <pick.h>
#include <pick-gram.h>
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#include <obstack.h>

static char doc[] = N_("GNU MH pick")"\v"
N_("Compatibility syntax for picking a matching component is:\n\
\n\
  --Component pattern\n\
\n\
where Component is the component name, containing at least one capital\n\
letter or followed by a colon, e.g.:\n\
\n\
  --User-Agent Mailutils\n\
  --user-agent: Mailutils\n\
\n\
Use -help to obtain a list of traditional MH options.");
static char args_doc[] = N_("[messages]");

/* GNU options */
static struct argp_option options[] = {
  {"folder",  ARG_FOLDER, N_("FOLDER"), 0,
   N_("specify folder to operate upon"), 0},

  {N_("Specifying search patterns:"), 0,  NULL, OPTION_DOC,  NULL, 0},
  {"component", ARG_COMPONENT, N_("FIELD"), 0,
   N_("search the named header field"), 1},
  {"pattern", ARG_PATTERN, N_("STRING"), 0,
   N_("set pattern to look for"), 1},
  {"search",  0, NULL, OPTION_ALIAS, NULL, 1},
  {"cflags",  ARG_CFLAGS,  N_("STRING"), 0,
   N_("flags controlling the type of regular expressions. STRING must consist of one or more of the following letters: B=basic, E=extended, I=ignore case, C=case sensitive. Default is \"EI\". The flags remain in effect until the next occurrence of --cflags option. The option must occur right before --pattern or --component option (or its alias).") },
  {"cc",      ARG_CC,      N_("STRING"), 0,
   N_("same as --component cc --pattern STRING"), 1},
  {"date",    ARG_DATE,    N_("STRING"), 0,
   N_("same as --component date --pattern STRING"), 1},
  {"from",    ARG_FROM,    N_("STRING"), 0,
   N_("same as --component from --pattern STRING"), 1},
  {"subject", ARG_SUBJECT, N_("STRING"), 0,
   N_("same as --component subject --pattern STRING"), 1},
  {"to",      ARG_TO,      N_("STRING"), 0,
   N_("same as --component to --pattern STRING"), 1},

  {N_("Date constraint operations:"), 0,  NULL, OPTION_DOC, NULL, 1},
  {"datefield",ARG_DATEFIELD, N_("STRING"), 0,
   N_("search in the named date header field (default is `Date:')"), 2},
  {"after",    ARG_AFTER,     N_("DATE"), 0,
   N_("match messages after the given date"), 2},
  {"before",   ARG_BEFORE,    N_("DATE"), 0,
   N_("match messages before the given date"), 2},

  {N_("Logical operations and grouping:"), 0, NULL, OPTION_DOC, NULL, 2},
  {"and",     ARG_AND,    NULL, 0,
   N_("logical AND (default)"), 3 },
  {"or",      ARG_OR,     NULL, 0,
   N_("logical OR"), 3 },
  {"not",     ARG_NOT,    NULL, 0,
   N_("logical NOT"), 3},
  {"lbrace",  ARG_LBRACE, NULL, 0,
   N_("open group"), 3},
  {"(",       0, NULL, OPTION_ALIAS, NULL, 3},
  {"rbrace",  ARG_RBRACE, NULL, 0,
   N_("close group"), 3},
  {")",       0, NULL, OPTION_ALIAS, NULL, 3},

  {N_("Operations over the selected messages:"), 0, NULL, OPTION_DOC, NULL, 3},
  {"list",   ARG_LIST,       N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("list the numbers of the selected messages (default)"), 4},
  {"nolist", ARG_NOLIST,     NULL, OPTION_HIDDEN, "", 4 },
  {"sequence", ARG_SEQUENCE,  N_("NAME"), 0,
   N_("add matching messages to the given sequence"), 4},
  {"public", ARG_PUBLIC, N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("create public sequence"), 4},
  {"nopublic", ARG_NOPUBLIC, NULL, OPTION_HIDDEN, "", 4 },
  {"zero",     ARG_ZERO,     N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("empty the sequence before adding messages"), 4},
  {"nozero", ARG_NOZERO, NULL, OPTION_HIDDEN, "", 4 },
  {NULL},
};

/* Traditional MH options */
struct mh_option mh_option[] = {
  { "component", MH_OPT_ARG, "field" },
  { "pattern",   MH_OPT_ARG, "pattern" },
  { "search",    MH_OPT_ARG, "pattern" },
  { "cc",        MH_OPT_ARG, "pattern" },
  { "date",      MH_OPT_ARG, "pattern" },
  { "from",      MH_OPT_ARG, "pattern" },
  { "subject",   MH_OPT_ARG, "pattern" },
  { "to",        MH_OPT_ARG, "pattern" },
  { "datefield", MH_OPT_ARG, "field" },
  { "after",     MH_OPT_ARG, "date" },
  { "before",    MH_OPT_ARG, "date" },
  { "and" },
  { "or" }, 
  { "not" },
  { "lbrace" },
  { "rbrace" },

  { "list",      MH_OPT_BOOL },
  { "sequence",  MH_OPT_ARG, "name" },
  { "public",    MH_OPT_BOOL },
  { "zero",      MH_OPT_BOOL },
  { NULL }
};

static int list = 1;
static int seq_flags = 0; /* Create public sequences;
			     Do not zero the sequence before addition */
static mu_list_t seq_list;  /* List of sequence names to operate upon */

static mu_list_t lexlist;   /* List of input tokens */

static struct obstack msgno_stk; /* Stack of selected message numbers */
static size_t msgno_count;       /* Number of items on the stack */

static void
add_sequence (char *name)
{
  if (!seq_list && mu_list_create (&seq_list))
    {
      mu_error (_("cannot create sequence list"));
      exit (1);
    }
  mu_list_append (seq_list, name);
}

static error_t
opt_handler (int key, char *arg, struct argp_state *state)
{
  switch (key)
    {
    case ARG_FOLDER: 
      mh_set_current_folder (arg);
      break;

    case ARG_SEQUENCE:
      add_sequence (arg);
      list = 0;
      break;

    case ARG_LIST:
      list = is_true (arg);
      break;

    case ARG_NOLIST:
      list = 0;
      break;

    case ARG_COMPONENT:
      pick_add_token (&lexlist, T_COMP, arg);
      break;
      
    case ARG_PATTERN:
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
    case ARG_CC:
      pick_add_token (&lexlist, T_COMP, "cc");
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
    case ARG_DATE:           
      pick_add_token (&lexlist, T_COMP, "date");
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
    case ARG_FROM:           
      pick_add_token (&lexlist, T_COMP, "from");
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
    case ARG_SUBJECT:        
      pick_add_token (&lexlist, T_COMP, "subject");
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
    case ARG_TO:
      pick_add_token (&lexlist, T_COMP, "to");
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
    case ARG_DATEFIELD:
      pick_add_token (&lexlist, T_DATEFIELD, arg);
      break;
      
    case ARG_AFTER:
      pick_add_token (&lexlist, T_AFTER, NULL);
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
    case ARG_BEFORE:
      pick_add_token (&lexlist, T_BEFORE, NULL);
      pick_add_token (&lexlist, T_STRING, arg);
      break;
	
    case ARG_AND:
      pick_add_token (&lexlist, T_AND, NULL);
      break;
      
    case ARG_OR:
      pick_add_token (&lexlist, T_OR, NULL);
      break;
      
    case ARG_NOT:
      pick_add_token (&lexlist, T_NOT, NULL);
      break;

    case ARG_LBRACE:
      pick_add_token (&lexlist, T_LBRACE, NULL);
      break;
      
    case ARG_RBRACE:
      pick_add_token (&lexlist, T_RBRACE, NULL);
      break;

    case ARG_CFLAGS:
      pick_add_token (&lexlist, T_CFLAGS, arg);
      break;
      
    case ARG_PUBLIC:
      if (is_true (arg))
	seq_flags &= ~SEQ_PRIVATE;
      else
	seq_flags |= SEQ_PRIVATE;
      break;
      
    case ARG_NOPUBLIC:
      seq_flags |= SEQ_PRIVATE;
      break;
      
    case ARG_ZERO:
      if (is_true (arg))
	seq_flags |= SEQ_ZERO;
      else
	seq_flags &= ~SEQ_ZERO;
      break;

    case ARG_NOZERO:
      seq_flags &= ~SEQ_ZERO;
      break;
	
    default:
      return ARGP_ERR_UNKNOWN;
    }

  return 0;
}

void
pick_message (mu_mailbox_t mbox, mu_message_t msg, size_t num, void *data)
{
  if (pick_eval (msg))
    {
      mh_message_number (msg, &num);
      if (list)
	printf ("%s\n", mu_umaxtostr (0, num));
      if (seq_list)
	{
	  obstack_grow (&msgno_stk, &num, sizeof (num));
	  msgno_count++;
	}
    }
}

static int
action_add (void *item, void *data)
{
  mh_seq_add ((char *)item, (mh_msgset_t *)data, seq_flags);
  return 0;
}

void
pick_help_hook (void)
{
  printf ("\n");
  printf (_("To match another component, use:\n\n"));
  printf (_("  --Component pattern\n\n"));
  printf (_("Note, that the component name must either be capitalized,\n"
	    "or followed by a colon.\n"));
  printf ("\n");
}

/* NOTICE: For compatibility with RAND MH we have to support
   the following command line syntax:

       --COMP STRING

   where `COMP' may be any string and which is equivalent to
   `--component FIELD --pattern STRING'. Obviously this is in conflict
   with the usual GNU long options paradigm which requires that any
   unrecognized long option produce an error. The following
   compromise solution is used:

   The arguments `--COMP STRING' is recognized as a component matching
   request if any of the following conditions is met:

   1. The word `COMP' contains at least one capital letter.  E.g.:

       --User-Agent Mailutils
       
   2. The word `COMP' ends with a colon, e.g.:

       --user-agent: Mailutils
   
   3. Standard input is not connected to a terminal.  This is always
      true when pick is invoked from mh-pick.el Emacs module.
*/

int
main (int argc, char **argv)
{
  int status;
  int index;
  mu_mailbox_t mbox;
  mh_msgset_t msgset;
  int interactive = mh_interactive_mode_p ();

  MU_APP_INIT_NLS ();

  for (index = 1; index < argc; index++)
    {
      int colon = 0, cpos;
      if (argv[index][0] == '-' && argv[index][1] == '-' &&
	  !strchr (argv[index], '=') &&
	  (!interactive ||
	   (colon = argv[index][cpos = strlen (argv[index]) - 1] == ':') ||
	   *mu_str_skip_class_comp (argv[index], MU_CTYPE_UPPER)) &&
	  index + 1 < argc)
	{
	  if (colon)
	    {
	      cpos -= 2;
	      mu_asprintf (&argv[index], "--component=%*.*s", cpos, cpos,
			   argv[index] + 2);
	    }
	  else
	    mu_asprintf (&argv[index], "--component=%s", argv[index] + 2);
	  mu_asprintf (&argv[index + 1], "--pattern=%s", argv[index + 1]);
	  index++;
	}
    }
  mh_help_hook = pick_help_hook;
  mh_argp_init ();
  mh_argp_parse (&argc, &argv, 0, options, mh_option,
		 args_doc, doc, opt_handler, NULL, &index);
  if (pick_parse (lexlist))
    return 1;

  mbox = mh_open_folder (mh_current_folder (), 0);

  argc -= index;
  argv += index;

  if (seq_list)
    obstack_init (&msgno_stk);
  
  mh_msgset_parse (mbox, &msgset, argc, argv, "all");
  status = mh_iterate (mbox, &msgset, pick_message, NULL);

  if (seq_list)
    {
      mh_msgset_t msgset;
      msgset.count = msgno_count;
      msgset.list = obstack_finish (&msgno_stk);
      mu_list_do (seq_list, action_add, (void*) &msgset);
    }

  mh_global_save_state ();
  return status;
}