Blame view

mh/pick.c 10.9 KB
1
/* GNU Mailutils -- a suite of utilities for electronic mail
Sergey Poznyakoff authored
2
   Copyright (C) 2003, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
3 4 5

   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
6
   the Free Software Foundation; either version 3, or (at your option)
7 8 9 10 11 12 13 14 15
   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, write to the Free Software
16 17
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301 USA */
18 19 20 21 22

/* MH pick command */

#include <mh.h>
#include <regex.h>
Sergey Poznyakoff authored
23 24 25 26 27
#include <pick.h>
#include <pick-gram.h>
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#include <obstack.h>
28

29
const char *program_version = "pick (" PACKAGE_STRING ")";
30 31
static char doc[] = N_("GNU MH pick")"\v"
N_("Options marked with `*' are not yet implemented.\n\
32
Use -help to obtain the list of traditional MH options.");
33 34 35 36 37 38 39 40 41 42 43 44 45
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_("A pattern to look for"), 1},
  {"search",  0, NULL, OPTION_ALIAS, NULL, 1},
Sergey Poznyakoff authored
46 47
  {"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).") },
48 49 50 51 52 53 54 55 56 57 58 59 60
  {"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,
61
   N_("Search in the named date header field (default is `Date:')"), 2},
62
  {"after",    ARG_AFTER,     N_("DATE"), 0,
63
   N_("Match messages after the given date"), 2},
64
  {"before",   ARG_BEFORE,    N_("DATE"), 0,
65
   N_("Match messages before the given date"), 2},
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

  {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 },
93 94
  {"license", ARG_LICENSE, 0,      0,
   N_("Display software license"), -1},
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
  {NULL},
};

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

  {"list",      1,  MH_OPT_BOOL, },
  {"sequence",  1,  0, NULL },
  {"public",    1,  MH_OPT_BOOL },
  {"zero",      1,  MH_OPT_BOOL },
  {NULL}
};

Sergey Poznyakoff authored
124 125 126
static int list = 1;
static int seq_flags = 0; /* Create public sequences;
			     Do not zero the sequence before addition */
127
static mu_list_t seq_list;  /* List of sequence names to operate upon */
Sergey Poznyakoff authored
128

129
static mu_list_t lexlist;   /* List of input tokens */
Sergey Poznyakoff authored
130 131 132 133 134 135 136

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)
{
137
  if (!seq_list && mu_list_create (&seq_list))
Sergey Poznyakoff authored
138
    {
139
      mu_error (_("Cannot create sequence list"));
Sergey Poznyakoff authored
140 141
      exit (1);
    }
142
  mu_list_append (seq_list, name);
Sergey Poznyakoff authored
143
}
144 145

