moving to a semi-immutable model. Entries can be archived, but not deleted or edited.
This commit is contained in:
parent
44f48c2f76
commit
6ab92f96b9
45
README.md
45
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:
|
||||
|
||||
@ -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)
|
||||
*
|
||||
|
||||
@ -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) {
|
||||
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", "----------", "-----");
|
||||
}
|
||||
|
||||
|
||||
80
src/cndel.c
80
src/cndel.c
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
86
src/cndump.c
86
src/cndump.c
@ -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) {
|
||||
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]);
|
||||
}
|
||||
|
||||
38
src/cnfind.c
38
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) {
|
||||
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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user