The computation never really worked before and it seems like the feature is not very helpful, sometimes even confusing (see GitHub issue #21). The macro ISLEAP is moved to calcurse.h. Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk> Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
1830 lines
38 KiB
C
1830 lines
38 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 <time.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <termios.h>
|
|
|
|
#include "calcurse.h"
|
|
#include "sha1.h"
|
|
|
|
#define FS_EXT_MAXLEN 64
|
|
|
|
enum format_specifier {
|
|
FS_STARTDATE,
|
|
FS_DURATION,
|
|
FS_ENDDATE,
|
|
FS_REMAINING,
|
|
FS_MESSAGE,
|
|
FS_NOTE,
|
|
FS_NOTEFILE,
|
|
FS_PRIORITY,
|
|
FS_RAW,
|
|
FS_HASH,
|
|
FS_PSIGN,
|
|
FS_EOF,
|
|
FS_UNKNOWN
|
|
};
|
|
|
|
/* General routine to exit calcurse properly. */
|
|
void exit_calcurse(int status)
|
|
{
|
|
int was_interactive;
|
|
|
|
ui_calendar_stop_date_thread();
|
|
io_stop_psave_thread();
|
|
|
|
if (ui_mode == UI_CURSES) {
|
|
notify_stop_main_thread();
|
|
clear();
|
|
wins_refresh();
|
|
endwin();
|
|
ui_mode = UI_CMDLINE;
|
|
was_interactive = 1;
|
|
} else {
|
|
was_interactive = 0;
|
|
}
|
|
|
|
free_user_data();
|
|
keys_free();
|
|
mem_stats();
|
|
|
|
if (was_interactive) {
|
|
if (unlink(path_cpid) != 0)
|
|
EXIT(_("Could not remove calcurse lock file: %s\n"),
|
|
strerror(errno));
|
|
if (dmon.enable)
|
|
dmon_start(status);
|
|
}
|
|
|
|
exit(status);
|
|
}
|
|
|
|
void free_user_data(void)
|
|
{
|
|
unsigned i;
|
|
|
|
day_free_vector();
|
|
event_llist_free();
|
|
apoint_llist_free();
|
|
recur_apoint_llist_free();
|
|
recur_event_llist_free();
|
|
for (i = 0; i <= 37; i++)
|
|
ui_day_item_cut_free(i);
|
|
todo_free_list();
|
|
notify_free_app();
|
|
}
|
|
|
|
/* Function to exit on internal error. */
|
|
void fatalbox(const char *errmsg)
|
|
{
|
|
WINDOW *errwin;
|
|
const char *label = _("/!\\ INTERNAL ERROR /!\\");
|
|
const char *reportmsg = _("Please report the following bug:");
|
|
const int WINROW = 10;
|
|
const int WINCOL = col - 2;
|
|
const int MSGLEN = WINCOL - 2;
|
|
char msg[MSGLEN];
|
|
|
|
if (errmsg == NULL)
|
|
return;
|
|
|
|
strncpy(msg, errmsg, MSGLEN);
|
|
msg[MSGLEN - 1] = '\0';
|
|
errwin =
|
|
newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2);
|
|
custom_apply_attr(errwin, ATTR_HIGHEST);
|
|
box(errwin, 0, 0);
|
|
wins_show(errwin, label);
|
|
mvwaddstr(errwin, 3, 1, reportmsg);
|
|
mvwaddstr(errwin, 5, (WINCOL - strlen(msg)) / 2, msg);
|
|
custom_remove_attr(errwin, ATTR_HIGHEST);
|
|
wins_wrefresh(errwin);
|
|
keys_wait_for_any_key(errwin);
|
|
delwin(errwin);
|
|
wins_doupdate();
|
|
}
|
|
|
|
void warnbox(const char *msg)
|
|
{
|
|
WINDOW *warnwin;
|
|
const char *label = "/!\\";
|
|
const int WINROW = 10;
|
|
const int WINCOL = col - 2;
|
|
const int MSGLEN = WINCOL - 2;
|
|
char displmsg[MSGLEN];
|
|
|
|
if (msg == NULL)
|
|
return;
|
|
|
|
strncpy(displmsg, msg, MSGLEN);
|
|
displmsg[MSGLEN - 1] = '\0';
|
|
warnwin =
|
|
newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2);
|
|
custom_apply_attr(warnwin, ATTR_HIGHEST);
|
|
box(warnwin, 0, 0);
|
|
wins_show(warnwin, label);
|
|
mvwaddstr(warnwin, 5, (WINCOL - strlen(displmsg)) / 2, displmsg);
|
|
custom_remove_attr(warnwin, ATTR_HIGHEST);
|
|
wins_wrefresh(warnwin);
|
|
keys_wait_for_any_key(warnwin);
|
|
delwin(warnwin);
|
|
wins_doupdate();
|
|
}
|
|
|
|
/*
|
|
* Print a message in the status bar.
|
|
* Message texts for first line and second line are to be provided.
|
|
*/
|
|
void status_mesg(const char *msg1, const char *msg2)
|
|
{
|
|
wins_erase_status_bar();
|
|
custom_apply_attr(win[STA].p, ATTR_HIGHEST);
|
|
mvwaddstr(win[STA].p, 0, 0, msg1);
|
|
mvwaddstr(win[STA].p, 1, 0, msg2);
|
|
custom_remove_attr(win[STA].p, ATTR_HIGHEST);
|
|
wins_wrefresh(win[STA].p);
|
|
}
|
|
|
|
/*
|
|
* Prompts the user to make a choice between named alternatives.
|
|
*
|
|
* The available choices are described by a string of the form
|
|
* "[ynp]". The first and last char are ignored (they are only here to
|
|
* make the translators' life easier), and every other char indicates
|
|
* a key the user is allowed to press.
|
|
*
|
|
* Returns the index of the key pressed by the user (starting from 1),
|
|
* or -1 if the user doesn't want to answer (e.g. by escaping).
|
|
*/
|
|
int status_ask_choice(const char *message, const char choice[],
|
|
int nb_choice)
|
|
{
|
|
/* "[4/2/f/t/w/.../Z] " */
|
|
char avail_choice[2 * nb_choice + 3];
|
|
int i, ch;
|
|
|
|
avail_choice[0] = '[';
|
|
for (i = 1; i <= nb_choice; i++) {
|
|
avail_choice[i * 2 - 1] = choice[i];
|
|
avail_choice[i * 2] = '/';
|
|
}
|
|
avail_choice[nb_choice * 2] = ']';
|
|
avail_choice[nb_choice * 2 + 1] = '\0';
|
|
|
|
status_mesg(message, avail_choice);
|
|
|
|
for (;;) {
|
|
ch = keys_wgetch(win[KEY].p);
|
|
for (i = 1; i <= nb_choice; i++)
|
|
if (ch == choice[i])
|
|
return i;
|
|
if (ch == ESCAPE)
|
|
return (-1);
|
|
if (resize) {
|
|
resize = 0;
|
|
wins_reset();
|
|
status_mesg(message, avail_choice);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prompts the user with a boolean question.
|
|
*
|
|
* Returns 1 if yes, 2 if no, and -1 otherwise
|
|
*/
|
|
int status_ask_bool(const char *msg)
|
|
{
|
|
return (status_ask_choice(msg, _("[yn]"), 2));
|
|
}
|
|
|
|
/*
|
|
* Prompts the user to make a choice between a number of alternatives.
|
|
*
|
|
* Returns the option chosen by the user (starting from 1), or -1 if
|
|
* the user doesn't want to answer.
|
|
*/
|
|
int
|
|
status_ask_simplechoice(const char *prefix, const char *choice[],
|
|
int nb_choice)
|
|
{
|
|
int i;
|
|
char *tmp;
|
|
/* "(1) Choice1, (2) Choice2, (3) Choice3?" */
|
|
char choicestr[BUFSIZ];
|
|
/* Holds the characters to choose from ('1', '2', etc) */
|
|
char char_choice[nb_choice + 2];
|
|
|
|
/* No need to initialize first and last char. */
|
|
for (i = 1; i <= nb_choice; i++)
|
|
char_choice[i] = '0' + i;
|
|
|
|
strcpy(choicestr, prefix);
|
|
|
|
for (i = 0; i < nb_choice; i++) {
|
|
asprintf(&tmp,
|
|
((i + 1) == nb_choice) ? "(%d) %s?" : "(%d) %s, ",
|
|
(i + 1), choice[i]);
|
|
strcat(choicestr, tmp);
|
|
mem_free(tmp);
|
|
}
|
|
|
|
return (status_ask_choice(choicestr, char_choice, nb_choice));
|
|
}
|
|
|
|
/* Erase part of a window. */
|
|
void
|
|
erase_window_part(WINDOW * win, int first_col, int first_row, int last_col,
|
|
int last_row)
|
|
{
|
|
int c, r;
|
|
|
|
for (r = first_row; r <= last_row; r++) {
|
|
for (c = first_col; c <= last_col; c++)
|
|
mvwaddstr(win, r, c, " ");
|
|
}
|
|
}
|
|
|
|
/* draws a popup window */
|
|
WINDOW *popup(int pop_row, int pop_col, int pop_y, int pop_x,
|
|
const char *title, const char *msg, int hint)
|
|
{
|
|
const char *any_key = _("Press any key to continue...");
|
|
WINDOW *popup_win;
|
|
const int MSGXPOS = 5;
|
|
|
|
popup_win = newwin(pop_row, pop_col, pop_y, pop_x);
|
|
keypad(popup_win, TRUE);
|
|
if (msg)
|
|
mvwaddstr(popup_win, MSGXPOS, (pop_col - strlen(msg)) / 2,
|
|
msg);
|
|
custom_apply_attr(popup_win, ATTR_HIGHEST);
|
|
box(popup_win, 0, 0);
|
|
wins_show(popup_win, title);
|
|
if (hint)
|
|
mvwaddstr(popup_win, pop_row - 2,
|
|
pop_col - (strlen(any_key) + 1), any_key);
|
|
custom_remove_attr(popup_win, ATTR_HIGHEST);
|
|
wins_wrefresh(popup_win);
|
|
|
|
return popup_win;
|
|
}
|
|
|
|
/* prints in middle of a panel */
|
|
void
|
|
print_in_middle(WINDOW * win, int starty, int startx, int width,
|
|
const char *string)
|
|
{
|
|
int len = strlen(string);
|
|
int x, y;
|
|
|
|
win = win ? win : stdscr;
|
|
getyx(win, y, x);
|
|
x = startx ? startx : x;
|
|
y = starty ? starty : y;
|
|
width = width ? width : 80;
|
|
|
|
x += (width - len) / 2;
|
|
|
|
custom_apply_attr(win, ATTR_HIGHEST);
|
|
mvwaddstr(win, y, x, string);
|
|
custom_remove_attr(win, ATTR_HIGHEST);
|
|
}
|
|
|
|
/* checks if a string is only made of digits */
|
|
int is_all_digit(const char *string)
|
|
{
|
|
for (; *string; string++) {
|
|
if (!isdigit((int)*string))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Given an item date expressed in seconds, return its start time in seconds. */
|
|
long get_item_time(long date)
|
|
{
|
|
return (long)(get_item_hour(date) * HOURINSEC +
|
|
get_item_min(date) * MININSEC);
|
|
}
|
|
|
|
int get_item_hour(long date)
|
|
{
|
|
struct tm lt;
|
|
|
|
localtime_r((time_t *) & date, <);
|
|
return lt.tm_hour;
|
|
}
|
|
|
|
int get_item_min(long date)
|
|
{
|
|
struct tm lt;
|
|
|
|
localtime_r((time_t *) & date, <);
|
|
return lt.tm_min;
|
|
}
|
|
|
|
struct tm date2tm(struct date day, unsigned hour, unsigned min)
|
|
{
|
|
time_t t = now();
|
|
struct tm start;
|
|
|
|
localtime_r(&t, &start);
|
|
|
|
start.tm_mon = day.mm - 1;
|
|
start.tm_mday = day.dd;
|
|
start.tm_year = day.yyyy - 1900;
|
|
start.tm_hour = hour;
|
|
start.tm_min = min;
|
|
start.tm_sec = 0;
|
|
start.tm_isdst = -1;
|
|
|
|
return start;
|
|
}
|
|
|
|
time_t date2sec(struct date day, unsigned hour, unsigned min)
|
|
{
|
|
struct tm start = date2tm(day, hour, min);
|
|
time_t t = mktime(&start);
|
|
|
|
EXIT_IF(t == -1, _("failure in mktime"));
|
|
|
|
return t;
|
|
}
|
|
|
|
time_t utcdate2sec(struct date day, unsigned hour, unsigned min)
|
|
{
|
|
char *tz;
|
|
time_t t;
|
|
|
|
tz = getenv("TZ");
|
|
if (tz)
|
|
tz = mem_strdup(tz);
|
|
setenv("TZ", "", 1);
|
|
tzset();
|
|
|
|
t = date2sec(day, hour, min);
|
|
|
|
if (tz) {
|
|
setenv("TZ", tz, 1);
|
|
mem_free(tz);
|
|
} else {
|
|
unsetenv("TZ");
|
|
}
|
|
tzset();
|
|
|
|
return t;
|
|
}
|
|
|
|
/* Compare two dates (without comparing times). */
|
|
int date_cmp_day(time_t d1, time_t d2)
|
|
{
|
|
struct tm lt1, lt2;
|
|
|
|
localtime_r((time_t *)&d1, <1);
|
|
localtime_r((time_t *)&d2, <2);
|
|
|
|
if (lt1.tm_year < lt2.tm_year)
|
|
return -1;
|
|
if (lt1.tm_year > lt2.tm_year)
|
|
return 1;
|
|
if (lt1.tm_mon < lt2.tm_mon)
|
|
return -1;
|
|
if (lt1.tm_mon > lt2.tm_mon)
|
|
return 1;
|
|
if (lt1.tm_mday < lt2.tm_mday)
|
|
return -1;
|
|
if (lt1.tm_mday > lt2.tm_mday)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Return a string containing the date, given a date in seconds. */
|
|
char *date_sec2date_str(long sec, const char *datefmt)
|
|
{
|
|
struct tm lt;
|
|
char *datestr = (char *)mem_calloc(BUFSIZ, sizeof(char));
|
|
|
|
if (sec == 0) {
|
|
strncpy(datestr, "0", BUFSIZ);
|
|
} else {
|
|
localtime_r((time_t *) & sec, <);
|
|
strftime(datestr, BUFSIZ, datefmt, <);
|
|
}
|
|
|
|
return datestr;
|
|
}
|
|
|
|
/* Generic function to format date. */
|
|
void date_sec2date_fmt(long sec, const char *fmt, char *datef)
|
|
{
|
|
#if ENABLE_NLS
|
|
/* TODO: Find a better way to deal with localization and strftime(). */
|
|
char *locale_old = mem_strdup(setlocale(LC_ALL, NULL));
|
|
setlocale(LC_ALL, "C");
|
|
#endif
|
|
|
|
struct tm lt;
|
|
localtime_r((time_t *) & sec, <);
|
|
strftime(datef, BUFSIZ, fmt, <);
|
|
|
|
#if ENABLE_NLS
|
|
setlocale(LC_ALL, locale_old);
|
|
mem_free(locale_old);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Used to change date by adding a certain amount of days or weeks.
|
|
* Returns 0 on success, 1 otherwise.
|
|
*/
|
|
int date_change(struct tm *date, int delta_month, int delta_day)
|
|
{
|
|
struct tm t;
|
|
|
|
t = *date;
|
|
t.tm_mon += delta_month;
|
|
t.tm_mday += delta_day;
|
|
|
|
if (mktime(&t) == -1) {
|
|
return 1;
|
|
} else {
|
|
*date = t;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Used to change date by adding a certain amount of days or weeks.
|
|
*/
|
|
long date_sec_change(long date, int delta_month, int delta_day)
|
|
{
|
|
struct tm lt;
|
|
time_t t;
|
|
|
|
t = date;
|
|
localtime_r(&t, <);
|
|
lt.tm_mon += delta_month;
|
|
lt.tm_mday += delta_day;
|
|
lt.tm_isdst = -1;
|
|
t = mktime(<);
|
|
EXIT_IF(t == -1, _("failure in mktime"));
|
|
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* Return a long containing the date which is updated taking into account
|
|
* the new time and date entered by the user.
|
|
*/
|
|
long update_time_in_date(long date, unsigned hr, unsigned mn)
|
|
{
|
|
struct tm lt;
|
|
time_t t, new_date;
|
|
|
|
t = date;
|
|
localtime_r(&t, <);
|
|
lt.tm_hour = hr;
|
|
lt.tm_min = mn;
|
|
lt.tm_sec = 0;
|
|
new_date = mktime(<);
|
|
EXIT_IF(new_date == -1, _("error in mktime"));
|
|
|
|
return new_date;
|
|
}
|
|
|
|
/*
|
|
* Returns the date in seconds from year 1900.
|
|
* If no date is entered, current date is chosen.
|
|
*/
|
|
time_t get_sec_date(struct date date)
|
|
{
|
|
struct tm ptrtime;
|
|
time_t timer;
|
|
char current_day[] = "dd ";
|
|
char current_month[] = "mm ";
|
|
char current_year[] = "yyyy ";
|
|
|
|
if (date.yyyy == 0 && date.mm == 0 && date.dd == 0) {
|
|
timer = time(NULL);
|
|
localtime_r(&timer, &ptrtime);
|
|
strftime(current_day, strlen(current_day), "%d", &ptrtime);
|
|
strftime(current_month, strlen(current_month), "%m", &ptrtime);
|
|
strftime(current_year, strlen(current_year), "%Y", &ptrtime);
|
|
date.mm = atoi(current_month);
|
|
date.dd = atoi(current_day);
|
|
date.yyyy = atoi(current_year);
|
|
}
|
|
|
|
return date2sec(date, 0, 0);
|
|
}
|
|
|
|
long min2sec(unsigned minutes)
|
|
{
|
|
return minutes * MININSEC;
|
|
}
|
|
|
|
/*
|
|
* Display a scroll bar when there are so many items that they
|
|
* can not be displayed inside the corresponding panel.
|
|
*/
|
|
void
|
|
draw_scrollbar(WINDOW * win, int y, int x, int length,
|
|
int bar_top, int bar_bottom, unsigned hilt)
|
|
{
|
|
mvwvline(win, bar_top, x, ACS_VLINE, bar_bottom - bar_top + 1);
|
|
if (hilt)
|
|
custom_apply_attr(win, ATTR_HIGHEST);
|
|
wattron(win, A_REVERSE);
|
|
mvwvline(win, y, x, ' ', length);
|
|
wattroff(win, A_REVERSE);
|
|
if (hilt)
|
|
custom_remove_attr(win, ATTR_HIGHEST);
|
|
}
|
|
|
|
/*
|
|
* Print an item (either an appointment, event, or todo) in a
|
|
* popup window. This is useful if an item description is too
|
|
* long to fit in its corresponding panel window.
|
|
*/
|
|
void
|
|
item_in_popup(const char *a_start, const char *a_end, const char *msg,
|
|
const char *pop_title)
|
|
{
|
|
WINDOW *popup_win, *pad;
|
|
const int margin_left = 4, margin_top = 4;
|
|
const int winl = row - 5, winw = col - margin_left;
|
|
const int padl = winl - 2, padw = winw - margin_left;
|
|
|
|
pad = newpad(padl, padw);
|
|
popup_win = popup(winl, winw, 1, 2, pop_title, NULL, 1);
|
|
if (a_start && a_end) {
|
|
mvwprintw(popup_win, margin_top, margin_left, "- %s -> %s",
|
|
a_start, a_end);
|
|
}
|
|
mvwaddstr(pad, 0, margin_left, msg);
|
|
wmove(win[STA].p, 0, 0);
|
|
pnoutrefresh(pad, 0, 0, margin_top + 2, margin_left, padl, winw);
|
|
wins_doupdate();
|
|
keys_wait_for_any_key(popup_win);
|
|
delwin(pad);
|
|
delwin(popup_win);
|
|
}
|
|
|
|
/* Returns the beginning of current day in seconds from 1900. */
|
|
time_t get_today(void)
|
|
{
|
|
struct tm lt;
|
|
time_t current_time;
|
|
struct date day;
|
|
|
|
current_time = time(NULL);
|
|
localtime_r(¤t_time, <);
|
|
day.mm = lt.tm_mon + 1;
|
|
day.dd = lt.tm_mday;
|
|
day.yyyy = lt.tm_year + 1900;
|
|
|
|
return date2sec(day, 0, 0);
|
|
}
|
|
|
|
/* Returns the current time in seconds. */
|
|
long now(void)
|
|
{
|
|
return (long)time(NULL);
|
|
}
|
|
|
|
char *nowstr(void)
|
|
{
|
|
struct tm lt;
|
|
static char buf[BUFSIZ];
|
|
time_t t = now();
|
|
|
|
localtime_r(&t, <);
|
|
strftime(buf, sizeof buf, "%a %b %d %T %Y", <);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* Print the given option value with appropriate color. */
|
|
void
|
|
print_bool_option_incolor(WINDOW * win, unsigned option, int pos_y,
|
|
int pos_x)
|
|
{
|
|
int color = 0;
|
|
const char *option_value;
|
|
|
|
if (option == 1) {
|
|
color = ATTR_TRUE;
|
|
option_value = _("yes");
|
|
} else if (option == 0) {
|
|
color = ATTR_FALSE;
|
|
option_value = _("no");
|
|
} else {
|
|
EXIT(_("option not defined"));
|
|
}
|
|
|
|
custom_apply_attr(win, color);
|
|
mvwaddstr(win, pos_y, pos_x, option_value);
|
|
custom_remove_attr(win, color);
|
|
wnoutrefresh(win);
|
|
wins_doupdate();
|
|
}
|
|
|
|
/*
|
|
* Get the name of the default directory for temporary files.
|
|
*/
|
|
const char *get_tempdir(void)
|
|
{
|
|
if (getenv("TMPDIR"))
|
|
return getenv("TMPDIR");
|
|
#ifdef P_tmpdir
|
|
else if (P_tmpdir)
|
|
return P_tmpdir;
|
|
#endif
|
|
else
|
|
return "/tmp";
|
|
}
|
|
|
|
/*
|
|
* Create a new unique file, and return a newly allocated string which contains
|
|
* the random part of the file name.
|
|
*/
|
|
char *new_tempfile(const char *prefix)
|
|
{
|
|
char *fullname;
|
|
int fd;
|
|
FILE *file;
|
|
|
|
if (prefix == NULL)
|
|
return NULL;
|
|
|
|
asprintf(&fullname, "%s.XXXXXX", prefix);
|
|
if ((fd = mkstemp(fullname)) == -1
|
|
|| (file = fdopen(fd, "w+")) == NULL) {
|
|
if (fd != -1) {
|
|
unlink(fullname);
|
|
close(fd);
|
|
}
|
|
ERROR_MSG(_("temporary file \"%s\" could not be created"),
|
|
fullname);
|
|
|
|
mem_free(fullname);
|
|
return NULL;
|
|
}
|
|
fclose(file);
|
|
|
|
return fullname;
|
|
}
|
|
|
|
static void get_ymd(int *year, int *month, int *day, time_t t)
|
|
{
|
|
struct tm tm;
|
|
|
|
localtime_r(&t, &tm);
|
|
*day = tm.tm_mday;
|
|
*month = tm.tm_mon + 1;
|
|
*year = tm.tm_year + 1900;
|
|
}
|
|
|
|
static void get_weekday_ymd(int *year, int *month, int *day, int weekday)
|
|
{
|
|
time_t t = get_today();
|
|
struct tm tm;
|
|
int delta;
|
|
|
|
localtime_r(&t, &tm);
|
|
delta = weekday - tm.tm_wday;
|
|
t = date_sec_change(t, 0, delta > 0 ? delta : 7);
|
|
|
|
localtime_r(&t, &tm);
|
|
*day = tm.tm_mday;
|
|
*month = tm.tm_mon + 1;
|
|
*year = tm.tm_year + 1900;
|
|
}
|
|
|
|
/*
|
|
* Check if a date is valid.
|
|
*/
|
|
int check_date(unsigned year, unsigned month, unsigned day)
|
|
{
|
|
return (year >= 1902 && month >= 1 && month <= 12 && day >= 1 &&
|
|
day <= days[month - 1] + (month == 2 && ISLEAP(year)) ? 1 : 0);
|
|
}
|
|
|
|
/*
|
|
* Convert a string containing a date into three integers containing the year,
|
|
* month and day.
|
|
*
|
|
* If a pointer to a date structure containing the current date is passed as
|
|
* last parameter ("slctd_date"), the function will accept several short forms,
|
|
* e.g. "26" for the 26th of the current month/year or "3/1" for Mar 01 (or Jan
|
|
* 03, depending on the date format) of the current year. If a null pointer is
|
|
* passed, short forms won't be accepted at all.
|
|
*
|
|
* Returns 1 if sucessfully converted or 0 if the string is an invalid date.
|
|
*/
|
|
int
|
|
parse_date(const char *date_string, enum datefmt datefmt, int *year,
|
|
int *month, int *day, struct date *slctd_date)
|
|
{
|
|
const char sep = (datefmt == DATEFMT_ISO) ? '-' : '/';
|
|
const char *p;
|
|
int in[3] = { 0, 0, 0 }, n = 0;
|
|
int d, m, y;
|
|
|
|
if (!date_string)
|
|
return 0;
|
|
|
|
if (!strcasecmp(date_string, "today")) {
|
|
get_ymd(year, month, day, get_today());
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "yesterday")) {
|
|
get_ymd(year, month, day, date_sec_change(get_today(), 0, -1));
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "tomorrow")) {
|
|
get_ymd(year, month, day, date_sec_change(get_today(), 0, 1));
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "now")) {
|
|
get_ymd(year, month, day, now());
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "sunday") ||
|
|
!strcasecmp(date_string, "sun")) {
|
|
get_weekday_ymd(year, month, day, 0);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "monday") ||
|
|
!strcasecmp(date_string, "mon")) {
|
|
get_weekday_ymd(year, month, day, 1);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "tuesday") ||
|
|
!strcasecmp(date_string, "tue")) {
|
|
get_weekday_ymd(year, month, day, 2);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "wednesday") ||
|
|
!strcasecmp(date_string, "wed")) {
|
|
get_weekday_ymd(year, month, day, 3);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "thursday") ||
|
|
!strcasecmp(date_string, "thu")) {
|
|
get_weekday_ymd(year, month, day, 4);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "friday") ||
|
|
!strcasecmp(date_string, "fri")) {
|
|
get_weekday_ymd(year, month, day, 5);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "saturday") ||
|
|
!strcasecmp(date_string, "sat")) {
|
|
get_weekday_ymd(year, month, day, 6);
|
|
return 1;
|
|
}
|
|
|
|
/* parse string into in[], read up to three integers */
|
|
for (p = date_string; *p; p++) {
|
|
if (*p == sep) {
|
|
if ((++n) > 2)
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
in[n] = in[n] * 10 + (int)(*p - '0');
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ((!slctd_date && n < 2) || in[n] == 0)
|
|
return 0;
|
|
|
|
/* convert into day, month and year, depending on the date format */
|
|
switch (datefmt) {
|
|
case DATEFMT_MMDDYYYY:
|
|
m = (n >= 1) ? in[0] : 0;
|
|
d = (n >= 1) ? in[1] : in[0];
|
|
y = in[2];
|
|
break;
|
|
case DATEFMT_DDMMYYYY:
|
|
d = in[0];
|
|
m = in[1];
|
|
y = in[2];
|
|
break;
|
|
case DATEFMT_YYYYMMDD:
|
|
case DATEFMT_ISO:
|
|
y = (n >= 2) ? in[n - 2] : 0;
|
|
m = (n >= 1) ? in[n - 1] : 0;
|
|
d = in[n];
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (slctd_date) {
|
|
if (y > 0 && y < 100) {
|
|
/* convert "YY" format into "YYYY" */
|
|
y += slctd_date->yyyy - slctd_date->yyyy % 100;
|
|
} else if (n < 2) {
|
|
/* set year and, optionally, month if short from is used */
|
|
y = slctd_date->yyyy;
|
|
if (n < 1)
|
|
m = slctd_date->mm;
|
|
}
|
|
}
|
|
|
|
/* check if date is valid, take leap years into account */
|
|
if (!check_date(y, m, d))
|
|
return 0;
|
|
|
|
if (year)
|
|
*year = y;
|
|
if (month)
|
|
*month = m;
|
|
if (day)
|
|
*day = d;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Convert a date duration string into a number of days.
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int parse_date_duration(const char *string, unsigned *days)
|
|
{
|
|
enum {
|
|
STATE_INITIAL,
|
|
STATE_WWDD_DD,
|
|
STATE_DONE
|
|
} state = STATE_INITIAL;
|
|
|
|
const char *p;
|
|
unsigned in = 0, frac = 0, denom = 1;
|
|
unsigned dur = 0;
|
|
|
|
if (!string || *string == '\0')
|
|
return 0;
|
|
|
|
/* parse string using a simple state machine */
|
|
for (p = string; *p; p++) {
|
|
if (state == STATE_DONE) {
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
in = in * 10 + (int)(*p - '0');
|
|
if (frac)
|
|
denom *= 10;
|
|
} else if (*p == '.') {
|
|
if (frac)
|
|
return 0;
|
|
frac++;
|
|
} else {
|
|
switch (state) {
|
|
case STATE_INITIAL:
|
|
if (*p == 'w') {
|
|
dur += in * WEEKINDAYS / denom;
|
|
state = STATE_WWDD_DD;
|
|
} else if (*p == 'd') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_WWDD_DD:
|
|
if (*p == 'd') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
in = frac = 0;
|
|
denom = 1;
|
|
}
|
|
}
|
|
|
|
if (state == STATE_DONE && in > 0)
|
|
return 0;
|
|
|
|
dur += in;
|
|
*days = dur;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Check if time is valid.
|
|
*/
|
|
int check_time(unsigned hours, unsigned minutes)
|
|
{
|
|
return (hours < DAYINHOURS && minutes < HOURINMIN);
|
|
}
|
|
|
|
/*
|
|
* Converts a time string into hours and minutes. Short forms like "23:"
|
|
* (23:00) or ":45" (0:45) are allowed.
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int parse_time(const char *string, unsigned *hour, unsigned *minute)
|
|
{
|
|
const char *p;
|
|
unsigned in[2] = { 0, 0 }, n = 0;
|
|
|
|
if (!string)
|
|
return 0;
|
|
|
|
/* parse string into in[], read up to two integers */
|
|
for (p = string; *p; p++) {
|
|
if (*p == ':') {
|
|
if ((++n) > 1)
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
if ((n == 0) && (p == (string + 2)) && *(p + 1))
|
|
n++;
|
|
in[n] = in[n] * 10 + (int)(*p - '0');
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (n != 1 || !check_time(in[0], in[1]))
|
|
return 0;
|
|
|
|
*hour = in[0];
|
|
*minute = in[1];
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Converts a duration string into minutes.
|
|
*
|
|
* Allowed formats (noted as regular expressions):
|
|
*
|
|
* - \d*:\d*
|
|
* - (\d*m|\d*h(|\d*m)|\d*d(|\d*m|\d*h(|\d*m)))
|
|
* - \d+
|
|
*
|
|
* "\d" is used as a placeholder for "(0|1|2|3|4|5|6|7|8|9)".
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int parse_duration(const char *string, unsigned *duration)
|
|
{
|
|
enum {
|
|
STATE_INITIAL,
|
|
STATE_HHMM_MM,
|
|
STATE_DDHHMM_HH,
|
|
STATE_DDHHMM_MM,
|
|
STATE_DONE
|
|
} state = STATE_INITIAL;
|
|
|
|
const char *p;
|
|
unsigned in = 0, frac = 0, denom = 1;
|
|
unsigned dur = 0;
|
|
|
|
if (!string || *string == '\0')
|
|
return 0;
|
|
|
|
/* parse string using a simple state machine */
|
|
for (p = string; *p; p++) {
|
|
if (state == STATE_DONE) {
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
in = in * 10 + (int)(*p - '0');
|
|
if (frac)
|
|
denom *= 10;
|
|
} else if (*p == '.') {
|
|
if (frac)
|
|
return 0;
|
|
frac++;
|
|
} else {
|
|
switch (state) {
|
|
case STATE_INITIAL:
|
|
if (*p == ':') {
|
|
dur += in * HOURINMIN / denom;
|
|
state = STATE_HHMM_MM;
|
|
} else if (*p == 'd') {
|
|
dur += in * DAYINMIN / denom;
|
|
state = STATE_DDHHMM_HH;
|
|
} else if (*p == 'h') {
|
|
dur += in * HOURINMIN / denom;
|
|
state = STATE_DDHHMM_MM;
|
|
} else if (*p == 'm') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_DDHHMM_HH:
|
|
if (*p == 'h') {
|
|
dur += in * HOURINMIN / denom;
|
|
state = STATE_DDHHMM_MM;
|
|
} else if (*p == 'm') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_DDHHMM_MM:
|
|
if (*p == 'm') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_HHMM_MM:
|
|
return 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
in = frac = 0;
|
|
denom = 1;
|
|
}
|
|
}
|
|
|
|
if ((state == STATE_HHMM_MM && in >= HOURINMIN) ||
|
|
((state == STATE_DDHHMM_HH || state == STATE_DDHHMM_MM)
|
|
&& in > 0))
|
|
return 0;
|
|
|
|
dur += in;
|
|
*duration = dur;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Converts a string containing a date or a time into a time stamp.
|
|
*
|
|
* Takes a date/time string and a time stamp. If the string only contains a
|
|
* date, the date of the time stamp is updated while the time remains
|
|
* untouched. If the string only contains a time, the time of the time stamp is
|
|
* updated and the date remains the same. If the string contains both a date
|
|
* and a time, the time stamp is updated to match the given string.
|
|
*
|
|
* Returns a positive value on success and 0 on failure. The least-significant
|
|
* bit is set if the date was updated. Bit 1 is set if the time was updated.
|
|
*/
|
|
int parse_datetime(const char *string, long *ts)
|
|
{
|
|
char *t = mem_strdup(string);
|
|
char *p = strtok(t, " ");
|
|
|
|
unsigned int hour, minute;
|
|
int year, month, day;
|
|
struct date new_date;
|
|
int ret = 0;
|
|
|
|
while (p) {
|
|
if (parse_date(p, conf.input_datefmt, &year, &month, &day,
|
|
ui_calendar_get_slctd_day())) {
|
|
new_date.dd = day;
|
|
new_date.mm = month;
|
|
new_date.yyyy = year;
|
|
*ts = date2sec(new_date, 0, 0) + get_item_time(*ts);
|
|
ret |= PARSE_DATETIME_HAS_DATE;
|
|
} else if (parse_time(p, &hour, &minute) == 1) {
|
|
*ts = update_time_in_date(*ts, hour, minute);
|
|
ret |= PARSE_DATETIME_HAS_TIME;
|
|
} else {
|
|
return 0;
|
|
}
|
|
p = strtok(NULL, " ");
|
|
}
|
|
|
|
mem_free(t);
|
|
return ret;
|
|
}
|
|
|
|
void file_close(FILE * f, const char *pos)
|
|
{
|
|
EXIT_IF((fclose(f)) != 0, _("Error when closing file at %s"), pos);
|
|
}
|
|
|
|
/*
|
|
* Sleep the given number of seconds, but make it more 'precise' than sleep(3)
|
|
* (hence the 'p') in a way that even if a signal is caught during the sleep
|
|
* process, this function will return to sleep afterwards.
|
|
*/
|
|
void psleep(unsigned secs)
|
|
{
|
|
unsigned unslept;
|
|
|
|
for (unslept = sleep(secs); unslept; unslept = sleep(unslept)) ;
|
|
}
|
|
|
|
/*
|
|
* Fork and execute an external process.
|
|
*
|
|
* If pfdin and/or pfdout point to a valid address, a pipe is created and the
|
|
* appropriate file descriptors are written to pfdin/pfdout.
|
|
*/
|
|
int fork_exec(int *pfdin, int *pfdout, const char *path,
|
|
const char *const *arg)
|
|
{
|
|
int pin[2], pout[2];
|
|
int pid;
|
|
|
|
if (pfdin && (pipe(pin) == -1))
|
|
return 0;
|
|
if (pfdout && (pipe(pout) == -1))
|
|
return 0;
|
|
|
|
if ((pid = fork()) == 0) {
|
|
if (pfdout) {
|
|
if (dup2(pout[0], STDIN_FILENO) < 0)
|
|
_exit(127);
|
|
close(pout[0]);
|
|
close(pout[1]);
|
|
}
|
|
|
|
if (pfdin) {
|
|
if (dup2(pin[1], STDOUT_FILENO) < 0)
|
|
_exit(127);
|
|
close(pin[0]);
|
|
close(pin[1]);
|
|
}
|
|
|
|
execvp(path, (char *const *)arg);
|
|
_exit(127);
|
|
} else {
|
|
if (pfdin)
|
|
close(pin[1]);
|
|
if (pfdout)
|
|
close(pout[0]);
|
|
|
|
if (pid > 0) {
|
|
if (pfdin) {
|
|
fcntl(pin[0], F_SETFD, FD_CLOEXEC);
|
|
*pfdin = pin[0];
|
|
}
|
|
if (pfdout) {
|
|
fcntl(pout[1], F_SETFD, FD_CLOEXEC);
|
|
*pfdout = pout[1];
|
|
}
|
|
} else {
|
|
if (pfdin)
|
|
close(pin[0]);
|
|
if (pfdout)
|
|
close(pout[1]);
|
|
return 0;
|
|
}
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
/* Execute an external program in a shell. */
|
|
int
|
|
shell_exec(int *pfdin, int *pfdout, const char *path,
|
|
const char *const *arg)
|
|
{
|
|
int argc, i;
|
|
const char **narg;
|
|
char *arg0 = NULL;
|
|
int ret;
|
|
|
|
for (argc = 0; arg[argc]; argc++) ;
|
|
|
|
if (argc < 1)
|
|
return -1;
|
|
|
|
narg = mem_calloc(argc + 4, sizeof(const char *));
|
|
|
|
narg[0] = "sh";
|
|
narg[1] = "-c";
|
|
|
|
if (argc > 1) {
|
|
asprintf(&arg0, "%s \"$@\"", path);
|
|
narg[2] = arg0;
|
|
|
|
for (i = 0; i < argc; i++)
|
|
narg[i + 3] = arg[i];
|
|
narg[argc + 3] = NULL;
|
|
} else {
|
|
narg[2] = path;
|
|
narg[3] = NULL;
|
|
}
|
|
|
|
ret = fork_exec(pfdin, pfdout, *narg, narg);
|
|
|
|
if (arg0)
|
|
mem_free(arg0);
|
|
mem_free(narg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Wait for a child process to terminate. */
|
|
int child_wait(int *pfdin, int *pfdout, int pid)
|
|
{
|
|
int stat;
|
|
|
|
if (pfdin)
|
|
close(*pfdin);
|
|
if (pfdout)
|
|
close(*pfdout);
|
|
|
|
waitpid(pid, &stat, 0);
|
|
return stat;
|
|
}
|
|
|
|
/* Display "Press any key to continue..." and wait for a key press. */
|
|
void press_any_key(void)
|
|
{
|
|
struct termios t_attr_old, t_attr;
|
|
|
|
tcgetattr(STDIN_FILENO, &t_attr_old);
|
|
memcpy(&t_attr, &t_attr_old, sizeof(struct termios));
|
|
t_attr.c_lflag &= ~(ICANON | ECHO | ECHONL);
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_attr);
|
|
|
|
fflush(stdout);
|
|
fputs(_("Press any key to continue..."), stdout);
|
|
fflush(stdout);
|
|
fgetc(stdin);
|
|
fflush(stdin);
|
|
fputs("\r\n", stdout);
|
|
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_attr_old);
|
|
}
|
|
|
|
/*
|
|
* Display note contents if one is asociated with the currently displayed item
|
|
* (to be used together with the '-a' or '-t' flag in non-interactive mode).
|
|
* Each line begins with nbtab tabs.
|
|
* Print "No note file found", if the notefile does not exists.
|
|
*
|
|
* (patch submitted by Erik Saule).
|
|
*/
|
|
static void print_notefile(FILE * out, const char *filename, int nbtab)
|
|
{
|
|
char *path_to_notefile;
|
|
FILE *notefile;
|
|
char linestarter[BUFSIZ];
|
|
char buffer[BUFSIZ];
|
|
int i;
|
|
int printlinestarter = 1;
|
|
|
|
if (nbtab < BUFSIZ) {
|
|
for (i = 0; i < nbtab; i++)
|
|
linestarter[i] = '\t';
|
|
linestarter[nbtab] = '\0';
|
|
} else {
|
|
linestarter[0] = '\0';
|
|
}
|
|
|
|
asprintf(&path_to_notefile, "%s/%s", path_notes, filename);
|
|
notefile = fopen(path_to_notefile, "r");
|
|
mem_free(path_to_notefile);
|
|
if (notefile) {
|
|
while (fgets(buffer, BUFSIZ, notefile) != 0) {
|
|
if (printlinestarter) {
|
|
fputs(linestarter, out);
|
|
printlinestarter = 0;
|
|
}
|
|
fputs(buffer, out);
|
|
if (buffer[strlen(buffer) - 1] == '\n')
|
|
printlinestarter = 1;
|
|
}
|
|
fputs("\n", out);
|
|
file_close(notefile, __FILE_POS__);
|
|
} else {
|
|
fputs(linestarter, out);
|
|
fputs(_("No note file found\n"), out);
|
|
}
|
|
}
|
|
|
|
/* Print an escape sequence and return its length. */
|
|
static int print_escape(const char *s)
|
|
{
|
|
switch (*(s + 1)) {
|
|
case 'a':
|
|
putchar('\a');
|
|
return 1;
|
|
case 'b':
|
|
putchar('\b');
|
|
return 1;
|
|
case 'f':
|
|
putchar('\f');
|
|
return 1;
|
|
case 'n':
|
|
putchar('\n');
|
|
return 1;
|
|
case 'r':
|
|
putchar('\r');
|
|
return 1;
|
|
case 't':
|
|
putchar('\t');
|
|
return 1;
|
|
case 'v':
|
|
putchar('\v');
|
|
return 1;
|
|
case '0':
|
|
putchar('\0');
|
|
return 1;
|
|
case '\'':
|
|
putchar('\'');
|
|
return 1;
|
|
case '"':
|
|
putchar('"');
|
|
return 1;
|
|
case '\?':
|
|
putchar('?');
|
|
return 1;
|
|
case '\\':
|
|
putchar('\\');
|
|
return 1;
|
|
case '\0':
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Parse a format specifier. */
|
|
static enum format_specifier parse_fs(const char **s, char *extformat)
|
|
{
|
|
char buf[FS_EXT_MAXLEN];
|
|
int i;
|
|
|
|
extformat[0] = '\0';
|
|
|
|
switch (**s) {
|
|
case 's':
|
|
strcpy(extformat, "epoch");
|
|
return FS_STARTDATE;
|
|
case 'S':
|
|
return FS_STARTDATE;
|
|
case 'd':
|
|
return FS_DURATION;
|
|
case 'e':
|
|
strcpy(extformat, "epoch");
|
|
return FS_ENDDATE;
|
|
case 'E':
|
|
return FS_ENDDATE;
|
|
case 'm':
|
|
return FS_MESSAGE;
|
|
case 'n':
|
|
return FS_NOTE;
|
|
case 'N':
|
|
return FS_NOTEFILE;
|
|
case 'p':
|
|
return FS_PRIORITY;
|
|
case 'r':
|
|
return FS_REMAINING;
|
|
case '(':
|
|
/* Long format specifier. */
|
|
for ((*s)++, i = 0; **s != ':' && **s != ')'; (*s)++, i++) {
|
|
if (**s == '\0')
|
|
return FS_EOF;
|
|
|
|
if (i < FS_EXT_MAXLEN)
|
|
buf[i] = **s;
|
|
}
|
|
|
|
buf[(i < FS_EXT_MAXLEN) ? i : FS_EXT_MAXLEN - 1] = '\0';
|
|
|
|
if (**s == ':') {
|
|
for ((*s)++, i = 0; **s != ')'; (*s)++, i++) {
|
|
if (**s == '\0')
|
|
return FS_EOF;
|
|
|
|
if (i < FS_EXT_MAXLEN)
|
|
extformat[i] = **s;
|
|
}
|
|
|
|
extformat[(i <
|
|
FS_EXT_MAXLEN) ? i : FS_EXT_MAXLEN -
|
|
1] = '\0';
|
|
}
|
|
|
|
if (!strcmp(buf, "start"))
|
|
return FS_STARTDATE;
|
|
else if (!strcmp(buf, "duration"))
|
|
return FS_DURATION;
|
|
else if (!strcmp(buf, "end"))
|
|
return FS_ENDDATE;
|
|
else if (!strcmp(buf, "remaining"))
|
|
return FS_REMAINING;
|
|
else if (!strcmp(buf, "message"))
|
|
return FS_MESSAGE;
|
|
else if (!strcmp(buf, "noteid"))
|
|
return FS_NOTE;
|
|
else if (!strcmp(buf, "note"))
|
|
return FS_NOTEFILE;
|
|
else if (!strcmp(buf, "priority"))
|
|
return FS_PRIORITY;
|
|
else if (!strcmp(buf, "raw"))
|
|
return FS_RAW;
|
|
else if (!strcmp(buf, "hash"))
|
|
return FS_HASH;
|
|
else
|
|
return FS_UNKNOWN;
|
|
case '%':
|
|
return FS_PSIGN;
|
|
case '\0':
|
|
return FS_EOF;
|
|
default:
|
|
return FS_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
/* Print a formatted date to stdout. */
|
|
static void print_date(long date, long day, const char *extformat)
|
|
{
|
|
char buf[BUFSIZ];
|
|
|
|
if (!strcmp(extformat, "epoch")) {
|
|
printf("%ld", date);
|
|
} else {
|
|
time_t day_end = date_sec_change(day, 0, 1);
|
|
time_t t = date;
|
|
struct tm lt;
|
|
|
|
localtime_r((time_t *) & t, <);
|
|
|
|
if (extformat[0] == '\0' || !strcmp(extformat, "default")) {
|
|
if (date >= day && date <= day_end)
|
|
strftime(buf, BUFSIZ, "%H:%M", <);
|
|
else
|
|
strftime(buf, BUFSIZ, "..:..", <);
|
|
} else {
|
|
strftime(buf, BUFSIZ, extformat, <);
|
|
}
|
|
|
|
printf("%s", buf);
|
|
}
|
|
}
|
|
|
|
/* Print a time difference to stdout. */
|
|
static void print_datediff(long difference, const char *extformat)
|
|
{
|
|
const char *p;
|
|
const char *numfmt;
|
|
bool usetotal;
|
|
long value;
|
|
|
|
if (!strcmp(extformat, "epoch")) {
|
|
printf("%ld", difference);
|
|
} else {
|
|
if (extformat[0] == '\0' || !strcmp(extformat, "default")) {
|
|
/* Set a default format if none specified. */
|
|
p = "%EH:%M";
|
|
} else {
|
|
p = extformat;
|
|
}
|
|
while (*p) {
|
|
if (*p == '%') {
|
|
p++;
|
|
/* Default is to zero-pad, and assume
|
|
* the user wants the time unit modulo
|
|
* the next biggest time unit. */
|
|
numfmt = "%02d";
|
|
usetotal = FALSE;
|
|
if (*p == '-') {
|
|
numfmt = "%d";
|
|
p++;
|
|
}
|
|
if (*p == 'E') {
|
|
usetotal = TRUE;
|
|
p++;
|
|
}
|
|
switch (*p) {
|
|
case '\0':
|
|
return;
|
|
case 'd':
|
|
value = difference / DAYINSEC;
|
|
printf(numfmt, value);
|
|
break;
|
|
case 'H':
|
|
value = difference / HOURINSEC;
|
|
if (!usetotal)
|
|
value %= DAYINHOURS;
|
|
printf(numfmt, value);
|
|
break;
|
|
case 'M':
|
|
value = difference / MININSEC;
|
|
if (!usetotal)
|
|
value %= HOURINMIN;
|
|
printf(numfmt, value);
|
|
break;
|
|
case 'S':
|
|
value = difference;
|
|
if (!usetotal)
|
|
value %= MININSEC;
|
|
printf(numfmt, value);
|
|
break;
|
|
case '%':
|
|
putchar('%');
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print a formatted appointment to stdout. */
|
|
static void print_apoint_helper(const char *format, long day,
|
|
struct apoint *apt, struct recur_apoint *rapt)
|
|
{
|
|
const char *p;
|
|
char extformat[FS_EXT_MAXLEN];
|
|
|
|
for (p = format; *p; p++) {
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (parse_fs(&p, extformat)) {
|
|
case FS_STARTDATE:
|
|
print_date(apt->start, day, extformat);
|
|
break;
|
|
case FS_DURATION:
|
|
/* Backwards compatibility: Use epoch by
|
|
* default. */
|
|
if (*extformat == '\0')
|
|
strcpy(extformat, "epoch");
|
|
print_datediff(apt->dur, extformat);
|
|
break;
|
|
case FS_ENDDATE:
|
|
print_date(apt->start + apt->dur, day,
|
|
extformat);
|
|
break;
|
|
case FS_REMAINING:
|
|
print_datediff(difftime(apt->start, now()),
|
|
extformat);
|
|
break;
|
|
case FS_MESSAGE:
|
|
printf("%s", apt->mesg);
|
|
break;
|
|
case FS_NOTE:
|
|
printf("%s", apt->note);
|
|
break;
|
|
case FS_NOTEFILE:
|
|
print_notefile(stdout, apt->note, 1);
|
|
break;
|
|
case FS_RAW:
|
|
if (rapt)
|
|
recur_apoint_write(rapt, stdout);
|
|
else
|
|
apoint_write(apt, stdout);
|
|
break;
|
|
case FS_HASH:
|
|
if (rapt)
|
|
printf("%s", recur_apoint_hash(rapt));
|
|
else
|
|
printf("%s", apoint_hash(apt));
|
|
break;
|
|
case FS_PSIGN:
|
|
putchar('%');
|
|
break;
|
|
case FS_EOF:
|
|
return;
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else if (*p == '\\') {
|
|
p += print_escape(p);
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print a formatted event to stdout. */
|
|
static void print_event_helper(const char *format, long day, struct event *ev,
|
|
struct recur_event *rev)
|
|
{
|
|
const char *p;
|
|
char extformat[FS_EXT_MAXLEN];
|
|
|
|
for (p = format; *p; p++) {
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (parse_fs(&p, extformat)) {
|
|
case FS_MESSAGE:
|
|
printf("%s", ev->mesg);
|
|
break;
|
|
case FS_NOTE:
|
|
printf("%s", ev->note);
|
|
break;
|
|
case FS_NOTEFILE:
|
|
print_notefile(stdout, ev->note, 1);
|
|
break;
|
|
case FS_PSIGN:
|
|
putchar('%');
|
|
break;
|
|
case FS_RAW:
|
|
if (rev)
|
|
recur_event_write(rev, stdout);
|
|
else
|
|
event_write(ev, stdout);
|
|
break;
|
|
case FS_HASH:
|
|
if (rev)
|
|
printf("%s", recur_event_hash(rev));
|
|
else
|
|
printf("%s", event_hash(ev));
|
|
break;
|
|
case FS_EOF:
|
|
return;
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else if (*p == '\\') {
|
|
p += print_escape(p);
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print a formatted appointment to stdout. */
|
|
void print_apoint(const char *format, long day, struct apoint *apt)
|
|
{
|
|
print_apoint_helper(format, day, apt, NULL);
|
|
}
|
|
|
|
/* Print a formatted event to stdout. */
|
|
void print_event(const char *format, long day, struct event *ev)
|
|
{
|
|
print_event_helper(format, day, ev, NULL);
|
|
}
|
|
|
|
/* Print a formatted recurrent appointment to stdout. */
|
|
void
|
|
print_recur_apoint(const char *format, long day, time_t occurrence,
|
|
struct recur_apoint *rapt)
|
|
{
|
|
struct apoint apt;
|
|
|
|
apt.start = occurrence;
|
|
apt.dur = rapt->dur;
|
|
apt.mesg = rapt->mesg;
|
|
apt.note = rapt->note;
|
|
|
|
print_apoint_helper(format, day, &apt, rapt);
|
|
}
|
|
|
|
/* Print a formatted recurrent event to stdout. */
|
|
void print_recur_event(const char *format, long day,
|
|
struct recur_event *rev)
|
|
{
|
|
struct event ev;
|
|
|
|
ev.mesg = rev->mesg;
|
|
ev.note = rev->note;
|
|
|
|
print_event_helper(format, day, &ev, rev);
|
|
}
|
|
|
|
/* Print a formatted todo item to stdout. */
|
|
void print_todo(const char *format, struct todo *todo)
|
|
{
|
|
const char *p;
|
|
char extformat[FS_EXT_MAXLEN];
|
|
|
|
for (p = format; *p; p++) {
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (parse_fs(&p, extformat)) {
|
|
case FS_PRIORITY:
|
|
printf("%d", abs(todo->id));
|
|
break;
|
|
case FS_MESSAGE:
|
|
printf("%s", todo->mesg);
|
|
break;
|
|
case FS_NOTE:
|
|
printf("%s", todo->note);
|
|
break;
|
|
case FS_NOTEFILE:
|
|
print_notefile(stdout, todo->note, 1);
|
|
break;
|
|
case FS_RAW:
|
|
todo_write(todo, stdout);
|
|
break;
|
|
case FS_HASH:
|
|
printf("%s", todo_hash(todo));
|
|
break;
|
|
case FS_PSIGN:
|
|
putchar('%');
|
|
break;
|
|
case FS_EOF:
|
|
return;
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else if (*p == '\\') {
|
|
p += print_escape(p);
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
asprintf(char **str, const char *format, ...)
|
|
{
|
|
struct string s;
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start(ap, format);
|
|
string_init(&s);
|
|
n = string_vcatf(&s, format, ap);
|
|
*str = string_buf(&s);
|
|
va_end(ap);
|
|
|
|
return n;
|
|
}
|
|
|
|
int starts_with(const char *s, const char *p)
|
|
{
|
|
for (; *p && *p == *s; s++, p++);
|
|
return (*p == '\0');
|
|
}
|
|
|
|
int starts_with_ci(const char *s, const char *p)
|
|
{
|
|
for (; *p && tolower(*p) == tolower(*s); s++, p++);
|
|
return (*p == '\0');
|
|
}
|
|
|
|
int hash_matches(const char *pattern, const char *hash)
|
|
{
|
|
int invert = 0;
|
|
|
|
if (pattern[0] == '!') {
|
|
invert = 1;
|
|
pattern++;
|
|
}
|
|
|
|
return (starts_with(hash, pattern) != invert);
|
|
}
|
|
|
|
int show_dialogs(void)
|
|
{
|
|
return (!quiet) && conf.system_dialogs;
|
|
}
|