New filter option: --filter-invert. When present it inverts (negates) the other filter options combined. This is mostly useful with the -G option (with -Q the output is limited by the query range (day range)). The ouput from "calcurse -G <filter options>" is the (set) complement of "calcurse -G <filter options> --filter-invert". Here <filter options> may be any combination of filter options. Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk> Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
983 lines
28 KiB
C
983 lines
28 KiB
C
/*
|
|
* Calcurse - text-based organizer
|
|
*
|
|
* Copyright (c) 2004-2017 calcurse Development Team <misc@calcurse.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* - Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
*
|
|
* - Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* Send your feedback or comments to : misc@calcurse.org
|
|
* Calcurse home page : http://calcurse.org
|
|
*
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <limits.h>
|
|
#include <getopt.h>
|
|
#include <time.h>
|
|
|
|
#include "calcurse.h"
|
|
|
|
/* Input types for parse_datetimearg() */
|
|
enum {
|
|
ARG_DATE,
|
|
ARG_DATE_TIME,
|
|
ARG_TIME,
|
|
ARG_ERR
|
|
};
|
|
|
|
/* Long options */
|
|
enum {
|
|
OPT_FILTER_INVERT = 1000,
|
|
OPT_FILTER_TYPE,
|
|
OPT_FILTER_HASH,
|
|
OPT_FILTER_PATTERN,
|
|
OPT_FILTER_START_FROM,
|
|
OPT_FILTER_START_TO,
|
|
OPT_FILTER_START_AFTER,
|
|
OPT_FILTER_START_BEFORE,
|
|
OPT_FILTER_START_RANGE,
|
|
OPT_FILTER_END_FROM,
|
|
OPT_FILTER_END_TO,
|
|
OPT_FILTER_END_AFTER,
|
|
OPT_FILTER_END_BEFORE,
|
|
OPT_FILTER_END_RANGE,
|
|
OPT_FILTER_PRIORITY,
|
|
OPT_FILTER_COMPLETED,
|
|
OPT_FILTER_UNCOMPLETED,
|
|
OPT_FROM,
|
|
OPT_TO,
|
|
OPT_DAYS,
|
|
OPT_FMT_APT,
|
|
OPT_FMT_RAPT,
|
|
OPT_FMT_EV,
|
|
OPT_FMT_REV,
|
|
OPT_FMT_TODO,
|
|
OPT_DUMP_IMPORTED,
|
|
OPT_EXPORT_UID,
|
|
OPT_READ_ONLY,
|
|
OPT_STATUS,
|
|
OPT_DAEMON,
|
|
OPT_INPUT_DATEFMT,
|
|
OPT_OUTPUT_DATEFMT
|
|
};
|
|
|
|
/*
|
|
* Print Calcurse usage and exit.
|
|
*/
|
|
static void usage(void)
|
|
{
|
|
printf("%s\n", _("Usage:\n"
|
|
"calcurse [-D <directory>] [-C <directory>] [-c <calendar file>]\n"
|
|
"calcurse -Q [--from <date>] [--to <date>] [--days <number>]\n"
|
|
"calcurse -a | -d <date> | -d <number> | -n | -r[<number>] | -s[<date>] | -t[<number>]\n"
|
|
"calcurse -h | -v | --status | -G | -P | -g | -i <file> | -x[<file>] | --daemon"));
|
|
}
|
|
|
|
static void usage_try(void)
|
|
{
|
|
printf("%s\n", _("Try `calcurse -h` for more information."));
|
|
}
|
|
|
|
/*
|
|
* Print Calcurse version with a short copyright text and exit.
|
|
*/
|
|
static void version_arg(void)
|
|
{
|
|
printf(_("calcurse %s -- text-based organizer\n"), VERSION);
|
|
putchar('\n');
|
|
printf("%s\n", _("Copyright (c) 2004-2017 calcurse Development Team."));
|
|
printf("%s\n", _("This is free software; see the source for copying conditions."));
|
|
}
|
|
|
|
/*
|
|
* Print the command line options and exit.
|
|
*/
|
|
static void help_arg(void)
|
|
{
|
|
usage();
|
|
putchar('\n');
|
|
printf("%s\n", _("Operations in command line mode:"));
|
|
printf("%s\n", _(" -Q, --query Print items in a given query range"));
|
|
printf("%s\n", _(" -G, --grep Grep items from the data files"));
|
|
printf("%s\n", _(" -P, --purge Read items and write them back"));
|
|
printf("%s\n", _("Query short forms:\n"
|
|
"-a, -d <date>|<number>, -n, -r[<number>], -s[<date>], -t<number>"));
|
|
putchar('\n');
|
|
printf("%s\n", _("Note that filter, format and day-range options affect input or output:"));
|
|
printf("%s\n", _(" --filter-* Filter items loaded by -Q, -G, -P and -x"));
|
|
printf("%s\n", _(" --format-* Rewrite output from -Q, -G and --dump-imported"));
|
|
printf("%s\n", _(" --from <date> Limit day range of -Q."));
|
|
printf("%s\n", _(" --to <date> Limit day range of -Q."));
|
|
printf("%s\n", _(" --days <number> Limit day range of -Q."));
|
|
putchar('\n');
|
|
printf("%s\n", _(" --limit, -l <number> Limit number of query results"));
|
|
printf("%s\n", _(" --search, -S <regexp> Match regular expression in queries"));
|
|
printf("%s\n", _("Consult the man page for details."));
|
|
putchar('\n');
|
|
printf("%s\n", _("Miscellaneous:"));
|
|
printf("%s\n", _(" -c, --calendar <file> The calendar data file to use"));
|
|
printf("%s\n", _(" -C, --conf <directory> The configuration directory to use"));
|
|
printf("%s\n", _(" --daemon Run notification daemon in the background"));
|
|
printf("%s\n", _(" -D, --directory <dir> The data directory to use"));
|
|
printf("%s\n", _(" -g, --gc Run the garbage collector"));
|
|
printf("%s\n", _(" -h, --help Show this help text"));
|
|
printf("%s\n", _(" -i, --import <file> Import iCal data from file"));
|
|
printf("%s\n", _(" -q, --quiet Suppress system dialogs"));
|
|
printf("%s\n", _(" --read-only Do not save configuration or data files"));
|
|
printf("%s\n", _(" --status Display status of running instances"));
|
|
printf("%s\n", _(" -v, --version Show version information"));
|
|
printf("%s\n", _(" -x, --export[<format>] Export to stdout in ical (default) or pcal format"));
|
|
putchar('\n');
|
|
printf("%s\n", _("For more information, type '?' from within calcurse, or read the manpage."));
|
|
printf("%s\n", _("Submit feature requests and suggestions to <misc@calcurse.org>."));
|
|
printf("%s\n", _("Submit bug reports to <bugs@calcurse.org>."));
|
|
}
|
|
|
|
/*
|
|
* Used to display the status of running instances of calcurse.
|
|
* The displayed message will look like one of the following ones:
|
|
*
|
|
* calcurse is running (pid #)
|
|
* calcurse is running in background (pid #)
|
|
* calcurse is not running
|
|
*
|
|
* The status is obtained by looking at pid files in user data directory
|
|
* (.calcurse.pid and .daemon.pid).
|
|
*/
|
|
static void status_arg(void)
|
|
{
|
|
int cpid, dpid;
|
|
|
|
cpid = io_get_pid(path_cpid);
|
|
dpid = io_get_pid(path_dpid);
|
|
|
|
EXIT_IF(cpid && dpid,
|
|
_("Error: both calcurse (pid: %d) and its daemon (pid: %d)\n"
|
|
"seem to be running at the same time!\n"
|
|
"Please check manually and restart calcurse.\n"), cpid,
|
|
dpid);
|
|
|
|
if (cpid)
|
|
fprintf(stdout, _("calcurse is running (pid %d)\n"), cpid);
|
|
else if (dpid)
|
|
fprintf(stdout,
|
|
_("calcurse is running in background (pid %d)\n"),
|
|
dpid);
|
|
else
|
|
puts(_("calcurse is not running"));
|
|
}
|
|
|
|
/* Print TODO list and return the number of printed items. */
|
|
static int todo_arg(const char *format, int *limit, struct item_filter *filter)
|
|
{
|
|
const char *titlestr =
|
|
filter->completed ? _("completed tasks:\n") : _("to do:\n");
|
|
int title = 1;
|
|
int n = 0;
|
|
llist_item_t *i;
|
|
|
|
LLIST_FOREACH(&todolist, i) {
|
|
if (*limit == 0)
|
|
break;
|
|
struct todo *todo = LLIST_TS_GET_DATA(i);
|
|
|
|
if (title) {
|
|
fputs(titlestr, stdout);
|
|
title = 0;
|
|
}
|
|
print_todo(format, todo);
|
|
n++;
|
|
(*limit)--;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
/* Print the next appointment within the upcoming 24 hours. */
|
|
static void next_arg(void)
|
|
{
|
|
struct notify_app next_app;
|
|
const long current_time = now();
|
|
int time_left, hours_left, min_left;
|
|
|
|
next_app.time = date_sec_change(current_time, 0, 1);
|
|
next_app.got_app = 0;
|
|
next_app.txt = NULL;
|
|
|
|
next_app = *recur_apoint_check_next(&next_app, current_time,
|
|
get_today());
|
|
next_app = *apoint_check_next(&next_app, current_time);
|
|
|
|
if (next_app.got_app) {
|
|
time_left = next_app.time - current_time;
|
|
hours_left = (time_left / HOURINSEC);
|
|
min_left = (time_left - hours_left * HOURINSEC) / MININSEC;
|
|
fputs(_("next appointment:\n"), stdout);
|
|
fprintf(stdout, " [%02d:%02d] %s\n", hours_left,
|
|
min_left, next_app.txt);
|
|
mem_free(next_app.txt);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print the date on stdout.
|
|
*/
|
|
static void arg_print_date(long date)
|
|
{
|
|
char date_str[BUFSIZ];
|
|
struct tm lt;
|
|
|
|
localtime_r((time_t *) & date, <);
|
|
strftime(date_str, BUFSIZ, conf.output_datefmt, <);
|
|
fputs(date_str, stdout);
|
|
fputs(":\n", stdout);
|
|
}
|
|
|
|
/*
|
|
* Print appointments inside the given query range.
|
|
* If no start day is given (-1), today is considered.
|
|
* If no end date is given (-1), a range of 1 day is considered.
|
|
*/
|
|
static void
|
|
date_arg_from_to(long from, long to, int add_line, const char *fmt_apt,
|
|
const char *fmt_rapt, const char *fmt_ev, const char *fmt_rev,
|
|
int *limit)
|
|
{
|
|
long date;
|
|
|
|
for (date = from; date <= to; date = date_sec_change(date, 0, 1)) {
|
|
day_store_items(date, 0);
|
|
if (day_item_count(0) == 0)
|
|
continue;
|
|
if (add_line)
|
|
fputs("\n", stdout);
|
|
arg_print_date(date);
|
|
day_write_stdout(date, fmt_apt, fmt_rapt, fmt_ev,
|
|
fmt_rev, limit);
|
|
add_line = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert a string with a (local time) date, date-time or time into
|
|
* the Unix time for that point in time as follows:
|
|
* - a date only is converted to midnight (beginning) of that day
|
|
* - a date-time is converted to that day and time
|
|
* - a time only is converted to that time of the current day
|
|
* The date format is taken from the user configuration.
|
|
* The type of the input string is returned in the type argument.
|
|
*/
|
|
|
|
static time_t parse_datetimearg(const char *str, int *type)
|
|
{
|
|
char *date = mem_strdup(str);
|
|
*type = ARG_ERR;
|
|
char *time;
|
|
struct date day;
|
|
unsigned hour, min;
|
|
time_t ret;
|
|
|
|
time = strchr(date, ' ');
|
|
if (time) { /* Date and time? */
|
|
*time++ = '\0';
|
|
if (!parse_time(time, &hour, &min))
|
|
return -1;
|
|
if (!parse_date(date, conf.input_datefmt,
|
|
(int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL))
|
|
return -1;
|
|
ret = date2sec(day, hour, min);
|
|
*type = ARG_DATE_TIME;
|
|
} else /* Date?*/ if (parse_date(date, conf.input_datefmt,
|
|
(int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL)) {
|
|
ret = date2sec(day, 0, 0);
|
|
*type = ARG_DATE;
|
|
} else /* Time? */ if (parse_time(date, &hour, &min)) {
|
|
ret = date2sec(*ui_calendar_get_today(), hour, min);
|
|
*type = ARG_TIME;
|
|
} else
|
|
return -1;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Parse a "from,to" date range.
|
|
* For an open-end range no change occurs at the open end.
|
|
*/
|
|
static int parse_daterange(const char *str, time_t *date_from, time_t *date_to)
|
|
{
|
|
int type, ret = 0;
|
|
char *s = mem_strdup(str);
|
|
char *p = strchr(s, ',');
|
|
|
|
if (!p)
|
|
goto cleanup;
|
|
|
|
*p = '\0';
|
|
p++;
|
|
|
|
if (*s != '\0') {
|
|
*date_from = parse_datetimearg(s, &type);
|
|
if (*date_from == -1)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (*p != '\0') {
|
|
*date_to = parse_datetimearg(p, &type);
|
|
if (*date_to == -1)
|
|
goto cleanup;
|
|
if (type == ARG_DATE)
|
|
*date_to = ENDOFDAY(*date_to);
|
|
}
|
|
|
|
ret = 1;
|
|
cleanup:
|
|
mem_free(s);
|
|
return ret;
|
|
}
|
|
|
|
static int parse_type_mask(const char *str)
|
|
{
|
|
char *buf = mem_strdup(str), *p;
|
|
int mask = 0;
|
|
|
|
for (p = strtok(buf, ","); p; p = strtok(NULL, ",")) {
|
|
if (!strcmp(p, "event")) {
|
|
mask |= TYPE_MASK_EVNT;
|
|
} else if (!strcmp(p, "apt")) {
|
|
mask |= TYPE_MASK_APPT;
|
|
} else if (!strcmp(p, "recur-event")) {
|
|
mask |= TYPE_MASK_RECUR_EVNT;
|
|
} else if (!strcmp(p, "recur-apt")) {
|
|
mask |= TYPE_MASK_RECUR_APPT;
|
|
} else if (!strcmp(p, "recur")) {
|
|
mask |= TYPE_MASK_RECUR;
|
|
} else if (!strcmp(p, "cal")) {
|
|
mask |= TYPE_MASK_CAL;
|
|
} else if (!strcmp(p, "todo")) {
|
|
mask |= TYPE_MASK_TODO;
|
|
} else {
|
|
mask = 0;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
mem_free(buf);
|
|
return mask;
|
|
}
|
|
|
|
/*
|
|
* Parse the command-line arguments and call the appropriate
|
|
* routines to handle those arguments. Also initialize the data paths.
|
|
* Returns the non-interactive value.
|
|
*/
|
|
int parse_args(int argc, char **argv)
|
|
{
|
|
/* Command-line flags - NOTE that read_only is global */
|
|
int grep = 0, purge = 0, query = 0, next = 0;
|
|
int status = 0, gc = 0, import = 0, export = 0, daemon = 0;
|
|
/* Command line invocation */
|
|
int filter_opt = 0, format_opt = 0, query_range = 0, cmd_line = 0;
|
|
int start_from = 0, start_to = 0, end_from = 0, end_to = 0;
|
|
/* Query ranges */
|
|
time_t from = -1, to = -1;
|
|
int range = 0;
|
|
int limit = INT_MAX;
|
|
/* Filters */
|
|
struct item_filter filter = { 0, 0, NULL, NULL, -1, -1, -1, -1, 0, 0, 0 };
|
|
/* Format strings */
|
|
const char *fmt_apt = NULL;
|
|
const char *fmt_rapt = NULL;
|
|
const char *fmt_ev = NULL;
|
|
const char *fmt_rev = NULL;
|
|
const char *fmt_todo = NULL;
|
|
/* Import and export parameters */
|
|
int xfmt = IO_EXPORT_ICAL;
|
|
int dump_imported = 0, export_uid = 0;
|
|
/* Data file locations */
|
|
const char *datadir = NULL;
|
|
const char *cfile = NULL, *ifile = NULL, *confdir = NULL;
|
|
|
|
int non_interactive = 1;
|
|
int ch, cpid, type;
|
|
regex_t reg;
|
|
char buf[BUFSIZ];
|
|
struct tm tm;
|
|
time_t t;
|
|
|
|
static const char *optstr = "PFgGhvnNax::t::C:d:c:r::s::S:D:i:l:qQ";
|
|
|
|
struct option longopts[] = {
|
|
{"appointment", no_argument, NULL, 'a'},
|
|
{"calendar", required_argument, NULL, 'c'},
|
|
{"conf", required_argument, NULL, 'C'},
|
|
{"day", required_argument, NULL, 'd'},
|
|
{"directory", required_argument, NULL, 'D'},
|
|
{"filter", no_argument, NULL, 'F'},
|
|
{"gc", no_argument, NULL, 'g'},
|
|
{"grep", no_argument, NULL, 'G'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"import", required_argument, NULL, 'i'},
|
|
{"limit", required_argument, NULL, 'l'},
|
|
{"next", no_argument, NULL, 'n'},
|
|
{"note", no_argument, NULL, 'N'},
|
|
{"purge", no_argument, NULL, 'P'},
|
|
{"range", optional_argument, NULL, 'r'},
|
|
{"startday", optional_argument, NULL, 's'},
|
|
{"search", required_argument, NULL, 'S'},
|
|
{"todo", optional_argument, NULL, 't'},
|
|
{"version", no_argument, NULL, 'v'},
|
|
{"export", optional_argument, NULL, 'x'},
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
{"query", optional_argument, NULL, 'Q'},
|
|
|
|
{"filter-invert", no_argument, NULL, OPT_FILTER_INVERT},
|
|
{"filter-type", required_argument, NULL, OPT_FILTER_TYPE},
|
|
{"filter-hash", required_argument, NULL, OPT_FILTER_HASH},
|
|
{"filter-pattern", required_argument, NULL, OPT_FILTER_PATTERN},
|
|
{"filter-start-from", required_argument, NULL, OPT_FILTER_START_FROM},
|
|
{"filter-start-to", required_argument, NULL, OPT_FILTER_START_TO},
|
|
{"filter-start-after", required_argument, NULL, OPT_FILTER_START_AFTER},
|
|
{"filter-start-before", required_argument, NULL, OPT_FILTER_START_BEFORE},
|
|
{"filter-start-range", required_argument, NULL, OPT_FILTER_START_RANGE},
|
|
{"filter-end-from", required_argument, NULL, OPT_FILTER_END_FROM},
|
|
{"filter-end-to", required_argument, NULL, OPT_FILTER_END_TO},
|
|
{"filter-end-after", required_argument, NULL, OPT_FILTER_END_AFTER},
|
|
{"filter-end-before", required_argument, NULL, OPT_FILTER_END_BEFORE},
|
|
{"filter-end-range", required_argument, NULL, OPT_FILTER_END_RANGE},
|
|
{"filter-priority", required_argument, NULL, OPT_FILTER_PRIORITY},
|
|
{"filter-completed", no_argument, NULL, OPT_FILTER_COMPLETED},
|
|
{"filter-uncompleted", no_argument, NULL, OPT_FILTER_UNCOMPLETED},
|
|
{"from", required_argument, NULL, OPT_FROM},
|
|
{"to", required_argument, NULL, OPT_TO},
|
|
{"days", required_argument, NULL, OPT_DAYS},
|
|
{"format-apt", required_argument, NULL, OPT_FMT_APT},
|
|
{"format-recur-apt", required_argument, NULL, OPT_FMT_RAPT},
|
|
{"format-event", required_argument, NULL, OPT_FMT_EV},
|
|
{"format-recur-event", required_argument, NULL, OPT_FMT_REV},
|
|
{"format-todo", required_argument, NULL, OPT_FMT_TODO},
|
|
{"export-uid", no_argument, NULL, OPT_EXPORT_UID},
|
|
{"dump-imported", no_argument, NULL, OPT_DUMP_IMPORTED},
|
|
{"read-only", no_argument, NULL, OPT_READ_ONLY},
|
|
{"status", no_argument, NULL, OPT_STATUS},
|
|
{"daemon", no_argument, NULL, OPT_DAEMON},
|
|
{"input-datefmt", required_argument, NULL, OPT_INPUT_DATEFMT},
|
|
{"output-datefmt", required_argument, NULL, OPT_OUTPUT_DATEFMT},
|
|
{NULL, no_argument, NULL, 0}
|
|
};
|
|
|
|
/*
|
|
* Load the configuration file first to get the input date format for
|
|
* parsing the remaining options.
|
|
*/
|
|
while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) {
|
|
switch (ch) {
|
|
case 'C':
|
|
confdir = optarg;
|
|
break;
|
|
case 'D':
|
|
datadir = optarg;
|
|
break;
|
|
}
|
|
}
|
|
io_init(cfile, datadir, confdir);
|
|
vars_init();
|
|
notify_init_vars();
|
|
if (io_file_exists(path_conf))
|
|
config_load();
|
|
|
|
/* Parse the remaining options. */
|
|
optind = 1;
|
|
while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) {
|
|
switch (ch) {
|
|
case 'a':
|
|
filter.type_mask |= TYPE_MASK_CAL;
|
|
query = 1;
|
|
break;
|
|
case 'c':
|
|
cfile = optarg;
|
|
io_init(cfile, datadir, confdir);
|
|
break;
|
|
case 'C':
|
|
break;
|
|
case 'd':
|
|
if (is_all_digit(optarg) ||
|
|
(*optarg == '-' && is_all_digit(optarg + 1))) {
|
|
range = atoi(optarg);
|
|
EXIT_IF(range == 0, _("invalid range: %s"),
|
|
optarg);
|
|
} else {
|
|
from = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(from == -1 || type != ARG_DATE,
|
|
_("invalid date: %s"), optarg);
|
|
}
|
|
|
|
filter.type_mask |= TYPE_MASK_CAL;
|
|
query = 1;
|
|
break;
|
|
case 'D':
|
|
break;
|
|
case 'F':
|
|
purge = grep = 1;
|
|
break;
|
|
case 'h':
|
|
help_arg();
|
|
goto cleanup;
|
|
case 'g':
|
|
gc = 1;
|
|
break;
|
|
case 'G':
|
|
grep = 1;
|
|
break;
|
|
case 'i':
|
|
import = 1;
|
|
ifile = optarg;
|
|
break;
|
|
case 'l':
|
|
limit = atoi(optarg);
|
|
break;
|
|
case 'n':
|
|
next = 1;
|
|
break;
|
|
case 'P':
|
|
purge = grep = 1;
|
|
break;
|
|
case 'r':
|
|
if (optarg)
|
|
range = atoi(optarg);
|
|
else
|
|
range = 1;
|
|
EXIT_IF(range == 0, _("invalid range: %s"), optarg);
|
|
filter.type_mask |= TYPE_MASK_CAL;
|
|
query = 1;
|
|
break;
|
|
case 's':
|
|
if (!optarg)
|
|
optarg = "today";
|
|
from = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(from == -1 || type != ARG_DATE,
|
|
_("invalid date: %s"), optarg);
|
|
filter.type_mask |= TYPE_MASK_CAL;
|
|
query = 1;
|
|
break;
|
|
case 't':
|
|
if (optarg) {
|
|
EXIT_IF(!is_all_digit(optarg),
|
|
_("invalid priority: %s"), optarg);
|
|
filter.priority = atoi(optarg);
|
|
if (filter.priority == 0)
|
|
filter.completed = 1;
|
|
else
|
|
filter.uncompleted = 1;
|
|
EXIT_IF(filter.priority > 9,
|
|
_("invalid priority: %s"), optarg);
|
|
} else {
|
|
filter.uncompleted = 1;
|
|
}
|
|
filter.type_mask |= TYPE_MASK_TODO;
|
|
query = 1;
|
|
break;
|
|
case 'v':
|
|
version_arg();
|
|
goto cleanup;
|
|
case 'x':
|
|
export = 1;
|
|
if (optarg) {
|
|
if (!strcmp(optarg, "ical"))
|
|
xfmt = IO_EXPORT_ICAL;
|
|
else if (!strcmp(optarg, "pcal"))
|
|
xfmt = IO_EXPORT_PCAL;
|
|
else
|
|
EXIT(_("invalid export format: %s"),
|
|
optarg);
|
|
}
|
|
break;
|
|
case 'q':
|
|
quiet = 1;
|
|
break;
|
|
case 'Q':
|
|
query = 1;
|
|
break;
|
|
case OPT_FILTER_INVERT:
|
|
filter.invert = !filter.invert;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_TYPE:
|
|
filter.type_mask = parse_type_mask(optarg);
|
|
EXIT_IF(filter.type_mask == 0,
|
|
_("invalid filter mask"));
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_HASH:
|
|
filter.hash = mem_strdup(optarg);
|
|
filter_opt = 1;
|
|
break;
|
|
case 'S':
|
|
case OPT_FILTER_PATTERN:
|
|
EXIT_IF(filter.regex,
|
|
_("cannot handle more than one regular expression"));
|
|
if (regcomp(®, optarg, REG_EXTENDED))
|
|
EXIT(_("could not compile regular expression: %s"), optarg);
|
|
filter.regex = ®
|
|
filter_opt = 1;
|
|
break;
|
|
/*
|
|
* A date only is a time span (a day) and interpreted thus:
|
|
* "to" means "to end of day"
|
|
* "after" means "from start of next day"
|
|
*/
|
|
case OPT_FILTER_START_FROM:
|
|
EXIT_IF(start_from,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.start_from = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.start_from == -1,
|
|
_("invalid date: %s"), optarg);
|
|
start_from = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_START_TO:
|
|
EXIT_IF(start_to,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.start_to = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.start_to == -1,
|
|
_("invalid date: %s"), optarg);
|
|
if (type == ARG_DATE)
|
|
filter.start_to = ENDOFDAY(filter.start_to);
|
|
start_to = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_START_AFTER:
|
|
EXIT_IF(start_from,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.start_from = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.start_from == -1,
|
|
_("invalid date: %s"), optarg);
|
|
if (type == ARG_DATE)
|
|
filter.start_from = NEXTDAY(filter.start_from);
|
|
else
|
|
filter.start_from++;
|
|
start_from = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_START_BEFORE:
|
|
EXIT_IF(start_to,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.start_to = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.start_to == -1,
|
|
_("invalid date: %s"), optarg);
|
|
filter.start_to--;
|
|
start_to = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_START_RANGE:
|
|
EXIT_IF(start_from,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
EXIT_IF(start_to,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
/* Set initialization values in case of open-end range. */
|
|
filter.start_from = filter.start_to = -1;
|
|
EXIT_IF(!parse_daterange(optarg, &filter.start_from, &filter.start_to),
|
|
_("invalid date range: %s"), optarg);
|
|
start_from = 1;
|
|
start_to = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_END_FROM:
|
|
EXIT_IF(end_from,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.end_from = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.end_from == -1,
|
|
_("invalid date: %s"), optarg);
|
|
end_from = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_END_TO:
|
|
EXIT_IF(end_to,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.end_to = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.end_to == -1,
|
|
_("invalid date: %s"), optarg);
|
|
if (type == ARG_DATE)
|
|
filter.end_to = ENDOFDAY(filter.end_to);
|
|
end_to = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_END_AFTER:
|
|
EXIT_IF(end_from,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.end_from = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.end_from == -1,
|
|
_("invalid date: %s"), optarg);
|
|
if (type == ARG_DATE)
|
|
filter.end_from = NEXTDAY(filter.end_from);
|
|
else
|
|
filter.end_from++;
|
|
end_from = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_END_BEFORE:
|
|
EXIT_IF(end_to,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
filter.end_to = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(filter.end_to == -1,
|
|
_("invalid date: %s"), optarg);
|
|
filter.end_to--;
|
|
end_to = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_END_RANGE:
|
|
EXIT_IF(end_from,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
EXIT_IF(end_to,
|
|
_("filter criterion already in use: %s"), argv[optind - 2]);
|
|
/* Set default values in case of open-ended range. */
|
|
filter.start_from = filter.start_to = -1;
|
|
EXIT_IF(!parse_daterange(optarg, &filter.end_from, &filter.end_to),
|
|
_("invalid date range: %s"), optarg);
|
|
end_from = 1;
|
|
end_to = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_PRIORITY:
|
|
filter.priority = atoi(optarg);
|
|
EXIT_IF(filter.priority < 1 || filter.priority > 9,
|
|
_("invalid priority: %s"), optarg);
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_COMPLETED:
|
|
filter.completed = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FILTER_UNCOMPLETED:
|
|
filter.uncompleted = 1;
|
|
filter_opt = 1;
|
|
break;
|
|
case OPT_FROM:
|
|
from = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(from == -1 || type != ARG_DATE,
|
|
_("invalid date: %s"), optarg);
|
|
query_range = 1;
|
|
break;
|
|
case OPT_TO:
|
|
to = parse_datetimearg(optarg, &type);
|
|
EXIT_IF(to == -1 || type != ARG_DATE,
|
|
_("invalid date: %s"), optarg);
|
|
query_range = 1;
|
|
break;
|
|
case OPT_DAYS:
|
|
range = atoi(optarg);
|
|
EXIT_IF(range == 0, _("invalid range: %s"), optarg);
|
|
query_range = 1;
|
|
break;
|
|
case OPT_FMT_APT:
|
|
fmt_apt = optarg;
|
|
format_opt = 1;
|
|
break;
|
|
case OPT_FMT_RAPT:
|
|
fmt_rapt = optarg;
|
|
format_opt = 1;
|
|
break;
|
|
case OPT_FMT_EV:
|
|
fmt_ev = optarg;
|
|
format_opt = 1;
|
|
break;
|
|
case OPT_FMT_REV:
|
|
fmt_rev = optarg;
|
|
format_opt = 1;
|
|
break;
|
|
case OPT_FMT_TODO:
|
|
fmt_todo = optarg;
|
|
format_opt = 1;
|
|
break;
|
|
case OPT_DUMP_IMPORTED:
|
|
dump_imported = 1;
|
|
break;
|
|
case OPT_EXPORT_UID:
|
|
export_uid = 1;
|
|
break;
|
|
case OPT_READ_ONLY:
|
|
read_only = 1;
|
|
break;
|
|
case OPT_STATUS:
|
|
status = 1;
|
|
break;
|
|
case OPT_DAEMON:
|
|
EXIT_IF(cpid = io_get_pid(path_cpid),
|
|
_("calcurse is running (pid = %d)"), cpid);
|
|
daemon = 1;
|
|
filter.type_mask = TYPE_MASK_APPT | TYPE_MASK_RECUR_APPT;
|
|
break;
|
|
case OPT_INPUT_DATEFMT:
|
|
conf.input_datefmt = atoi(optarg);
|
|
EXIT_IF(conf.input_datefmt < 1 || conf.input_datefmt > 4,
|
|
_("invalid input date format: %s"), optarg);
|
|
cmd_line = 1;
|
|
break;
|
|
case OPT_OUTPUT_DATEFMT:
|
|
time(&t);
|
|
localtime_r(&t, &tm);
|
|
EXIT_IF(!strftime(buf, sizeof(buf), optarg, &tm),
|
|
_("invalid output date format: %s"), optarg);
|
|
strncpy(conf.output_datefmt, optarg,
|
|
sizeof(conf.output_datefmt) - 1);
|
|
conf.output_datefmt[sizeof(conf.output_datefmt) - 1] =
|
|
'\0';
|
|
cmd_line = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
usage_try();
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (filter.type_mask == 0)
|
|
filter.type_mask = TYPE_MASK_ALL;
|
|
|
|
if (status + grep + query + next + gc + import + export + daemon > 1 ||
|
|
optind < argc ||
|
|
(filter_opt && !(grep + query + export)) ||
|
|
(format_opt && !(grep + query + dump_imported)) ||
|
|
(query_range && !query)) {
|
|
ERROR_MSG(_("invalid argument combination"));
|
|
usage();
|
|
usage_try();
|
|
goto cleanup;
|
|
}
|
|
|
|
EXIT_IF(to >= 0 && range, _("cannot specify a range and an end date"));
|
|
if (from == -1)
|
|
from = get_today();
|
|
if (to == -1)
|
|
to = ENDOFDAY(from);
|
|
EXIT_IF(to < from, _("end date cannot come before start date"));
|
|
if (range > 0)
|
|
to = date_sec_change(from, 0, range - 1);
|
|
else if (range < 0)
|
|
from = date_sec_change(to, 0, range + 1);
|
|
|
|
io_check_dir(path_ddir);
|
|
io_check_dir(path_notes);
|
|
io_check_dir(path_cdir);
|
|
io_check_dir(path_hooks);
|
|
|
|
if (status) {
|
|
status_arg();
|
|
} else if (grep) {
|
|
io_check_file(path_apts);
|
|
io_check_file(path_todo);
|
|
io_check_file(path_conf);
|
|
io_load_data(&filter, FORCE);
|
|
if (purge) {
|
|
io_save_todo(path_todo);
|
|
io_save_apts(path_apts);
|
|
} else {
|
|
/*
|
|
* Use default values for non-specified format strings.
|
|
*/
|
|
fmt_apt = fmt_apt ? fmt_apt : "%(raw)";
|
|
fmt_rapt = fmt_rapt ? fmt_rapt : "%(raw)";
|
|
fmt_ev = fmt_ev ? fmt_ev : "%(raw)";
|
|
fmt_rev = fmt_rev ? fmt_rev : "%(raw)";
|
|
fmt_todo = fmt_todo ? fmt_todo : "%(raw)";
|
|
|
|
io_dump_todo(fmt_todo);
|
|
io_dump_apts(fmt_apt, fmt_rapt, fmt_ev, fmt_rev);
|
|
}
|
|
} else if (query) {
|
|
io_check_file(path_apts);
|
|
io_check_file(path_todo);
|
|
io_check_file(path_conf);
|
|
io_load_data(&filter, FORCE);
|
|
|
|
/* Use default values for non-specified format strings. */
|
|
fmt_apt = fmt_apt ? fmt_apt : " - %S -> %E\n\t%m\n";
|
|
fmt_rapt = fmt_rapt ? fmt_rapt : " - %S -> %E\n\t%m\n";
|
|
fmt_ev = fmt_ev ? fmt_ev : " * %m\n";
|
|
fmt_rev = fmt_rev ? fmt_rev : " * %m\n";
|
|
fmt_todo = fmt_todo ? fmt_todo : "%p. %m\n";
|
|
|
|
int add_line = todo_arg(fmt_todo, &limit, &filter);
|
|
date_arg_from_to(from, to, add_line, fmt_apt, fmt_rapt, fmt_ev,
|
|
fmt_rev, &limit);
|
|
} else if (next) {
|
|
io_check_file(path_apts);
|
|
io_load_app(&filter);
|
|
next_arg();
|
|
} else if (gc) {
|
|
io_check_file(path_apts);
|
|
io_check_file(path_todo);
|
|
io_load_data(NULL, FORCE);
|
|
note_gc();
|
|
} else if (import) {
|
|
io_check_file(path_apts);
|
|
io_check_file(path_todo);
|
|
io_load_data(NULL, FORCE);
|
|
if (dump_imported) {
|
|
/*
|
|
* Use default values for non-specified format strings.
|
|
*/
|
|
fmt_apt = fmt_apt ? fmt_apt : "%(raw)";
|
|
fmt_rapt = fmt_rapt ? fmt_rapt : "%(raw)";
|
|
fmt_ev = fmt_ev ? fmt_ev : "%(raw)";
|
|
fmt_rev = fmt_rev ? fmt_rev : "%(raw)";
|
|
fmt_todo = fmt_todo ? fmt_todo : "%(raw)";
|
|
} else {
|
|
/*
|
|
* Do not dump items, unset format strings explicitly.
|
|
*/
|
|
fmt_apt = fmt_rapt = fmt_ev = fmt_rev = NULL;
|
|
fmt_todo = NULL;
|
|
}
|
|
io_import_data(IO_IMPORT_ICAL, ifile, fmt_ev, fmt_rev, fmt_apt,
|
|
fmt_rapt, fmt_todo);
|
|
io_save_apts(path_apts);
|
|
io_save_todo(path_todo);
|
|
} else if (export) {
|
|
io_check_file(path_apts);
|
|
io_check_file(path_todo);
|
|
io_load_data(&filter, FORCE);
|
|
io_export_data(xfmt, export_uid);
|
|
} else if (daemon) {
|
|
dmon_stop();
|
|
dmon_start(0);
|
|
} else if (!cmd_line) {
|
|
/* interactive mode */
|
|
non_interactive = 0;
|
|
}
|
|
|
|
cleanup:
|
|
/* Free filter parameters. */
|
|
if (filter.regex)
|
|
regfree(filter.regex);
|
|
|
|
return non_interactive;
|
|
}
|