moving to a semi-immutable model. Entries can be archived, but not deleted or edited.

This commit is contained in:
Gregory Gauthier 2026-01-30 14:51:49 +00:00
parent 44f48c2f76
commit 6ab92f96b9
6 changed files with 215 additions and 100 deletions

View File

@ -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:

View File

@ -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)
*

View File

@ -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", "----------", "-----");
}

View File

@ -1,5 +1,5 @@
/*
* cndel - Delete note entries
* cndel - Archive note entries (move to archive file)
*/
#include <stdio.h>
@ -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;
}

View File

@ -1,5 +1,5 @@
/*
* Created by gmgauthier on 05/10/2025.
* cndump - Display note entries
*/
#include <stdio.h>
@ -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]);
}

View File

@ -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;