/* * Calcurse - text-based organizer * * Copyright (c) 2004-2013 calcurse Development Team * 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 #include #include #include #include #include #include #include "calcurse.h" /* Long options */ enum { OPT_FILTER_TYPE = 1000, OPT_FILTER_PATTERN, OPT_FILTER_START_FROM, OPT_FILTER_START_TO, OPT_FILTER_START_AFTER, OPT_FILTER_START_BEFORE, OPT_FILTER_END_FROM, OPT_FILTER_END_TO, OPT_FILTER_END_AFTER, OPT_FILTER_END_BEFORE, 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_READ_ONLY, OPT_STATUS }; /* * Print Calcurse usage and exit. */ static void usage(void) { const char *arg_usage = _("Usage: calcurse [-g|-h|-v] [-an] [-t[num]] [-i] [-x[format]]\n" " [-d |] [-s[date]] [-r[range]]\n" " [-c] [-D] [-S] [--status]\n" " [--read-only]\n"); fputs(arg_usage, stdout); } static void usage_try(void) { const char *arg_usage_try = _("Try 'calcurse -h' for more information.\n"); fputs(arg_usage_try, stdout); } /* * Print Calcurse version with a short copyright text and exit. */ static void version_arg(void) { const char *vtext = _("\nCopyright (c) 2004-2013 calcurse Development Team.\n" "This is free software; see the source for copying conditions.\n"); fprintf(stdout, _("Calcurse %s - text-based organizer\n"), VERSION); fputs(vtext, stdout); } static void more_info(void) { fputs(_("\nFor more information, type '?' from within Calcurse, " "or read the manpage.\n"), stdout); fputs(_("Mail feature requests and suggestions to .\n"), stdout); fputs(_("Mail bug reports to .\n"), stdout); } /* * Print the command line options and exit. */ static void help_arg(void) { const char *htext = _("\nMiscellaneous:\n" " -h, --help\n" " print this help and exit.\n" "\n -v, --version\n" " print calcurse version and exit.\n" "\n --status\n" " display the status of running instances of calcurse.\n" "\n --read-only\n" " Don't save configuration nor appointments/todos. Use with care.\n" "\nFiles:\n" " -c , --calendar \n" " specify the calendar to use (has precedence over '-D').\n" "\n -D , --directory \n" " specify the data directory to use.\n" "\tIf not specified, the default directory is ~/.calcurse\n" "\nNon-interactive:\n" " -a, --appointment\n" " print events and appointments for current day and exit.\n" "\n -d , --day \n" " print events and appointments for or upcoming days and" "\n\texit. To specify both a starting date and a range, use the\n" "\t'--startday' and the '--range' option.\n" "\n -g, --gc\n" " run the garbage collector for note files and exit. \n" "\n -i , --import \n" " import the icalendar data contained in . \n" "\n -n, --next\n" " print next appointment within upcoming 24 hours " "and exit. Also given\n\tis the remaining time before this " "next appointment.\n" "\n -l , --limit \n" " only print information regarding the next items. \n" "\n -r[num], --range[=num]\n" " print events and appointments for the [num] number of days" "\n\tand exit. If no [num] is given, a range of 1 day is considered.\n" "\n -s[date], --startday[=date]\n" " print events and appointments from [date] and exit.\n" "\tIf no [date] is given, the current day is considered.\n" "\n -S, --search=\n" " search for the given regular expression within events, appointments,\n" "\tand todos description.\n" "\n -t[num], --todo[=num]\n" " print todo list and exit. If the optional number [num] is given,\n" "\tthen only todos having a priority equal to [num] will be returned.\n" "\tThe priority number must be between 1 (highest) and 9 (lowest).\n" "\tIt is also possible to specify '0' for the priority, in which case\n" "\tonly completed tasks will be shown.\n" "\n -x[format], --export[=format]\n" " export user data to the specified format. Events, appointments and\n" "\ttodos are converted and echoed to stdout.\n" "\tTwo possible formats are available: 'ical' and 'pcal'.\n" "\tIf the optional argument format is not given, ical format is\n" "\tselected by default.\n" "\tnote: redirect standard output to export data to a file,\n" "\tby issuing a command such as: calcurse --export > calcurse.dat\n"); fprintf(stdout, _("Calcurse %s - text-based organizer\n"), VERSION); usage(); fputs(htext, stdout); more_info(); } /* * 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\n")); } /* Print TODO list and exit. */ static void 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; llist_item_t *i; LLIST_FOREACH(&todolist, i) { if (*limit == 0) return; struct todo *todo = LLIST_TS_GET_DATA(i); if (title) { fputs(titlestr, stdout); title = 0; } print_todo(format, todo); (*limit)--; } } /* 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 = current_time + DAYINSEC; 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, const char *fmt_apt, const char *fmt_rapt, const char *fmt_ev, const char *fmt_rev, int *limit) { long date; int add_line = 0; for (date = from; date < to; date += DAYINSEC) { 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; } } static int parse_datearg(const char *str) { struct date day; if (parse_date(str, DATEFMT_YYYYMMDD, (int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL)) return date2sec(day, 0, 0); if (parse_date(str, DATEFMT_MMDDYYYY, (int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL)) return date2sec(day, 0, 0); if (parse_date(str, DATEFMT_ISO, (int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL)) return date2sec(day, 0, 0); return -1; } 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. */ int parse_args(int argc, char **argv) { /* Command-line flags */ int status = 0, query = 0, next = 0, gc = 0, import = 0, export = 0; /* Query ranges */ long from = -1, range = -1, to = -1; int limit = INT_MAX; /* Filters */ struct item_filter filter = { 0, NULL, -1, -1, -1, -1, 0, 0, 0 }; /* Format strings */ const char *fmt_apt = " - %S -> %E\n\t%m\n"; const char *fmt_rapt = " - %S -> %E\n\t%m\n"; const char *fmt_ev = " * %m\n"; const char *fmt_rev = " * %m\n"; const char *fmt_todo = "%p. %m\n"; /* Import and export parameters */ int xfmt = IO_EXPORT_ICAL; /* Data file locations */ const char *cfile = NULL, *datadir = NULL, *ifile = NULL; int non_interactive = 1; int ch; regex_t reg; static const char *optstr = "ghvnNax::t::d:c:r::s::S:D:i:l:Q"; struct option longopts[] = { {"appointment", no_argument, NULL, 'a'}, {"calendar", required_argument, NULL, 'c'}, {"day", required_argument, NULL, 'd'}, {"directory", required_argument, NULL, 'D'}, {"gc", 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'}, {"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'}, {"query", optional_argument, NULL, 'Q'}, {"filter-type", required_argument, NULL, OPT_FILTER_TYPE}, {"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-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-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}, {"read-only", no_argument, NULL, OPT_READ_ONLY}, {"status", no_argument, NULL, OPT_STATUS}, {NULL, no_argument, NULL, 0} }; 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; break; case 'd': if (is_all_digit(optarg)) { range = atoi(optarg); } else { from = parse_datearg(optarg); EXIT_IF(from == -1, _("invalid date: %s"), optarg); } filter.type_mask |= TYPE_MASK_CAL; query = 1; break; case 'D': datadir = optarg; break; case 'h': help_arg(); goto cleanup; case 'g': gc = 1; break; case 'i': import = 1; ifile = optarg; break; case 'l': limit = atoi(optarg); break; case 'n': next = 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': from = parse_datearg(optarg); EXIT_IF(from == -1, _("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; 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': query = 1; break; case OPT_FILTER_TYPE: filter.type_mask = parse_type_mask(optarg); EXIT_IF(filter.type_mask == 0, _("invalid filter mask")); break; case 'S': case OPT_FILTER_PATTERN: EXIT_IF(filter.regex, _("Can not handle more than one regular expression.")); if (regcomp(®, optarg, REG_EXTENDED)) EXIT(_("Could not compile regular expression.")); filter.regex = ® break; case OPT_FILTER_START_FROM: filter.start_from = parse_datearg(optarg); EXIT_IF(filter.start_from == -1, _("invalid filter start date")); break; case OPT_FILTER_START_TO: filter.start_to = parse_datearg(optarg); EXIT_IF(filter.start_to == -1, _("invalid filter end date")); break; case OPT_FILTER_START_AFTER: filter.start_from = parse_datearg(optarg) + 1; EXIT_IF(filter.start_from == -1, _("invalid filter start date")); break; case OPT_FILTER_START_BEFORE: filter.start_to = parse_datearg(optarg) - 1; EXIT_IF(filter.start_to == -1, _("invalid filter end date")); break; case OPT_FILTER_END_FROM: filter.end_from = parse_datearg(optarg); EXIT_IF(filter.end_from == -1, _("invalid filter start date")); break; case OPT_FILTER_END_TO: filter.end_to = parse_datearg(optarg); EXIT_IF(filter.end_to == -1, _("invalid filter end date")); break; case OPT_FILTER_END_AFTER: filter.end_from = parse_datearg(optarg) + 1; EXIT_IF(filter.end_from == -1, _("invalid filter start date")); break; case OPT_FILTER_END_BEFORE: filter.end_to = parse_datearg(optarg) - 1; EXIT_IF(filter.end_to == -1, _("invalid filter end date")); break; case OPT_FILTER_PRIORITY: filter.priority = atoi(optarg); EXIT_IF(filter.priority < 1 || filter.priority > 9, _("invalid priority")); break; case OPT_FILTER_COMPLETED: filter.completed = 1; break; case OPT_FILTER_UNCOMPLETED: filter.uncompleted = 1; break; case OPT_FROM: from = parse_datearg(optarg); EXIT_IF(from == -1, _("invalid start date")); break; case OPT_TO: to = parse_datearg(optarg); EXIT_IF(to == -1, _("invalid end date")); break; case OPT_DAYS: range = atoi(optarg); EXIT_IF(range == 0, _("invalid range")); break; case OPT_FMT_APT: fmt_apt = optarg; break; case OPT_FMT_RAPT: fmt_rapt = optarg; break; case OPT_FMT_EV: fmt_ev = optarg; break; case OPT_FMT_REV: fmt_rev = optarg; break; case OPT_FMT_TODO: fmt_todo = optarg; break; case OPT_READ_ONLY: read_only = 1; break; case OPT_STATUS: status = 1; break; default: usage(); usage_try(); goto cleanup; } } argc -= optind; if (filter.type_mask == 0) filter.type_mask = TYPE_MASK_ALL; if (status + query + next + gc + import + export > 1) { ERROR_MSG(_("invalid argument combination")); usage(); usage_try(); goto cleanup; } if (from == -1) { struct date day = { 0, 0, 0 }; from = get_sec_date(day); } if (to == -1 && range == -1) to = from + DAYINSEC; else if (to == -1 && range >= 0) to = from + range * DAYINSEC; else if (to >= 0 && range >= 0) EXIT_IF(to >= 0, _("cannot specify a range and an end date")); io_init(cfile, datadir); io_check_dir(path_dir); io_check_dir(path_notes); if (status) { status_arg(); } else if (query) { io_check_file(path_apts); io_check_file(path_todo); io_check_file(path_conf); vars_init(); config_load(); /* To get output date format. */ io_load_app(&filter); io_load_todo(&filter); date_arg_from_to(from, to, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, &limit); todo_arg(fmt_todo, &limit, &filter); } 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_app(NULL); io_load_todo(NULL); note_gc(); } else if (import) { io_check_file(path_apts); io_check_file(path_todo); /* Get default pager in case we need to show a log file. */ vars_init(); io_load_app(NULL); io_load_todo(NULL); io_import_data(IO_IMPORT_ICAL, ifile); 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_app(&filter); io_load_todo(&filter); io_export_data(xfmt); } else { /* interactive mode */ non_interactive = 0; } cleanup: /* Free filter parameters. */ if (filter.regex) regfree(filter.regex); return non_interactive; }