dumpnotes rewrite
This commit is contained in:
		
							parent
							
								
									7b25576232
								
							
						
					
					
						commit
						2ba44e65be
					
				@ -8,7 +8,7 @@
 | 
			
		||||
#include "platform.h"
 | 
			
		||||
 | 
			
		||||
#define CATEGORY_LENGTH 10
 | 
			
		||||
#define FREETEXT_MAX_LENGTH 80
 | 
			
		||||
#define FREETEXT_MAX_LENGTH 125
 | 
			
		||||
#define DEFAULT_CATEGORY "General"
 | 
			
		||||
#define CNOTES_DIR ".local/share/cnotes"
 | 
			
		||||
#define CNOTES_FILE "cnotes.csv"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										281
									
								
								dumpnotes.c
									
									
									
									
									
								
							
							
						
						
									
										281
									
								
								dumpnotes.c
									
									
									
									
									
								
							@ -1,194 +1,253 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by gmgauthier on 05/10/2025.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_LINE_LENGTH 256
 | 
			
		||||
#define DATE_LENGTH 10
 | 
			
		||||
#define TIME_LENGTH 5
 | 
			
		||||
#define CATEGORY_LENGTH 10
 | 
			
		||||
#define FREETEXT_MAX_LENGTH 80
 | 
			
		||||
#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 CATEGORY_LENGTH 10 /* CATEGORY__ */
 | 
			
		||||
#define TXTMSG_LENGTH 125 /* Max length of a text message */
 | 
			
		||||
#define MAX_ENTRIES 5000 /* Maximum number of entries to load */
 | 
			
		||||
 | 
			
		||||
#define CNOTES_FILE "notes.csv"
 | 
			
		||||
#define CNOTES_DIR ".local/share/cnotes"
 | 
			
		||||
#define CNOTES_FILE "cnotes.csv"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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];
 | 
			
		||||
    char time[TIME_LENGTH + 1];
 | 
			
		||||
    char category[CATEGORY_LENGTH + 1];
 | 
			
		||||
    char freetext[FREETEXT_MAX_LENGTH + 1];
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ReSharper disable CppParameterMayBeConst */
 | 
			
		||||
