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>
993 lines
28 KiB
C
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, <);
|
|
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, 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(®, 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) ||
|
|
(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;
|
|
}
|