commit 81aae152b0fab610afa4446e6223b2a4dd242ecb
parent bb4051764c757fa0cf7ff4b3abe735b52c7889af
Author: Andreas Gruhler <agruhl@gmx.ch>
Date:   Sun,  3 Jan 2021 23:32:13 +0100
add configuration file
This adds a basic config file, see
https://github.com/in0rdr/diary/issues/35
Currently, it allows to configure:
- diary_dir
- year_range
- first_weekday
- date_fmt
Diffstat:
| M | README.md | | | 20 | ++++++++++++++++++++ | 
| M | diary.c | | | 138 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- | 
| A | diary.cfg | | | 8 | ++++++++ | 
| M | diary.h | | | 22 | +++++++++++++++++++++- | 
4 files changed, 133 insertions(+), 55 deletions(-)
diff --git a/README.md b/README.md
@@ -70,3 +70,22 @@ Note: for *BSD users run gmake.
    By default this will copy the binary to /usr/local/bin. To use a different
    path prefix, type `sudo make PREFIX=/usr install` to use /usr/bin for example.
    You can uninstall diary with `sudo make uninstall`.
+
+## Configuration File
+
+(version `master`)
+
+The [`diary.cfg`](./diary.cfg) configuration file can optionally be used to persist diary configuration. To install the sample from this repository:
+```bash
+mkdir -p ~/.config/diary/
+cp diary.cfg ~/.config/diary/
+```
+
+The file `~/.config/diary/diary.cfg` should adhere to a basic `key = value` format. Lines can be commented with the special characters `#` or `;`. The following configuration keys are currently supported:
+
+| Command Line Option | Config Key | Example Config Value | Default Config Value | Description |
+| --- | --- | --- | --- | --- |
+| `--dir`, `-d`, or first non-option argument | `diary_dir` | ~/diary | n/a | Diary directory. Path that holds the journal text files. |
+| n/a | `year_range` | 10 | 1 | Number of years to show before/after todays date |
+| n/a | `first_weekday` | 0 | 1 | First weekday, `0` = Sunday, `1` = Monday, ..., `6` = Saturday. Use `0` to display week beginning at Sunday ("S-M-T-W-T-F-S"), or `1` for "M-T-W-T-F-S-S" (default) |
+| n/a | `date_fmt` | %d_%b_%y | %Y-%m-%d | Date format and file name for the files inside the `diary_dir`. For the format specifiers, see [`man strftime`](https://man7.org/linux/man-pages/man3/strftime.3.html). |
+\ No newline at end of file
diff --git a/diary.c b/diary.c
@@ -6,10 +6,6 @@ struct tm today;
 struct tm curs_date;
 struct tm cal_start;
 struct tm cal_end;
-// 0 = Sunday, 1 = Monday, ..., 6 = Saturday
-int first_weekday = 1;
-
-#define DATE_FMT "%Y-%m-%d"
 
 // normally leap is every 4 years,
 // but is skipped every 100 years,
@@ -23,21 +19,21 @@ void setup_cal_timeframe()
     curs_date = today;
 
     cal_start = today;
-    cal_start.tm_year -= YEAR_RANGE;
+    cal_start.tm_year -= CONFIG.year_range;
     cal_start.tm_mon = 0;
     cal_start.tm_mday = 1;
     mktime(&cal_start);
 
-    if (cal_start.tm_wday != first_weekday) {
+    if (cal_start.tm_wday != CONFIG.first_weekday) {
         // adjust start date to first_weekday before 01.01
         cal_start.tm_year--;
         cal_start.tm_mon = 11;
-        cal_start.tm_mday = 31 - (cal_start.tm_wday - first_weekday) + 1;
+        cal_start.tm_mday = 31 - (cal_start.tm_wday - CONFIG.first_weekday) + 1;
         mktime(&cal_start);
     }
 
     cal_end = today;
-    cal_end.tm_year += YEAR_RANGE;
+    cal_end.tm_year += CONFIG.year_range;
     cal_end.tm_mon = 11;
     cal_end.tm_mday = 31;
     mktime(&cal_end);
@@ -46,7 +42,7 @@ void setup_cal_timeframe()
 void draw_wdays(WINDOW* head)
 {
     int i;
-    for (i = first_weekday; i < first_weekday + 7; i++) {
+    for (i = CONFIG.first_weekday; i < CONFIG.first_weekday + 7; i++) {
         waddstr(head, WEEKDAYS[i % 7]);
         waddch(head, ' ');
     }
@@ -217,7 +213,7 @@ bool date_has_entry(const char* dir, size_t dir_size, const struct tm* i)
 
 void get_date_str(const struct tm* date, char* date_str, size_t date_str_size)
 {
-    strftime(date_str, date_str_size, DATE_FMT, date);
+    strftime(date_str, date_str_size, CONFIG.date_fmt, date);
 }
 
 /* Writes file path for 'date' entry to 'rpath'. '*rpath' is NULL on error. */
@@ -287,16 +283,53 @@ struct tm find_closest_entry(const struct tm current,
     return current;
 }
 
-/* Set the diary storage directory.
-*  Copies the path to the storage directory from character
-*  string `path` to the destination location `pdiary_dir`
-*/
-bool set_diary_dir(char* path, char* pdiary_dir, size_t diary_dir_size) {
-    if (strlen(path) + 1 > diary_dir_size) {
-        fprintf(stderr, "Diary directory path too long\n");
+bool read_config(const char* file_path)
+{
+    wordexp_t config_file_path_wordexp;
+    char config_file_path[256];
+
+    if ( wordexp( file_path, &config_file_path_wordexp, 0 ) == 0) {
+        if (strlen(config_file_path_wordexp.we_wordv[0]) + 1 > sizeof config_file_path) {
+            fprintf(stderr, "Config file path '%s' too long\n", config_file_path_wordexp.we_wordv[0]);
+            return false;
+        }
+        strcpy(config_file_path, config_file_path_wordexp.we_wordv[0]);
+    }
+    wordfree(&config_file_path_wordexp);
+
+    // check if config file is readable
+    if( access( config_file_path, R_OK ) != 0 ) {
+        fprintf(stderr, "Config file '%s' not readable, skipping\n", config_file_path);
         return false;
     }
-    strcpy(pdiary_dir, path);
+
+    char key_buf[80];
+    char value_buf[80];
+    char line[256];
+    FILE * pfile;
+
+    // read config file line by line
+    pfile = fopen(config_file_path, "r");
+    while (fgets(line, sizeof line, pfile)) {
+        // ignore comment lines
+        if (*line == '#' || *line == ';') continue;
+
+        if (sscanf(line, "%s = %s", key_buf, value_buf) == 2) {
+            if (strcmp("diary_dir", key_buf) == 0) {
+                //  set diary directory from config file
+                CONFIG.diary_dir = (char *) calloc(strlen(value_buf) + 1, sizeof(char));
+                strcpy(CONFIG.diary_dir, value_buf);
+            } else if (strcmp("year_range", key_buf) == 0) {
+                CONFIG.year_range = atoi(value_buf);
+            } else if (strcmp("first_weekday", key_buf) == 0) {
+                CONFIG.first_weekday = atoi(value_buf);
+            } else if (strcmp("date_fmt", key_buf) == 0) {
+                CONFIG.date_fmt = (char *) malloc(strlen(value_buf) + 1 * sizeof(char));
+                strcpy(CONFIG.date_fmt, value_buf);
+            }
+        }
+    }
+    fclose (pfile);
     return true;
 }
 
@@ -317,27 +350,26 @@ void usage() {
 
 int main(int argc, char** argv) {
     setlocale(LC_ALL, "");
-    char diary_dir[80];
     char* env_var;
     chtype atrs;
 
-    // get the diary directory via environment variable or argument
-    // if both are given, the argument takes precedence
-    if (argc < 2) {
-        // the diary directory is not specified via command line argument
-        // use the environment variable if available
-        env_var = getenv("DIARY_DIR");
-        if (env_var == NULL) {
+    // the diary directory defaults to the diary_dir specified in the config file
+    read_config(CONFIG_FILE_PATH);
+
+    env_var = getenv("DIARY_DIR");
+    if (env_var != NULL) {
+        // if available, overwrite the diary directory with the environment variable
+        CONFIG.diary_dir = (char *) calloc(strlen(env_var) + 1, sizeof(char));
+        strcpy(CONFIG.diary_dir, env_var);
+    }
 
+    // get the diary directory via argument, this takes precedence over env/config
+    if (argc < 2) {
+        if (CONFIG.diary_dir == NULL) {
             fprintf(stderr, "The diary directory must be provided as (non-option) arg, `--dir` arg,\n"
                             "or in the DIARY_DIR environment variable, see `diary --help` or DIARY(1)\n");
             return 1;
         }
-
-        // set diary directory from environment variable
-        if ( !set_diary_dir(env_var, diary_dir, sizeof diary_dir) ) {
-            return 1;
-        }
     } else {
         int option_char;
         int option_index = 0;
@@ -372,9 +404,8 @@ int main(int argc, char** argv) {
                     break;
                 case 'd':
                     // set diary directory from option character
-                    if ( !set_diary_dir(optarg, diary_dir, sizeof diary_dir) ) {
-                        return 1;
-                    }
+                    CONFIG.diary_dir = (char *) calloc(strlen(optarg) + 1, sizeof(char));
+                    strcpy(CONFIG.diary_dir, optarg);
                     break;
                 default:
                     printf("?? getopt returned character code 0%o ??\n", option_char);
@@ -384,22 +415,21 @@ int main(int argc, char** argv) {
         if (optind < argc) {
             // set diary directory from first non-option argv-element,
             // required for backwarad compatibility with diary <= 0.4
-            if ( !set_diary_dir(argv[optind], diary_dir, sizeof diary_dir) ) {
-                return 1;
-            }
+            CONFIG.diary_dir = (char *) calloc(strlen(argv[optind]) + 1, sizeof(char));
+            strcpy(CONFIG.diary_dir, argv[optind]);
         }
     }
 
     // check if that directory exists
-    DIR* diary_dir_ptr = opendir(diary_dir);
+    DIR* diary_dir_ptr = opendir(CONFIG.diary_dir);
     if (diary_dir_ptr) {
         // directory exists, continue
         closedir(diary_dir_ptr);
     } else if (errno == ENOENT) {
-        fprintf(stderr, "The directory '%s' does not exist\n", diary_dir);
+        fprintf(stderr, "The directory '%s' does not exist\n", CONFIG.diary_dir);
         return 2;
     } else {
-        fprintf(stderr, "The directory '%s' could not be opened\n", diary_dir);
+        fprintf(stderr, "The directory '%s' could not be opened\n", CONFIG.diary_dir);
         return 1;
     }
 
@@ -424,13 +454,13 @@ int main(int argc, char** argv) {
         mktime(&base);
         // first_weekday is base date's day of the week offset by (_NL_TIME_FIRST_WEEKDAY - 1)
         #ifdef __linux__
-            first_weekday = (base.tm_wday + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % 7;
+            CONFIG.first_weekday = (base.tm_wday + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % 7;
         #elif defined __MACH__
             CFIndex first_day_of_week;
             CFCalendarRef currentCalendar = CFCalendarCopyCurrent();
             first_day_of_week = CFCalendarGetFirstWeekday(currentCalendar);
             CFRelease(currentCalendar);
-            first_weekday = (base.tm_wday + first_day_of_week - 1) % 7;
+            CONFIG.first_weekday = (base.tm_wday + first_day_of_week - 1) % 7;
         #endif
     #endif
 
@@ -446,10 +476,10 @@ int main(int argc, char** argv) {
     WINDOW* wdays = newwin(1, 3 * 7, 0, ASIDE_WIDTH);
     draw_wdays(wdays);
 
-    WINDOW* aside = newpad((YEAR_RANGE * 2 + 1) * 12 * MAX_MONTH_HEIGHT, ASIDE_WIDTH);
-    WINDOW* cal = newpad((YEAR_RANGE * 2 + 1) * 12 * MAX_MONTH_HEIGHT, CAL_WIDTH);
+    WINDOW* aside = newpad((CONFIG.year_range * 2 + 1) * 12 * MAX_MONTH_HEIGHT, ASIDE_WIDTH);
+    WINDOW* cal = newpad((CONFIG.year_range * 2 + 1) * 12 * MAX_MONTH_HEIGHT, CAL_WIDTH);
     keypad(cal, TRUE);
-    draw_calendar(cal, aside, diary_dir, strlen(diary_dir));
+    draw_calendar(cal, aside, CONFIG.diary_dir, strlen(CONFIG.diary_dir));
 
     int ch, conf_ch;
     int pad_pos = 0;
@@ -457,7 +487,7 @@ int main(int argc, char** argv) {
     struct tm new_date;
     int prev_width = COLS - ASIDE_WIDTH - CAL_WIDTH;
     int prev_height = LINES - 1;
-    size_t diary_dir_size = strlen(diary_dir);
+    size_t diary_dir_size = strlen(CONFIG.diary_dir);
 
     bool mv_valid = go_to(cal, aside, raw_time, &pad_pos);
     // mark current day
@@ -466,7 +496,7 @@ int main(int argc, char** argv) {
     prefresh(cal, pad_pos, 0, 1, ASIDE_WIDTH, LINES - 1, ASIDE_WIDTH + CAL_WIDTH);
 
     WINDOW* prev = newwin(prev_height, prev_width, 1, ASIDE_WIDTH + CAL_WIDTH);
-    display_entry(diary_dir, strlen(diary_dir), &today, prev, prev_width);
+    display_entry(CONFIG.diary_dir, diary_dir_size, &today, prev, prev_width);
 
 
     do {
@@ -479,7 +509,7 @@ int main(int argc, char** argv) {
         char pth[100];
         char* ppth = pth;
         char dstr[16];
-        edit_cmd(diary_dir, diary_dir_size, &new_date, &pecmd, sizeof ecmd);
+        edit_cmd(CONFIG.diary_dir, diary_dir_size, &new_date, &pecmd, sizeof ecmd);
 
         switch(ch) {
             // basic movements
@@ -551,9 +581,9 @@ int main(int argc, char** argv) {
             // delete entry
             case 'd':
             case 'x':
-                if (date_has_entry(diary_dir, diary_dir_size, &curs_date)) {
+                if (date_has_entry(CONFIG.diary_dir, diary_dir_size, &curs_date)) {
                     // get file path of entry and delete entry
-                    fpath(diary_dir, diary_dir_size, &curs_date, &ppth, sizeof pth);
+                    fpath(CONFIG.diary_dir, diary_dir_size, &curs_date, &ppth, sizeof pth);
                     if (ppth == NULL) {
                         fprintf(stderr, "Error retrieving file path for entry removal");
                         break;
@@ -601,7 +631,7 @@ int main(int argc, char** argv) {
                 keypad(cal, TRUE);
 
                 // mark newly created entry
-                if (date_has_entry(diary_dir, diary_dir_size, &curs_date)) {
+                if (date_has_entry(CONFIG.diary_dir, diary_dir_size, &curs_date)) {
                     atrs = winch(cal) & A_ATTRIBUTES;
                     wchgat(cal, 2, atrs | A_BOLD, 0, NULL);
 
@@ -612,12 +642,12 @@ int main(int argc, char** argv) {
                 break;
             // Move to the previous diary entry
             case 'N':
-                new_date = find_closest_entry(new_date, true, diary_dir, diary_dir_size);
+                new_date = find_closest_entry(new_date, true, CONFIG.diary_dir, diary_dir_size);
                 mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos);
                 break;
             // Move to the next diary entry
             case 'n':
-                new_date = find_closest_entry(new_date, false, diary_dir, diary_dir_size);
+                new_date = find_closest_entry(new_date, false, CONFIG.diary_dir, diary_dir_size);
                 mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos);
                 break;
         }
@@ -630,7 +660,7 @@ int main(int argc, char** argv) {
             wresize(prev, prev_height, prev_width);
 
             // read the diary
-            display_entry(diary_dir, diary_dir_size, &curs_date, prev, prev_width);
+            display_entry(CONFIG.diary_dir, diary_dir_size, &curs_date, prev, prev_width);
         }
     } while (ch != 'q');
 
diff --git a/diary.cfg b/diary.cfg
@@ -0,0 +1,8 @@
+# Path that holds the journal text files
+diary_dir = /tmp/diary
+# Number of years to show before/after todays date
+year_range = 2
+# 0 = Sunday, 1 = Monday, ..., 6 = Saturday
+first_weekday = 0
+# Date and file format, change with care
+date_fmt = %d_%b_%y
diff --git a/diary.h b/diary.h
@@ -5,8 +5,10 @@
 #ifdef __MACH__
 	#include <CoreFoundation/CoreFoundation.h>
 #endif
+#include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <wordexp.h>
 #include <unistd.h>
 #include <getopt.h>
 #include <string.h>
@@ -17,8 +19,8 @@
 #include <locale.h>
 #include <langinfo.h>
 
+#define CONFIG_FILE_PATH "~/.config/diary/diary.cfg"
 #define DIARY_VERSION "0.4"
-#define YEAR_RANGE 1
 #define CAL_WIDTH 21
 #define ASIDE_WIDTH 4
 #define MAX_MONTH_HEIGHT 6
@@ -38,4 +40,22 @@ bool date_has_entry(const char* dir, size_t dir_size, const struct tm* i);
 void get_date_str(const struct tm* date, char* date_str, size_t date_str_size);
 void fpath(const char* dir, size_t dir_size, const struct tm* date, char** rpath, size_t rpath_size);
 
+typedef struct
+{
+    // Path that holds the journal text files
+    char* diary_dir;
+    // Number of years to show before/after todays date
+    int year_range;
+    // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
+    int first_weekday;
+    // 2020-12-31
+	char* date_fmt;
+} config;
+
+config CONFIG = {
+    .year_range = 1,
+    .first_weekday = 1,
+    .date_fmt = "%Y-%m-%d"
+};
+
 #endif