diff --git a/README.md b/README.md index c7b5c7e..46fa0ee 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,14 @@ cndump -d # sort by date (newest first) cndump -c # sort by category cndump -r # reverse sort order cndump -d -r # sort by date, oldest first +cndump -a # display archived entries ``` Options: - `-d, --date` - Sort by date/time - `-c, --category` - Sort by category - `-r, --reverse` - Reverse sort order +- `-a, --archive` - Show archived entries instead of active ### cncount @@ -82,29 +84,34 @@ Display note statistics. cncount # total entry count cncount -c # count by category cncount -d # count by date +cncount -a # count archived entries +cncount -a -c # count archived entries by category ``` Options: - `-c, --category` - Show counts per category - `-d, --date` - Show counts per date +- `-a, --archive` - Count archived entries instead of active ### cndel -Delete note entries. +Archive note entries. Entries are moved to `cnotes.arc` rather than permanently deleted. ```bash -cndel -n 5 # delete entry at line 5 -cndel -l # delete the last (most recent) entry -cndel -d 2025-01-30 # delete all entries from a specific date -cndel -n 5 -y # delete without confirmation prompt +cndel -n 5 # archive entry at line 5 +cndel -l # archive the last (most recent) entry +cndel -d 2025-01-30 # archive all entries from a specific date +cndel -n 5 -y # archive without confirmation prompt ``` Options: -- `-n LINE_NUMBER` - Delete entry at specified line number -- `-d DATE` - Delete all entries matching DATE (YYYY-MM-DD) -- `-l` - Delete the last (most recent) entry +- `-n LINE_NUMBER` - Archive entry at specified line number +- `-d DATE` - Archive all entries matching DATE (YYYY-MM-DD) +- `-l` - Archive the last (most recent) entry - `-y` - Skip confirmation prompt +Archived entries can be viewed using `cndump -a`, searched with `cnfind -a`, or counted with `cncount -a`. + ### cnfind Search notes by text, category, or date. @@ -116,6 +123,7 @@ cnfind -d 2025-01-30 # show all entries from a specific date cnfind -c Work meeting # search 'meeting' in Work category only cnfind -i meeting # case-sensitive search cnfind -n meeting # show only match count +cnfind -a meeting # search in archived entries ``` Options: @@ -123,6 +131,7 @@ Options: - `-d DATE` - Filter by date (YYYY-MM-DD) - `-i` - Case-sensitive search (default is case-insensitive) - `-n` - Show only count of matches +- `-a, --archive` - Search archived entries instead of active The output includes line numbers for use with `cndel -n`. @@ -132,11 +141,11 @@ The output includes line numbers for use with `cndel -n`. By default, notes are stored in: -| Platform | Location | -|----------|----------| -| Unix/Linux | `~/.local/share/cnotes/cnotes.csv` | -| Windows | `%USERPROFILE%\.cnotes\cnotes.csv` | -| DOS | Current directory or `%CNOTES_HOME%\cnotes.csv` | +| Platform | Active Notes | Archived Notes | +|----------|--------------|----------------| +| Unix/Linux | `~/.local/share/cnotes/cnotes.csv` | `~/.local/share/cnotes/cnotes.arc` | +| Windows | `%USERPROFILE%\.cnotes\cnotes.csv` | `%USERPROFILE%\.cnotes\cnotes.arc` | +| DOS | `cnotes.csv` | `cnotes.arc` | ### Environment Variables @@ -172,6 +181,16 @@ Fields: - Category: padded to 10 chars - Message: quoted, max 125 chars +### Archive + +cnotes follows an immutable-log philosophy. When entries are "deleted" using `cndel`, they are moved to an archive file (`cnotes.arc`) rather than permanently removed. This provides: + +- **Recoverability** - Archived entries can be reviewed or manually restored +- **Audit trail** - A complete history of all notes is preserved +- **Data safety** - Accidental deletions are never permanent + +Use the `-a` flag with `cndump`, `cnfind`, or `cncount` to work with archived entries. + ## TODO Future commands to implement: diff --git a/include/config.h b/include/config.h index ec38c2f..cf5a312 100644 --- a/include/config.h +++ b/include/config.h @@ -19,6 +19,15 @@ #define CNOTES_FILE "cnotes.csv" #endif +/* + * CNOTES_ARCHIVE_FILE - The filename for archived (deleted) notes + * Deleted entries are moved here instead of being permanently removed. + * Use cndump -a, cnfind -a, cncount -a to view archived entries. + */ +#ifndef CNOTES_ARCHIVE_FILE +#define CNOTES_ARCHIVE_FILE "cnotes.arc" +#endif + /* * CNOTES_DIR - Default directory path (relative to home) * diff --git a/src/cncount.c b/src/cncount.c index 1ecf1e5..bd2440d 100644 --- a/src/cncount.c +++ b/src/cncount.c @@ -94,18 +94,18 @@ static int compare_by_count(const void *a, const void *b) { return eb->count - ea->count; } -int get_cnotes_path(char *buffer, size_t bufsize) { +int get_cnotes_path(char *buffer, size_t bufsize, const char *filename) { const char *custom_path; const char *home; size_t len; custom_path = getenv(CNOTES_PATH_ENV); if (custom_path != NULL && strlen(custom_path) > 0) { - if (strlen(custom_path) + strlen(CNOTES_FILE) + 2 > bufsize) { + if (strlen(custom_path) + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, filename); return 1; } @@ -118,25 +118,28 @@ int get_cnotes_path(char *buffer, size_t bufsize) { len = strlen(home); if (strlen(CNOTES_DIR) == 0) { - if (len + strlen(CNOTES_FILE) + 2 > bufsize) { + if (len + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", home, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", home, filename); } else { - if (len + strlen(CNOTES_DIR) + strlen(CNOTES_FILE) + 3 > bufsize) { + if (len + strlen(CNOTES_DIR) + strlen(filename) + 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); + sprintf(buffer, "%s" PATH_SEP_STR "%s" PATH_SEP_STR "%s", home, CNOTES_DIR, filename); } return 1; } void print_usage(const char *prog_name) { - fprintf(stderr, "Usage: %s [-c|--category] [-d|--date]\n", prog_name); + fprintf(stderr, "Usage: %s [-c|--category] [-d|--date] [-a|--archive]\n", prog_name); + fprintf(stderr, "\n"); + fprintf(stderr, "Options:\n"); fprintf(stderr, " -c, --category Count entries by category\n"); fprintf(stderr, " -d, --date Count entries by date\n"); + fprintf(stderr, " -a, --archive Count archived entries instead of active\n"); fprintf(stderr, " (no options) Show total count only\n"); } @@ -151,6 +154,8 @@ int main(int argc, char *argv[]) { CountEntry *counts; int num_entries = 0; int idx; + int count_archive = 0; + const char *target_file; /* Parse command line arguments */ for (i = 1; i < argc; i++) { @@ -170,6 +175,9 @@ int main(int argc, char *argv[]) { } mode = COUNT_BY_DATE; } + else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) { + count_archive = 1; + } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { print_usage(argv[0]); return 0; @@ -191,18 +199,25 @@ int main(int argc, char *argv[]) { } } + /* Select target file */ + target_file = count_archive ? CNOTES_ARCHIVE_FILE : CNOTES_FILE; + /* Get cnotes file path */ - if (!get_cnotes_path(file_path, sizeof(file_path))) { + if (!get_cnotes_path(file_path, sizeof(file_path), target_file)) { if (counts) free(counts); 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"); + if (count_archive) { + printf("No archived entries.\n"); + } else { + fprintf(stderr, "Error: Cannot open file '%s'\n", file_path); + fprintf(stderr, "Hint: Use 'cnadd' to create your first entry\n"); + } if (counts) free(counts); - return 1; + return count_archive ? 0 : 1; } /* Read and count entries */ @@ -233,7 +248,7 @@ int main(int argc, char *argv[]) { /* Print results */ if (mode == COUNT_TOTAL) { - printf("Total entries: %d\n", total_count); + printf("Total %sentries: %d\n", count_archive ? "archived " : "", total_count); } else { /* Sort results */ @@ -245,10 +260,10 @@ int main(int argc, char *argv[]) { /* Print header */ if (mode == COUNT_BY_DATE) { - printf("%-12s %s\n", "Date", "Count"); + printf("%-12s %s%s\n", "Date", "Count", count_archive ? " (archived)" : ""); printf("%-12s %s\n", "----------", "-----"); } else { - printf("%-12s %s\n", "Category", "Count"); + printf("%-12s %s%s\n", "Category", "Count", count_archive ? " (archived)" : ""); printf("%-12s %s\n", "----------", "-----"); } diff --git a/src/cndel.c b/src/cndel.c index 241d6fb..95b5920 100644 --- a/src/cndel.c +++ b/src/cndel.c @@ -1,5 +1,5 @@ /* - * cndel - Delete note entries + * cndel - Archive note entries (move to archive file) */ #include @@ -12,18 +12,18 @@ #define DATE_LENGTH 10 #define TIME_LENGTH 5 -int get_cnotes_path(char *buffer, size_t bufsize) { +int get_cnotes_path(char *buffer, size_t bufsize, const char *filename) { const char *custom_path; const char *home; size_t len; custom_path = getenv(CNOTES_PATH_ENV); if (custom_path != NULL && strlen(custom_path) > 0) { - if (strlen(custom_path) + strlen(CNOTES_FILE) + 2 > bufsize) { + if (strlen(custom_path) + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, filename); return 1; } @@ -36,17 +36,17 @@ int get_cnotes_path(char *buffer, size_t bufsize) { len = strlen(home); if (strlen(CNOTES_DIR) == 0) { - if (len + strlen(CNOTES_FILE) + 2 > bufsize) { + if (len + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", home, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", home, filename); } else { - if (len + strlen(CNOTES_DIR) + strlen(CNOTES_FILE) + 3 > bufsize) { + if (len + strlen(CNOTES_DIR) + strlen(filename) + 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); + sprintf(buffer, "%s" PATH_SEP_STR "%s" PATH_SEP_STR "%s", home, CNOTES_DIR, filename); } return 1; } @@ -121,17 +121,22 @@ void print_usage(const char *prog_name) { fprintf(stderr, " %s -l [-y]\n", prog_name); fprintf(stderr, "\n"); fprintf(stderr, "Options:\n"); - fprintf(stderr, " -n LINE_NUMBER Delete entry at specified line number\n"); - fprintf(stderr, " -d DATE Delete all entries matching DATE (YYYY-MM-DD)\n"); - fprintf(stderr, " -l Delete the last (most recent) entry\n"); + fprintf(stderr, " -n LINE_NUMBER Archive entry at specified line number\n"); + fprintf(stderr, " -d DATE Archive all entries matching DATE (YYYY-MM-DD)\n"); + fprintf(stderr, " -l Archive the last (most recent) entry\n"); fprintf(stderr, " -y Skip confirmation prompt\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Archived entries are moved to %s and can be viewed with:\n", CNOTES_ARCHIVE_FILE); + fprintf(stderr, " cndump -a, cnfind -a, cncount -a\n"); } int main(int argc, char *argv[]) { int i; FILE *infile; FILE *outfile; + FILE *archivefile; char file_path[600]; + char archive_path[600]; char temp_path[610]; char line[MAX_LENGTH + 1]; int line_num; @@ -140,7 +145,7 @@ int main(int argc, char *argv[]) { int delete_last = 0; int skip_confirm = 0; int total_lines = 0; - int deleted_count = 0; + int archived_count = 0; int confirm; char response[10]; @@ -201,7 +206,11 @@ int main(int argc, char *argv[]) { } /* Get file paths */ - if (!get_cnotes_path(file_path, sizeof(file_path))) { + if (!get_cnotes_path(file_path, sizeof(file_path), CNOTES_FILE)) { + return 1; + } + + if (!get_cnotes_path(archive_path, sizeof(archive_path), CNOTES_ARCHIVE_FILE)) { return 1; } @@ -224,20 +233,20 @@ int main(int argc, char *argv[]) { fclose(infile); if (total_lines == 0) { - fprintf(stderr, "cndel: no entries to delete\n"); + fprintf(stderr, "cndel: no entries to archive\n"); return 1; } target_line = total_lines; } - /* First pass: show what will be deleted and count matches */ + /* First pass: show what will be archived and count matches */ infile = fopen(file_path, "r"); if (infile == NULL) { fprintf(stderr, "Error: Cannot open file '%s'\n", file_path); return 1; } - printf("The following entries will be deleted:\n\n"); + printf("The following entries will be archived:\n\n"); line_num = 0; while (fgets(line, MAX_LENGTH, infile) != NULL) { if (strlen(line) < DATE_LENGTH) continue; @@ -245,16 +254,16 @@ int main(int argc, char *argv[]) { if (target_line > 0 && line_num == target_line) { display_entry(line_num, line); - deleted_count++; + archived_count++; } else if (target_date != NULL && strncmp(line, target_date, DATE_LENGTH) == 0) { display_entry(line_num, line); - deleted_count++; + archived_count++; } } fclose(infile); - if (deleted_count == 0) { + if (archived_count == 0) { if (target_line > 0) { fprintf(stderr, "cndel: line %d does not exist (file has %d entries)\n", target_line, line_num); @@ -264,9 +273,9 @@ int main(int argc, char *argv[]) { return 1; } - printf("\n%d entry(s) will be deleted.\n", deleted_count); + printf("\n%d entry(s) will be archived.\n", archived_count); - /* Confirm deletion */ + /* Confirm archival */ if (!skip_confirm) { printf("Are you sure? [y/N]: "); fflush(stdout); @@ -283,7 +292,7 @@ int main(int argc, char *argv[]) { } } - /* Second pass: copy all lines except deleted ones to temp file */ + /* Second pass: copy kept lines to temp, archived lines to archive */ infile = fopen(file_path, "r"); if (infile == NULL) { fprintf(stderr, "Error: Cannot open file '%s'\n", file_path); @@ -297,8 +306,17 @@ int main(int argc, char *argv[]) { return 1; } + archivefile = fopen(archive_path, "a"); + if (archivefile == NULL) { + fprintf(stderr, "Error: Cannot open archive file '%s'\n", archive_path); + fclose(infile); + fclose(outfile); + remove(temp_path); + return 1; + } + line_num = 0; - deleted_count = 0; + archived_count = 0; while (fgets(line, MAX_LENGTH, infile) != NULL) { if (strlen(line) < DATE_LENGTH) { /* Preserve empty/invalid lines as-is */ @@ -308,20 +326,24 @@ int main(int argc, char *argv[]) { line_num++; if (target_line > 0 && line_num == target_line) { - deleted_count++; - continue; /* Skip this line */ + /* Move to archive */ + fputs(line, archivefile); + archived_count++; } else if (target_date != NULL && strncmp(line, target_date, DATE_LENGTH) == 0) { - deleted_count++; - continue; /* Skip this line */ + /* Move to archive */ + fputs(line, archivefile); + archived_count++; + } + else { + /* Keep this line */ + fputs(line, outfile); } - - /* Keep this line */ - fputs(line, outfile); } fclose(infile); fclose(outfile); + fclose(archivefile); /* Replace original with temp file */ if (remove(file_path) != 0) { @@ -335,6 +357,6 @@ int main(int argc, char *argv[]) { return 1; } - printf("Deleted %d entry(s).\n", deleted_count); + printf("Archived %d entry(s) to %s\n", archived_count, CNOTES_ARCHIVE_FILE); return 0; } diff --git a/src/cndump.c b/src/cndump.c index 46ca072..76cfa99 100644 --- a/src/cndump.c +++ b/src/cndump.c @@ -1,5 +1,5 @@ /* - * Created by gmgauthier on 05/10/2025. + * cndump - Display note entries */ #include @@ -68,7 +68,7 @@ void print_horizontal_line(char left, char middle, char right, char fill) { printf("%c\n", right); } -void print_header(void) { +void print_header(int is_archive) { /* Top border (double line) */ print_horizontal_line('+', '+', '+', '='); @@ -77,7 +77,7 @@ void print_header(void) { DATE_LENGTH, "Date", TIME_LENGTH, "Time", CATEGORY_LENGTH, "Category", - TXTMSG_LENGTH, "Free Text"); + TXTMSG_LENGTH, is_archive ? "Free Text (ARCHIVED)" : "Free Text"); /* Bottom border (double line) */ print_horizontal_line('+', '+', '+', '='); @@ -145,7 +145,7 @@ int parse_line(const char *line, Entry *entry) { return 1; } -int get_cnotes_path(char *buffer, size_t bufsize) { +int get_cnotes_path(char *buffer, size_t bufsize, const char *filename) { const char *custom_path; const char *home; size_t len; @@ -153,11 +153,11 @@ int get_cnotes_path(char *buffer, size_t bufsize) { /* 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) { + if (strlen(custom_path) + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, filename); return 1; } @@ -173,23 +173,32 @@ int get_cnotes_path(char *buffer, size_t bufsize) { len = strlen(home); if (strlen(CNOTES_DIR) == 0) { /* DOS mode: file in home directory directly */ - if (len + strlen(CNOTES_FILE) + 2 > bufsize) { + if (len + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", home, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", home, filename); } else { - /* Unix/Windows: HOME/CNOTES_DIR/CNOTES_FILE */ - if (len + strlen(CNOTES_DIR) + strlen(CNOTES_FILE) + 3 > bufsize) { + /* Unix/Windows: HOME/CNOTES_DIR/filename */ + if (len + strlen(CNOTES_DIR) + strlen(filename) + 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); + sprintf(buffer, "%s" PATH_SEP_STR "%s" PATH_SEP_STR "%s", home, CNOTES_DIR, filename); } return 1; } -/* start with a simple cat of the file */ +void print_usage(const char *prog_name) { + fprintf(stderr, "Usage: %s [-d|--date] [-c|--category] [-r|--reverse] [-a|--archive]\n", prog_name); + fprintf(stderr, "\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -d, --date Sort by date/time\n"); + fprintf(stderr, " -c, --category Sort by category\n"); + fprintf(stderr, " -r, --reverse Reverse sort order\n"); + fprintf(stderr, " -a, --archive Show archived entries instead of active\n"); +} + int main(int argc, char *argv[]) { int i; FILE *notesfile; @@ -197,8 +206,9 @@ int main(int argc, char *argv[]) { Entry *entries; int entry_count = 0; char line[MAX_LENGTH +1]; - char *filename = NULL; SortMode sort_mode = SORT_NONE; + int show_archive = 0; + const char *target_file; /* Insures that we won't run out of memory in DOS */ entries = (Entry *)malloc(MAX_ENTRIES * sizeof(Entry)); @@ -212,42 +222,58 @@ int main(int argc, char *argv[]) { 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); + print_usage(argv[0]); + free(entries); + return 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); + print_usage(argv[0]); + free(entries); + return 1; } sort_mode = SORT_CATEGORY; } - else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--reverse") == 0) { g_sort_order = 1; /* Ascending order */ } - + else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) { + show_archive = 1; + } + else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + free(entries); + return 0; + } else { - filename = argv[i]; + fprintf(stderr, "cndump: unknown option '%s'\n", argv[i]); + print_usage(argv[0]); + free(entries); + return 1; } } - /* TODO: filename argument is parsed but not yet used */ - (void)filename; + /* Select target file */ + target_file = show_archive ? CNOTES_ARCHIVE_FILE : CNOTES_FILE; /* Get cnotes file path */ - if (!get_cnotes_path(file_path, sizeof(file_path))) { + if (!get_cnotes_path(file_path, sizeof(file_path), target_file)) { + free(entries); 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"); + if (show_archive) { + fprintf(stderr, "No archived entries found.\n"); + } else { + fprintf(stderr, "Error: Cannot open file '%s'\n", file_path); + fprintf(stderr, "Hint: Use 'cnadd' to create your first entry\n"); + } + free(entries); return 1; } @@ -262,6 +288,16 @@ int main(int argc, char *argv[]) { } fclose(notesfile); + if (entry_count == 0) { + if (show_archive) { + printf("No archived entries.\n"); + } else { + printf("No entries found.\n"); + } + free(entries); + return 0; + } + /* Sort if requested */ if (sort_mode == SORT_DATE) { qsort(entries, entry_count, sizeof(Entry), compare_by_date); @@ -271,7 +307,7 @@ int main(int argc, char *argv[]) { } /* Print the table */ - print_header(); + print_header(show_archive); for (i = 0; i < entry_count; i++) { print_entry(&entries[i]); } diff --git a/src/cnfind.c b/src/cnfind.c index 0731478..322ba30 100644 --- a/src/cnfind.c +++ b/src/cnfind.c @@ -88,18 +88,18 @@ static int parse_line(const char *line, Entry *entry) { return 1; } -int get_cnotes_path(char *buffer, size_t bufsize) { +int get_cnotes_path(char *buffer, size_t bufsize, const char *filename) { const char *custom_path; const char *home; size_t len; custom_path = getenv(CNOTES_PATH_ENV); if (custom_path != NULL && strlen(custom_path) > 0) { - if (strlen(custom_path) + strlen(CNOTES_FILE) + 2 > bufsize) { + if (strlen(custom_path) + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", custom_path, filename); return 1; } @@ -112,17 +112,17 @@ int get_cnotes_path(char *buffer, size_t bufsize) { len = strlen(home); if (strlen(CNOTES_DIR) == 0) { - if (len + strlen(CNOTES_FILE) + 2 > bufsize) { + if (len + strlen(filename) + 2 > bufsize) { fprintf(stderr, "Error: Path too long\n"); return 0; } - sprintf(buffer, "%s" PATH_SEP_STR "%s", home, CNOTES_FILE); + sprintf(buffer, "%s" PATH_SEP_STR "%s", home, filename); } else { - if (len + strlen(CNOTES_DIR) + strlen(CNOTES_FILE) + 3 > bufsize) { + if (len + strlen(CNOTES_DIR) + strlen(filename) + 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); + sprintf(buffer, "%s" PATH_SEP_STR "%s" PATH_SEP_STR "%s", home, CNOTES_DIR, filename); } return 1; } @@ -152,14 +152,14 @@ void print_horizontal_line(char left, char middle, char right, char fill) { printf("%c\n", right); } -void print_header(void) { +void print_header(int is_archive) { print_horizontal_line('+', '+', '+', '='); printf("| %-3s | %-*s | %-*s | %-*s | %-*s |\n", "Ln", DATE_LENGTH, "Date", TIME_LENGTH, "Time", CATEGORY_LENGTH, "Category", - TXTMSG_LENGTH, "Free Text"); + TXTMSG_LENGTH, is_archive ? "Free Text (ARCHIVED)" : "Free Text"); print_horizontal_line('+', '+', '+', '='); } @@ -186,6 +186,7 @@ void print_usage(const char *prog_name) { fprintf(stderr, " -d DATE Filter by date (YYYY-MM-DD)\n"); fprintf(stderr, " -i Case-sensitive search (default is case-insensitive)\n"); fprintf(stderr, " -n Show only count of matches\n"); + fprintf(stderr, " -a, --archive Search archived entries instead of active\n"); fprintf(stderr, " -h, --help Show this help message\n"); fprintf(stderr, "\n"); fprintf(stderr, "Examples:\n"); @@ -193,6 +194,7 @@ void print_usage(const char *prog_name) { fprintf(stderr, " %s -c Work Show all entries in Work category\n", prog_name); fprintf(stderr, " %s -d 2025-01-30 Show all entries from a date\n", prog_name); fprintf(stderr, " %s -c Work meeting Search 'meeting' in Work category\n", prog_name); + fprintf(stderr, " %s -a meeting Search archived entries\n", prog_name); } int main(int argc, char *argv[]) { @@ -211,6 +213,8 @@ int main(int argc, char *argv[]) { char *filter_date = NULL; int case_sensitive = 0; int count_only = 0; + int search_archive = 0; + const char *target_file; /* Parse command line arguments */ for (i = 1; i < argc; i++) { @@ -240,6 +244,9 @@ int main(int argc, char *argv[]) { else if (strcmp(argv[i], "-n") == 0) { count_only = 1; } + else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) { + search_archive = 1; + } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { print_usage(argv[0]); return 0; @@ -262,15 +269,22 @@ int main(int argc, char *argv[]) { return 1; } + /* Select target file */ + target_file = search_archive ? CNOTES_ARCHIVE_FILE : CNOTES_FILE; + /* Get file path */ - if (!get_cnotes_path(file_path, sizeof(file_path))) { + if (!get_cnotes_path(file_path, sizeof(file_path), target_file)) { 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"); + if (search_archive) { + fprintf(stderr, "No archived entries found.\n"); + } else { + fprintf(stderr, "Error: Cannot open file '%s'\n", file_path); + fprintf(stderr, "Hint: Use 'cnadd' to create your first entry\n"); + } return 1; } @@ -317,7 +331,7 @@ int main(int argc, char *argv[]) { match_count++; if (!count_only) { if (!header_printed) { - print_header(); + print_header(search_archive); header_printed = 1; } print_entry(&entry); @@ -335,7 +349,7 @@ int main(int argc, char *argv[]) { if (count_only) { printf("%d\n", match_count); } else { - printf("\n%d match(es) found.\n", match_count); + printf("\n%d match(es) found%s.\n", match_count, search_archive ? " in archive" : ""); } return (match_count > 0) ? 0 : 1;