/* * Created by gmgauthier on 05/10/2025. */ #include #include #include #include "platform.h" #include "config.h" #define MAX_LENGTH 500 /* Allow fgets to read up to this many characters */ #define DATE_LENGTH 10 /* YYYY-MM-DD */ #define TIME_LENGTH 5 /* HH:MM */ #define TXTMSG_LENGTH 125 /* Max length of a text message */ typedef enum { SORT_NONE, SORT_DATE, SORT_CATEGORY } SortMode; /* Global variable to control sort order: 1 for ascending, -1 for descending */ static int g_sort_order = -1; /* Default: descending (newest/Z-A first) */ typedef struct { char date[DATE_LENGTH +1]; /* YYYY-MM-DD\0 */ char time[TIME_LENGTH +1]; /* HH:MM\0 */ char category[CATEGORY_LENGTH +1]; /* CATEGORY__\0 */ char text[TXTMSG_LENGTH +1]; /* Text message\0 */ } Entry; /* Comparison function for sorting by date/time */ static int compare_by_date(const void *a, const void *b) { const Entry *entry_a = (const Entry *)a; const Entry *entry_b = (const Entry *)b; int date_cmp = strcmp(entry_a->date, entry_b->date); if (date_cmp != 0) return date_cmp * g_sort_order; return strcmp(entry_a->time, entry_b->time) * g_sort_order; } /* Comparison function for sorting by category */ static int compare_by_category(const void *a, const void *b) { const Entry *entry_a = (const Entry *)a; const Entry *entry_b = (const Entry *)b; return strcmp(entry_b->category, entry_a->category) * g_sort_order; } void print_horizontal_line(char left, char middle, char right, char fill) { int i; printf("%c", left); /* Date column */ for (i = 0; i < DATE_LENGTH + 2; i++) printf("%c", fill); printf("%c", middle); /* Time column */ for (i = 0; i < TIME_LENGTH + 2; i++) printf("%c", fill); printf("%c", middle); /* Category column */ for (i = 0; i < CATEGORY_LENGTH + 2; i++) printf("%c", fill); printf("%c", middle); /* Free text column */ for (i = 0; i < TXTMSG_LENGTH + 2; i++) printf("%c", fill); printf("%c\n", right); } void print_header(void) { /* Top border (double line) */ print_horizontal_line('+', '+', '+', '='); /* Header row */ printf("| %-*s | %-*s | %-*s | %-*s |\n", DATE_LENGTH, "Date", TIME_LENGTH, "Time", CATEGORY_LENGTH, "Category", TXTMSG_LENGTH, "Free Text"); /* Bottom border (double line) */ print_horizontal_line('+', '+', '+', '='); } void print_footer(void) { print_horizontal_line('+', '+', '+', '-'); } void print_entry(const Entry *entry) { printf("| %-*s | %-*s | %-*s | %-*s |\n", DATE_LENGTH, entry->date, TIME_LENGTH, entry->time, CATEGORY_LENGTH, entry->category, TXTMSG_LENGTH, entry->text); } /* Parse a fixed-length field followed by a delimiter */ static const char *parse_fixed_field(const char *ptr, char *dest, int length, char delimiter) { if ((int)strlen(ptr) < length) return NULL; strncpy(dest, ptr, length); dest[length] = '\0'; ptr += length; if (*ptr != delimiter) return NULL; return ptr + 1; /* skip delimiter */ } /* Parse a variable-length field up to max_length, terminated by delimiter */ /* Truncates if field exceeds max_length but continues to find delimiter */ static const char *parse_variable_field(const char *ptr, char *dest, int max_length, char delimiter) { int i = 0; while (*ptr != '\0' && *ptr != delimiter) { if (i < max_length) { dest[i++] = *ptr; } ptr++; } dest[i] = '\0'; if (*ptr != delimiter) return NULL; return ptr + 1; /* skip delimiter */ } int parse_line(const char *line, Entry *entry) { const char *charptr = line; /* Parse date field (fixed length) */ charptr = parse_fixed_field(charptr, entry->date, DATE_LENGTH, ','); if (!charptr) return 0; /* Parse time field (fixed length) */ charptr = parse_fixed_field(charptr, entry->time, TIME_LENGTH, ','); if (!charptr) return 0; /* Parse category field (variable length) */ charptr = parse_variable_field(charptr, entry->category, CATEGORY_LENGTH, ','); if (!charptr) return 0; /* Parse text field (variable length, quoted) */ if (*charptr != '"') return 0; charptr++; charptr = parse_variable_field(charptr, entry->text, TXTMSG_LENGTH, '"'); if (!charptr) return 0; return 1; } int get_cnotes_path(char *buffer, size_t bufsize) { const char *custom_path; const char *home; size_t len; /* First check for CNOTES_PATH environment variable override */ custom_path = getenv(CNOTES_PATH_ENV); if (custom_path != NULL && strlen(custom_path) > 0) { if (strlen(custom_path) + strlen(CNOTES_FILE) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, CNOTES_FILE); return 1; } /* Fall back to HOME_ENV + CNOTES_DIR */ home = getenv(HOME_ENV); if (home == NULL) { fprintf(stderr, "Error: %s environment variable not set\n", HOME_ENV); fprintf(stderr, "Hint: Set %s to specify notes directory\n", CNOTES_PATH_ENV); return 0; } /* Build path to cnotes file */ len = strlen(home); if (strlen(CNOTES_DIR) == 0) { /* DOS mode: file in home directory directly */ if (len + strlen(CNOTES_FILE) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } sprintf(buffer, "%s" PATH_SEP_STR "%s", home, CNOTES_FILE); } else { /* Unix/Windows: HOME/CNOTES_DIR/CNOTES_FILE */ if (len + strlen(CNOTES_DIR) + strlen(CNOTES_FILE) + 3 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } sprintf(buffer, "%s" PATH_SEP_STR "%s" PATH_SEP_STR "%s", home, CNOTES_DIR, CNOTES_FILE); } return 1; } /* start with a simple cat of the file */ int main(int argc, char *argv[]) { int i; FILE *notesfile; char file_path[600]; Entry *entries; int entry_count = 0; char line[MAX_LENGTH +1]; char *filename = NULL; SortMode sort_mode = SORT_NONE; /* Insures that we won't run out of memory in DOS */ entries = (Entry *)malloc(MAX_ENTRIES * sizeof(Entry)); if (entries == NULL) { fprintf(stderr, "Error: Cannot allocate memory\n"); return 1; } /* Parse command line arguments */ for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--date") == 0) { if (sort_mode != SORT_NONE) { fprintf(stderr, "cndump: cannot specify multiple sort options\n"); fprintf(stderr, "usage: cndump [[-d|--date] | [-c|--category]] [-r|--reverse] [filename]\n"); exit(1); } sort_mode = SORT_DATE; } else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--category") == 0) { if (sort_mode != SORT_NONE) { fprintf(stderr, "cndump: cannot specify multiple sort options\n"); fprintf(stderr, "usage: cndump [[-d|--date] | [-c|--category]] [-r|--reverse] [filename]\n"); exit(1); } sort_mode = SORT_CATEGORY; } else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--reverse") == 0) { g_sort_order = 1; /* Ascending order */ } else { filename = argv[i]; } } /* TODO: filename argument is parsed but not yet used */ (void)filename; /* Get cnotes file path */ if (!get_cnotes_path(file_path, sizeof(file_path))) { return 1; } notesfile = fopen(file_path, "r"); if (notesfile == NULL) { fprintf(stderr, "Error: Cannot open file '%s'\n", file_path); fprintf(stderr, "Hint: Use 'cnadd' to create your first entry\n"); return 1; } /* Read all entries into memory */ while (fgets(line, MAX_LENGTH, notesfile) != NULL && entry_count < MAX_ENTRIES) { if (parse_line(line, &entries[entry_count])) { entry_count++; } else { fprintf(stderr, "cndump: invalid line: %s\n", line); } } fclose(notesfile); /* Sort if requested */ if (sort_mode == SORT_DATE) { qsort(entries, entry_count, sizeof(Entry), compare_by_date); } else if (sort_mode == SORT_CATEGORY) { qsort(entries, entry_count, sizeof(Entry), compare_by_category); } /* Print the table */ print_header(); for (i = 0; i < entry_count; i++) { print_entry(&entries[i]); } print_footer(); free(entries); return 0; }