/* * Calcurse - text-based organizer * * Copyright (c) 2004-2012 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 #include "calcurse.h" /* Long options */ enum { OPT_FMT_APT = 1000, OPT_FMT_RAPT, OPT_FMT_EV, OPT_FMT_REV, OPT_FMT_TODO, OPT_READ_ONLY }; /* * 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-2012 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 (incompatible with '-D').\n" "\n -D , --directory \n" " specify the data directory to use (incompatible with '-c').\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 -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. If a priority number is given, then only todo * then only todo items that have this priority will be displayed. * If priority is < 0, all todos will be displayed. * If priority == 0, only completed tasks will be displayed. * If regex is not null, only the matching todos are printed. */ static void todo_arg(int priority, const char *format, regex_t * regex) { llist_item_t *i; int title = 1; const char *titlestr; const char *all_todos_title = _("to do:\n"); const char *completed_title = _("completed tasks:\n"); titlestr = priority == 0 ? completed_title : all_todos_title; #define DISPLAY_TITLE do { \ if (title) \ { \ fputs (titlestr, stdout); \ title = 0; \ } \ } while (0) LLIST_FOREACH(&todolist, i) { struct todo *todo = LLIST_TS_GET_DATA(i); if (regex && regexec(regex, todo->mesg, 0, 0, 0) != 0) continue; if (todo->id < 0) { /* completed task */ if (priority == 0) { DISPLAY_TITLE; print_todo(format, todo); } } else { if (priority < 0 || todo->id == priority) { DISPLAY_TITLE; print_todo(format, todo); } } } #undef DISPLAY_TITLE } /* 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]; time_t t; struct tm *lt; t = date; lt = localtime(&t); strftime(date_str, BUFSIZ, conf.output_datefmt, lt); fputs(date_str, stdout); fputs(":\n", stdout); } /* * Print appointments for given day and exit. * If no day is given, the given date is used. * If there is also no date given, current date is considered. * If regex is not null, only the matching appointments or events are printed. */ static int app_arg(int add_line, struct date *day, long date, const char *fmt_apt, const char *fmt_rapt, const char *fmt_ev, const char *fmt_rev, regex_t * regex) { llist_item_t *i, *j; long today; unsigned print_date = 1; int app_found = 0; if (date == 0) today = get_sec_date(*day); else today = date; /* * Calculate and print the selected date if there is an event for * that date and it is the first one, and then print all the events for * that date. */ LLIST_FIND_FOREACH(&recur_elist, today, recur_event_inday, i) { struct recur_event *re = LLIST_GET_DATA(i); if (regex && regexec(regex, re->mesg, 0, 0, 0) != 0) continue; app_found = 1; if (add_line) { fputs("\n", stdout); add_line = 0; } if (print_date) { arg_print_date(today); print_date = 0; } print_recur_event(fmt_rev, today, re); } LLIST_FIND_FOREACH_CONT(&eventlist, today, event_inday, i) { struct event *ev = LLIST_TS_GET_DATA(i); if (regex && regexec(regex, ev->mesg, 0, 0, 0) != 0) continue; app_found = 1; if (add_line) { fputs("\n", stdout); add_line = 0; } if (print_date) { arg_print_date(today); print_date = 0; } print_event(fmt_ev, today, ev); } /* Same process is performed but this time on the appointments. */ LLIST_TS_LOCK(&alist_p); LLIST_TS_LOCK(&recur_alist_p); /* * Iterate over regular appointments and recurrent ones simultaneously (fixes * http://lists.calcurse.org/bugs/msg00002.html). */ i = LLIST_TS_FIND_FIRST(&alist_p, today, apoint_inday); j = LLIST_TS_FIND_FIRST(&recur_alist_p, today, recur_apoint_inday); while (i || j) { struct apoint *apt = LLIST_TS_GET_DATA(i); struct recur_apoint *ra = LLIST_TS_GET_DATA(j); unsigned occurrence; while (i && regex && regexec(regex, apt->mesg, 0, 0, 0) != 0) { i = LLIST_TS_FIND_NEXT(i, today, apoint_inday); apt = LLIST_TS_GET_DATA(i); } while (j && regex && regexec(regex, ra->mesg, 0, 0, 0) != 0) { j = LLIST_TS_FIND_NEXT(j, today, recur_apoint_inday); ra = LLIST_TS_GET_DATA(j); } if (apt && ra) { if (recur_apoint_find_occurrence(ra, today, &occurrence) && apt->start <= occurrence) ra = NULL; else apt = NULL; } if (apt) { app_found = 1; if (add_line) { fputs("\n", stdout); add_line = 0; } if (print_date) { arg_print_date(today); print_date = 0; } print_apoint(fmt_apt, today, apt); i = LLIST_TS_FIND_NEXT(i, today, apoint_inday); } else if (ra) { app_found = 1; if (add_line) { fputs("\n", stdout); add_line = 0; } if (print_date) { arg_print_date(today); print_date = 0; } recur_apoint_find_occurrence(ra, today, &occurrence); print_recur_apoint(fmt_rapt, today, occurrence, ra); apt = NULL; j = LLIST_TS_FIND_NEXT(j, today, recur_apoint_inday); } } LLIST_TS_UNLOCK(&recur_alist_p); LLIST_TS_UNLOCK(&alist_p); return app_found; } /* * For a given date, print appointments for each day * in the chosen interval. app_found and add_line are used * to format the output correctly. */ static void display_app(struct tm *t, int numdays, int add_line, const char *fmt_apt, const char *fmt_rapt, const char *fmt_ev, const char *fmt_rev, regex_t * regex) { int i, app_found; struct date day; for (i = 0; i < numdays; i++) { day.dd = t->tm_mday; day.mm = t->tm_mon + 1; day.yyyy = t->tm_year + 1900; app_found = app_arg(add_line, &day, 0, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, regex); if (app_found) add_line = 1; t->tm_mday++; mktime(t); } } /* * Print appointment for the given date or for the given n upcoming * days. */ static void date_arg(const char *ddate, int add_line, const char *fmt_apt, const char *fmt_rapt, const char *fmt_ev, const char *fmt_rev, regex_t * regex) { int i; struct date day; int numdays = 0, num_digit = 0; int arg_len = 0; static struct tm t; time_t timer; /* * Check (with the argument length) if a date or a number of days * was entered, and then call app_arg() to print appointments */ arg_len = strlen(ddate); if (arg_len <= 4) { /* a number of days was entered */ for (i = 0; i <= arg_len - 1; i++) { if (isdigit(ddate[i])) num_digit++; } if (num_digit == arg_len) numdays = atoi(ddate); /* * Get current date, and print appointments for each day * in the chosen interval. app_found and add_line are used * to format the output correctly. */ timer = time(NULL); t = *localtime(&timer); display_app(&t, numdays, add_line, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, regex); } else { /* a date was entered */ if (parse_date(ddate, conf.input_datefmt, (int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL)) { app_arg(add_line, &day, 0, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, regex); } else { fputs(_("Argument to the '-d' flag is not valid\n"), stderr); fprintf(stdout, _("Possible argument format are: '%s' or 'n'\n"), DATEFMT_DESC(conf.input_datefmt)); more_info(); } } } /* * Print appointment from the given date 'startday' for the 'range' upcoming * days. * If no starday is given (NULL), today is considered * If no range is given (NULL), 1 day is considered * * Many thanks to Erik Saule for providing this function. */ static void date_arg_extended(const char *startday, const char *range, int add_line, const char *fmt_apt, const char *fmt_rapt, const char *fmt_ev, const char *fmt_rev, regex_t * regex) { int i, numdays = 1, error = 0, arg_len = 0; static struct tm t; time_t timer; /* * Check arguments and extract information */ if (range != NULL) { arg_len = strlen(range); for (i = 0; i <= arg_len - 1; i++) { if (!isdigit(range[i])) error = 1; } if (!error) numdays = atoi(range); } timer = time(NULL); t = *localtime(&timer); if (startday != NULL) { if (parse_date(startday, conf.input_datefmt, (int *)&t.tm_year, (int *)&t.tm_mon, (int *)&t.tm_mday, NULL)) { t.tm_year -= 1900; t.tm_mon--; mktime(&t); } else { error = 1; } } if (!error) { display_app(&t, numdays, add_line, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, regex); } else { fputs(_("Argument is not valid\n"), stderr); fprintf(stdout, _("Argument format for -s and --startday is: '%s'\n"), DATEFMT_DESC(conf.input_datefmt)); fputs(_("Argument format for -r and --range is: 'n'\n"), stdout); more_info(); } } /* * 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) { int ch, add_line = 0; int unknown_flag = 0; /* Command-line flags */ int aflag = 0; /* -a: print appointments for current day */ int cflag = 0; /* -c: specify the calendar file to use */ int dflag = 0; /* -d: print appointments for a specified days */ int Dflag = 0; /* -D: specify data directory to use */ int hflag = 0; /* -h: print help text */ int gflag = 0; /* -g: run garbage collector */ int iflag = 0; /* -i: import data */ int nflag = 0; /* -n: print next appointment */ int rflag = 0; /* -r: specify the range of days to consider */ int sflag = 0; /* -s: specify the first day to consider */ int Sflag = 0; /* -S: specify a regex to search for */ int tflag = 0; /* -t: print todo list */ int vflag = 0; /* -v: print version number */ int xflag = 0; /* -x: export data */ /* 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"; int tnum = 0, xfmt = 0, non_interactive = 0, multiple_flag = 0, load_data = 0; const char *ddate = "", *cfile = NULL, *range = NULL, *startday = NULL; const char *datadir = NULL, *ifile = NULL; regex_t reg, *preg = NULL; /* Long options only */ int statusflag = 0; /* --status: get the status of running instances */ enum { STATUS_OPT = CHAR_MAX + 1 }; static const char *optstr = "ghvnNax::t::d:c:r::s::S:D:i:"; 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'}, {"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'}, {"status", no_argument, NULL, STATUS_OPT}, {"todo", optional_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {"export", optional_argument, NULL, 'x'}, {"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}, {NULL, no_argument, NULL, 0} }; while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) { switch (ch) { case STATUS_OPT: statusflag = 1; break; case 'a': aflag = 1; multiple_flag++; load_data++; break; case 'c': cflag = 1; multiple_flag++; cfile = optarg; load_data++; break; case 'd': dflag = 1; multiple_flag++; load_data++; ddate = optarg; break; case 'D': Dflag = 1; datadir = optarg; break; case 'h': hflag = 1; break; case 'g': gflag = 1; break; case 'i': iflag = 1; multiple_flag++; load_data++; ifile = optarg; break; case 'n': nflag = 1; multiple_flag++; load_data++; break; case 'r': rflag = 1; multiple_flag++; load_data++; range = optarg; break; case 's': sflag = 1; multiple_flag++; load_data++; startday = optarg; break; case 'S': EXIT_IF(Sflag > 0, _("Can not handle more than one regular expression.")); Sflag = 1; if (regcomp(®, optarg, REG_EXTENDED)) EXIT(_("Could not compile regular expression.")); preg = ® break; case 't': tflag = 1; multiple_flag++; load_data++; add_line = 1; if (optarg != NULL) { tnum = atoi(optarg); if (tnum < 0 || tnum > 9) { usage(); usage_try(); return EXIT_FAILURE; } } else tnum = -1; break; case 'v': vflag = 1; break; case 'x': xflag = 1; multiple_flag++; load_data++; if (optarg != NULL) { if (strcmp(optarg, "ical") == 0) xfmt = IO_EXPORT_ICAL; else if (strcmp(optarg, "pcal") == 0) xfmt = IO_EXPORT_PCAL; else { fputs(_("Argument for '-x' should be either " "'ical' or 'pcal'\n"), stderr); usage(); usage_try(); return EXIT_FAILURE; } } else { xfmt = IO_EXPORT_ICAL; } 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; default: usage(); usage_try(); unknown_flag = 1; non_interactive = 1; /* NOTREACHED */ } } argc -= optind; if (argc >= 1) { usage(); usage_try(); return EXIT_FAILURE; /* Incorrect arguments */ } else if (Dflag && cflag) { fputs(_("Options '-D' and '-c' cannot be used at the same time\n"), stderr); usage(); usage_try(); return EXIT_FAILURE; } else if (Sflag && !(aflag || dflag || rflag || sflag || tflag)) { fputs(_("Option '-S' must be used with either '-d', '-r', '-s', " "'-a' or '-t'\n"), stderr); usage(); usage_try(); return EXIT_FAILURE; } else { if (unknown_flag) { non_interactive = 1; } else if (hflag) { help_arg(); non_interactive = 1; } else if (vflag) { version_arg(); non_interactive = 1; } else if (statusflag) { io_init(cfile, datadir); status_arg(); non_interactive = 1; } else if (gflag) { io_init(cfile, datadir); io_check_dir(path_dir, NULL); io_check_dir(path_notes, NULL); io_check_file(path_apts, NULL); io_check_file(path_todo, NULL); io_load_app(); io_load_todo(); note_gc(); non_interactive = 1; } else if (multiple_flag) { if (load_data) { io_init(cfile, datadir); io_check_dir(path_dir, NULL); io_check_dir(path_notes, NULL); } if (iflag) { io_check_file(path_apts, NULL); io_check_file(path_todo, NULL); /* Get default pager in case we need to show a log file. */ vars_init(); io_load_app(); io_load_todo(); io_import_data(IO_IMPORT_ICAL, ifile); io_save_apts(); io_save_todo(); non_interactive = 1; } if (xflag) { io_check_file(path_apts, NULL); io_check_file(path_todo, NULL); io_load_app(); io_load_todo(); io_export_data(xfmt); non_interactive = 1; return non_interactive; } if (tflag) { io_check_file(path_todo, NULL); io_load_todo(); todo_arg(tnum, fmt_todo, preg); non_interactive = 1; } if (nflag) { io_check_file(path_apts, NULL); io_load_app(); next_arg(); non_interactive = 1; } if (dflag || rflag || sflag) { io_check_file(path_apts, NULL); io_check_file(path_conf, NULL); io_load_app(); config_load(); /* To get output date format. */ if (dflag) date_arg(ddate, add_line, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, preg); if (rflag || sflag) date_arg_extended(startday, range, add_line, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, preg); non_interactive = 1; } else if (aflag) { struct date day; io_check_file(path_apts, NULL); io_check_file(path_conf, NULL); vars_init(); config_load(); /* To get output date format. */ io_load_app(); day.dd = day.mm = day.yyyy = 0; app_arg(add_line, &day, 0, fmt_apt, fmt_rapt, fmt_ev, fmt_rev, preg); non_interactive = 1; } } else { non_interactive = 0; io_init(cfile, datadir); } } if (preg) regfree(preg); return non_interactive; }