static int
Sergey Poznyakoff authored
146
opt_handler (int key, char *arg, void *unused, struct argp_state *state)
147
{
Sergey Poznyakoff authored
148 149
  char *s, *p;
  
150 151 152
  switch (key)
    {
    case ARG_FOLDER: 
153
      mh_set_current_folder (arg);
154 155 156
      break;

    case ARG_SEQUENCE:
Sergey Poznyakoff authored
157 158
      add_sequence (arg);
      list = 0;
159 160 161 162 163 164 165 166 167 168
      break;

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

    case ARG_NOLIST:
      list = 0;
      break;

Sergey Poznyakoff authored
169 170 171 172 173 174 175 176 177 178 179 180 181
    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;
      
182
    case ARG_DATE:           
Sergey Poznyakoff authored
183 184 185 186
      pick_add_token (&lexlist, T_COMP, "date");
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
187
    case ARG_FROM:           
Sergey Poznyakoff authored
188 189 190 191
      pick_add_token (&lexlist, T_COMP, "from");
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
192
    case ARG_SUBJECT:        
Sergey Poznyakoff authored
193 194 195 196 197 198 199 200 201
      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;
      
202
    case ARG_DATEFIELD:
Sergey Poznyakoff authored
203 204 205
      pick_add_token (&lexlist, T_DATEFIELD, arg);
      break;
      
206
    case ARG_AFTER:
Sergey Poznyakoff authored
207 208 209 210
      pick_add_token (&lexlist, T_AFTER, NULL);
      pick_add_token (&lexlist, T_STRING, arg);
      break;
      
211
    case ARG_BEFORE:
Sergey Poznyakoff authored
212 213 214 215
      pick_add_token (&lexlist, T_BEFORE, NULL);
      pick_add_token (&lexlist, T_STRING, arg);
      break;
	
216
    case ARG_AND:
Sergey Poznyakoff authored
217 218 219
      pick_add_token (&lexlist, T_AND, NULL);
      break;
      
220
    case ARG_OR:
Sergey Poznyakoff authored
221 222 223
      pick_add_token (&lexlist, T_OR, NULL);
      break;
      
224
    case ARG_NOT:
Sergey Poznyakoff authored
225 226 227
      pick_add_token (&lexlist, T_NOT, NULL);
      break;

228
    case ARG_LBRACE:
Sergey Poznyakoff authored
229 230 231
      pick_add_token (&lexlist, T_LBRACE, NULL);
      break;
      
232
    case ARG_RBRACE:
Sergey Poznyakoff authored
233 234 235 236 237
      pick_add_token (&lexlist, T_RBRACE, NULL);
      break;

    case ARG_CFLAGS:
      pick_add_token (&lexlist, T_CFLAGS, arg);
238 239 240
      break;
      
    case ARG_PUBLIC:
Sergey Poznyakoff authored
241 242 243 244
      if (is_true (arg))
	seq_flags &= ~SEQ_PRIVATE;
      else
	seq_flags |= SEQ_PRIVATE;
245 246 247
      break;
      
    case ARG_NOPUBLIC:
Sergey Poznyakoff authored
248
      seq_flags |= SEQ_PRIVATE;
249 250 251
      break;
      
    case ARG_ZERO:
Sergey Poznyakoff authored
252 253 254 255
      if (is_true (arg))
	seq_flags |= SEQ_ZERO;
      else
	seq_flags &= ~SEQ_ZERO;
256 257 258
      break;

    case ARG_NOZERO:
Sergey Poznyakoff authored
259 260 261 262 263 264 265
      seq_flags &= ~SEQ_ZERO;
      break;
	
    case ARGP_KEY_ERROR:
      s = state->argv[state->next - 1];
      if (memcmp (s, "--", 2))
	{
266
	  argp_error (state, _("Invalid option -- %s"), s);
Sergey Poznyakoff authored
267 268 269 270 271 272 273 274 275 276 277 278
	  exit (1);
	}
      p = strchr (s, '=');
      if (p)
	*p++ = 0;
	
      pick_add_token (&lexlist, T_COMP, s + 2);

      if (!p)
	{
	  if (state->next == state->argc)
	    {
279
	      mu_error (_("Invalid option -- %s"), s);
Sergey Poznyakoff authored
280 281 282 283 284 285
	      exit (1);
	    }
	  p = state->argv[state->next++];
	}
      
      pick_add_token (&lexlist, T_STRING, p);
286 287
      break;

288 289 290 291
    case ARG_LICENSE:
      mh_license (argp_program_version);
      break;

292 293 294
    default:
      return 1;
    }
Sergey Poznyakoff authored
295

296 297 298
  return 0;
}

Sergey Poznyakoff authored
299
void
300
pick_message (mu_mailbox_t mbox, mu_message_t msg, size_t num, void *data)
Sergey Poznyakoff authored
301 302 303 304 305
{
  if (pick_eval (msg))
    {
      mh_message_number (msg, &num);
      if (list)
306
	printf ("%s\n", mu_umaxtostr (0, num));
Sergey Poznyakoff authored
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
      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;
}

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

       --FIELD STRING

   where `FIELD' may be any string and which is equivalent to
   `--field 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. Unfortunately, mh-pick.el
   relies heavily on this syntax, so it can't be simply removed.
   The approach taken here allows to properly recognize such syntax,
   however it has an undesirable side effect: due to the specifics of
   the underlying arpg library the --help and --usage options get
   disabled. To make them work as well, the following approach is
   taken: the mh-compatible syntax gets enabled only if the file
   descriptor of stdin is not connected to a terminal, which is true
   when invoked from mh-pick.el module. Otherwise, it is disabled
   and the standard GNU long option syntax is in force. */
340 341 342
int
main (int argc, char **argv)
{
Sergey Poznyakoff authored
343
  int status;
344
  int index;
345
  mu_mailbox_t mbox;
Sergey Poznyakoff authored
346 347
  mh_msgset_t msgset;
  int flags;
348

349
  flags = mh_interactive_mode_p () ? 0 : ARGP_NO_ERRS;
Sergey Poznyakoff authored
350
  MU_APP_INIT_NLS ();
351
  mh_argp_init (program_version);
352
  mh_argp_parse (&argc, &argv, flags, options, mh_option,
Sergey Poznyakoff authored
353 354 355 356
		 args_doc, doc, opt_handler, NULL, &index);
  if (pick_parse (lexlist))
    return 1;

357
  mbox = mh_open_folder (mh_current_folder (), 0);
Sergey Poznyakoff authored
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372

  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);
373
      mu_list_do (seq_list, action_add, (void*) &msgset);
Sergey Poznyakoff authored
374 375 376 377
    }

  mh_global_save_state ();
  return status;
378 379
}