Lars Henriksen 066df02cbf Introduce multiple days in the appointments panel
Overview of existing implementation
-----------------------------------

The APP panel displays the 'day_items' vector in the 'lb_apt' listbox. A
listbox consists of a scrollwin (structure) in which a number of items
is displayed. The listbox keeps track of:

    - the number of items
    - the selected item
    - the type of each item in an array type[]
    - the height of each item (ie. how many screen lines) in an array ch[]
    - how to display an item (on the screen)

The latter three are handled by functions fn_type(), fn_height(),
fn_draw(). The first two are used to fill in the corresponding array
entry, type[] or ch[], for item number i, the third draws item number i.

The items are taken from the global variables

    vector_t day_items
    int      day_items_nb

in day.c. Items include captions (DAY_HEADING, DAY_SEPARATOR).
Everything is sorted for display (DAY_HEADING, events, DAY_SEPARATOR,
appts).  These are filled in ("stored") [by day_store_items() for the
selected day in the calendar], before being "loaded" into the listbox.
See do_storage() in calcurse.c and ui_day_item_add() in ui-day.c.

New APP panel design
--------------------

Several days are displayed in the APP panel by loading them with
day_store_items().

With several days come several headings and separators. DAY_SEPARATOR is
reinterpreted to separate days, and a new separator, EVNT_SEPARATOR,
separates events from appointments. To sort everything, an 'order'
member of type time_t is added to the day_item structure. It is set for
headings and separators as well as for appointments and events as
follows:

    item            order
    ---------------------
    DAY_HEADING     BGNOFDAY (= midnight)
    EVNT_SEPARATOR  BGNOFDAY
    DAY_SEPARATOR   ENDOFDAY
    event           start time (midnight)
    appointment     start time (first day)
                    BGNOFDAY (following days, if any)

The sort function day_cmp() (used by vector_sort) is extended to sort by
order first.

The order field always indicates the day to which an item belongs. This
comes in handy, because with several days in the APP panel it is
necessary to distinguish between the selected day in the calendar and
the selected day in the APP panel.  This raises the question which day
should actions (commands) operate on: the one selected in the calendar
or the one selected in the APP panel? Unquestionably the one on the APP
panel which is the one tacitly implied. In most cases it is not a
problem, though, because actions work on the selected item and the
selected day does not come into play. But in some cases it does:

    delete item     When deleting an occurrence of a repeated item, the
                    selected day is the exception day to add.

    view item       day_popup_item() needs the day of the selected item
                    for display of correct start/end times.

    cut/paste item  Paste needs the selected day in which to paste.

    add item        The day of the new item is taken from the calendar.
                    Instead a dummy event is inserted in an empty day.
                    This makes the day selectable, which is otherwise
                    impossible with only the DAY_HEADING displayed.  The
                    dummy event is selectable but cannot be edited or
                    deleted (but viewed or piped).

With more than one day in the day_items vecter, an appointment spanning
more than one day may occur more than once in the vector (with start/end
times suitably adjusted for display). A day_item is no longer (always)
identified by the aptev_ptr (item) value. Instead the combination
(order, item.<ptr>) is used; order is roughly the day.

Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk>
Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
2019-05-22 01:56:59 -04:00

993 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, --confdir <dir> The configuration directory to use"));
printf("%s\n", _(" --daemon Run notification daemon in the background"));
printf("%s\n", _(" -D, --datadir <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;
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;
/* In minutes rounded up. */
min_left = time_left / MININSEC +
(time_left % MININSEC ? 1 : 0);
hours_left = min_left / HOURINMIN;
min_left = min_left % HOURINMIN;
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, &lt);
strftime(date_str, BUFSIZ, conf.output_datefmt, &lt);
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, 1);
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, grep_filter = 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'},
/* Deprecated */
{"conf", required_argument, NULL, 'C'},
{"confdir", required_argument, NULL, 'C'},
{"day", required_argument, NULL, 'd'},
{"datadir", required_argument, NULL, 'D'},
/* Deprecated */
{"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;
case 'c':
cfile = 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':
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':
grep_filter = 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':
filter.invert = 1;
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(&reg, optarg, REG_EXTENDED))
EXIT(_("could not compile regular expression: %s"), optarg);
filter.regex = &reg;
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) ||
(purge && !filter.invert)
) {
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 || grep_filter) {
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;
}