void print_horizontal_line(char left, char middle, char right, char fill) {
 | 
			
		||||
    /* ReSharper restore CppParameterMayBeConst */
 | 
			
		||||
    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 < FREETEXT_MAX_LENGTH + 2; i++) printf("%c", fill);
 | 
			
		||||
    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",
 | 
			
		||||
           FREETEXT_MAX_LENGTH, "Free Text");
 | 
			
		||||
    
 | 
			
		||||
           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,
 | 
			
		||||
           FREETEXT_MAX_LENGTH, entry->freetext);
 | 
			
		||||
           TXTMSG_LENGTH, entry->text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void print_footer(void) {
 | 
			
		||||
    print_horizontal_line('+', '+', '+', '-');
 | 
			
		||||
 | 
			
		||||
/* 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 (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 *ptr;
 | 
			
		||||
    int i;
 | 
			
		||||
    
 | 
			
		||||
    ptr = line;
 | 
			
		||||
    
 | 
			
		||||
    /* Parse date (10 chars) */
 | 
			
		||||
    if (strlen(ptr) < DATE_LENGTH) return 0;
 | 
			
		||||
    strncpy(entry->date, ptr, DATE_LENGTH);
 | 
			
		||||
    entry->date[DATE_LENGTH] = '\0';
 | 
			
		||||
    ptr += DATE_LENGTH;
 | 
			
		||||
    
 | 
			
		||||
    /* Skip comma */
 | 
			
		||||
    if (*ptr != ',') return 0;
 | 
			
		||||
    ptr++;
 | 
			
		||||
    
 | 
			
		||||
    /* Parse time (5 chars) */
 | 
			
		||||
    if (strlen(ptr) < TIME_LENGTH) return 0;
 | 
			
		||||
    strncpy(entry->time, ptr, TIME_LENGTH);
 | 
			
		||||
    entry->time[TIME_LENGTH] = '\0';
 | 
			
		||||
    ptr += TIME_LENGTH;
 | 
			
		||||
    
 | 
			
		||||
    /* Skip comma */
 | 
			
		||||
    if (*ptr != ',') return 0;
 | 
			
		||||
    ptr++;
 | 
			
		||||
    
 | 
			
		||||
    /* Parse category (10 chars) */
 | 
			
		||||
    if (strlen(ptr) < CATEGORY_LENGTH) return 0;
 | 
			
		||||
    strncpy(entry->category, ptr, CATEGORY_LENGTH);
 | 
			
		||||
    entry->category[CATEGORY_LENGTH] = '\0';
 | 
			
		||||
    ptr += CATEGORY_LENGTH;
 | 
			
		||||
    
 | 
			
		||||
    /* Skip comma */
 | 
			
		||||
    if (*ptr != ',') return 0;
 | 
			
		||||
    ptr++;
 | 
			
		||||
    
 | 
			
		||||
    /* Parse free text (quoted) */
 | 
			
		||||
    if (*ptr != '"') return 0;
 | 
			
		||||
    ptr++;
 | 
			
		||||
    
 | 
			
		||||
    i = 0;
 | 
			
		||||
    while (*ptr != '\0' && *ptr != '"' && i < FREETEXT_MAX_LENGTH) {
 | 
			
		||||
        entry->freetext[i++] = *ptr++;
 | 
			
		||||
    }
 | 
			
		||||
    entry->freetext[i] = '\0';
 | 
			
		||||
    
 | 
			
		||||
    /* Should end with quote */
 | 
			
		||||
    if (*ptr != '"') return 0;
 | 
			
		||||
    
 | 
			
		||||
    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 *home;
 | 
			
		||||
    size_t len;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    home = getenv("HOME");
 | 
			
		||||
    if (home == NULL) {
 | 
			
		||||
        fprintf(stderr, "Error: HOME environment variable not set\n");
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /* Build path: $HOME/.local/share/cnotes/cnotes.csv */
 | 
			
		||||
    len = strlen(home);
 | 
			
		||||
    if (len + strlen(CNOTES_DIR) + strlen(CNOTES_FILE) + 3 > bufsize) {
 | 
			
		||||
        fprintf(stderr, "Error: Path too long\n");
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    sprintf(buffer, "%s/%s/%s", home, CNOTES_DIR, CNOTES_FILE);
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(void) {
 | 
			
		||||
    FILE *file;
 | 
			
		||||
/* start with a simple cat of the file */
 | 
			
		||||
main(int argc, char *argv[]) {
 | 
			
		||||
    FILE *notesfile;
 | 
			
		||||
    char file_path[600];
 | 
			
		||||
    char line[MAX_LINE_LENGTH];
 | 
			
		||||
    Entry entry;
 | 
			
		||||
    int line_count;
 | 
			
		||||
    int first_entry;
 | 
			
		||||
    
 | 
			
		||||
    Entry entries[MAX_ENTRIES];
 | 
			
		||||
    int entry_count = 0;
 | 
			
		||||
    char line[MAX_LENGTH +1];
 | 
			
		||||
    char *filename = CNOTES_FILE;
 | 
			
		||||
    SortMode sort_mode = SORT_NONE;
 | 
			
		||||
    int i;
 | 
			
		||||
 | 
			
		||||
    /* 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, "dumpnotes: cannot specify multiple sort options\n");
 | 
			
		||||
                fprintf(stderr, "usage: dumpnotes [[-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, "dumpnotes: cannot specify multiple sort options\n");
 | 
			
		||||
                fprintf(stderr, "usage: dumpnotes [[-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];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Get cnotes file path */
 | 
			
		||||
    if (!get_cnotes_path(file_path, sizeof(file_path))) {
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    file = fopen(file_path, "r");
 | 
			
		||||
    if (file == NULL) {
 | 
			
		||||
 | 
			
		||||
    notesfile = fopen(file_path, "r");
 | 
			
		||||
    if (notesfile == NULL) {
 | 
			
		||||
        fprintf(stderr, "Error: Cannot open file '%s'\n", file_path);
 | 
			
		||||
        fprintf(stderr, "Hint: Use 'addnote' to create your first entry\n");
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    line_count = 0;
 | 
			
		||||
    first_entry = 1;
 | 
			
		||||
    
 | 
			
		||||
    while (fgets(line, sizeof(line), file) != NULL) {
 | 
			
		||||
        line_count++;
 | 
			
		||||
        
 | 
			
		||||
        /* Remove trailing newline */
 | 
			
		||||
        line[strcspn(line, "\n")] = '\0';
 | 
			
		||||
        
 | 
			
		||||
        /* Skip empty lines */
 | 
			
		||||
        if (strlen(line) == 0) continue;
 | 
			
		||||
        
 | 
			
		||||
        if (parse_line(line, &entry)) {
 | 
			
		||||
            if (first_entry) {
 | 
			
		||||
                print_header();
 | 
			
		||||
                first_entry = 0;
 | 
			
		||||
            }
 | 
			
		||||
            print_entry(&entry);
 | 
			
		||||
        } else {
 | 
			
		||||
            fprintf(stderr, "Warning: Skipping malformed line %d\n", line_count);
 | 
			
		||||
 | 
			
		||||
    /* 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, "dumpnotes: invalid line: %s\n", line);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (!first_entry) {
 | 
			
		||||
        print_footer();
 | 
			
		||||
    } else {
 | 
			
		||||
        printf("No valid entries found.\n");
 | 
			
		||||
    fclose(notesfile);
 | 
			
		||||
 | 
			
		||||
    /* Sort if requested */
 | 
			
		||||
    if (sort_mode == SORT_DATE) {
 | 
			
		||||
        qsort(entries, entry_count, sizeof(Entry), compare_by_date);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fclose(file);
 | 
			
		||||
    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();
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user