commit 7b25576232366a69091c0c509f27c56b1357564f Author: Greg Gauthier Date: Sat Oct 4 14:54:38 2025 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..990936c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +cmake-build-debug/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4bb0d38 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 4.0) +project(cnotes C) + +set(CMAKE_C_STANDARD 90) + +add_executable(dumpnotes dumpnotes.c) +add_executable(addnote addnote.c platform.h) + diff --git a/addnote.c b/addnote.c new file mode 100644 index 0000000..82dc35d --- /dev/null +++ b/addnote.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include + +#include "platform.h" + +#define CATEGORY_LENGTH 10 +#define FREETEXT_MAX_LENGTH 80 +#define DEFAULT_CATEGORY "General" +#define CNOTES_DIR ".local/share/cnotes" +#define CNOTES_FILE "cnotes.csv" + +int ensure_directory_exists(const char *path) { + struct stat st; + + if (stat(path, &st) == 0) { + /* Path exists, check if it's a directory */ + if (S_ISDIR(st.st_mode)) { + return 1; + } else { + fprintf(stderr, "Error: %s exists but is not a directory\n", path); + return 0; + } + } + + /* Directory doesn't exist, try to create it */ + if (mkdir_portable(path) != 0) { + fprintf(stderr, "Error: Cannot create directory %s\n", path); + 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 */ + len = strlen(home); + if (len + strlen(CNOTES_DIR) + 2 > bufsize) { + fprintf(stderr, "Error: Path too long\n"); + return 0; + } + + sprintf(buffer, "%s/%s", home, CNOTES_DIR); + return 1; +} + +int write_entry(const char *category, const char *message) { + char dir_path[512]; + char file_path[600]; + FILE *file; + time_t now; + struct tm *tm_info; + char date[11]; + char time_str[6]; + char padded_category[CATEGORY_LENGTH + 1]; + char truncated_message[FREETEXT_MAX_LENGTH + 1]; + int i; + + /* Get current date and time */ + now = time(NULL); + if (now == -1) { + fprintf(stderr, "Error: Cannot get current time\n"); + return 0; + } + + tm_info = localtime(&now); + if (tm_info == NULL) { + fprintf(stderr, "Error: Cannot convert time\n"); + return 0; + } + + /* Format date as YYYY-MM-DD */ + strftime(date, sizeof(date), "%Y-%m-%d", tm_info); + + /* Format time as HH:MM */ + strftime(time_str, sizeof(time_str), "%H:%M", tm_info); + + /* Pad category to 10 characters */ + strncpy(padded_category, category, CATEGORY_LENGTH); + padded_category[CATEGORY_LENGTH] = '\0'; + for (i = (int)strlen(padded_category); i < CATEGORY_LENGTH; i++) { + padded_category[i] = ' '; + } + padded_category[CATEGORY_LENGTH] = '\0'; + + /* Truncate message to 80 characters */ + strncpy(truncated_message, message, FREETEXT_MAX_LENGTH); + truncated_message[FREETEXT_MAX_LENGTH] = '\0'; + + /* Get cnotes directory path */ + if (!get_cnotes_path(dir_path, sizeof(dir_path))) { + return 0; + } + + /* Ensure directory exists */ + if (!ensure_directory_exists(dir_path)) { + return 0; + } + + /* Build full file path */ + sprintf(file_path, "%s/%s", dir_path, CNOTES_FILE); + + /* Open file in append mode */ + file = fopen(file_path, "a"); + if (file == NULL) { + fprintf(stderr, "Error: Cannot open file %s\n", file_path); + return 0; + } + + /* Write entry */ + fprintf(file, "%s,%s,%s,\"%s\"\n", + date, time_str, padded_category, truncated_message); + + fclose(file); + + printf("Entry added to %s\n", file_path); + return 1; +} + +void print_usage(const char *prog_name) { + fprintf(stderr, "Usage: %s [-c category] message\n", prog_name); + fprintf(stderr, " -c category Optional category (max 10 chars, default: %s)\n", + DEFAULT_CATEGORY); + fprintf(stderr, " message Required message text (max 80 chars). Wrapped in double-quotes.\n"); +} + +int main(int argc, char *argv[]) { + const char *category; + const char *message; + int i; + + category = DEFAULT_CATEGORY; + message = NULL; + + /* Parse command-line arguments */ + i = 1; + while (i < argc) { + if (strcmp(argv[i], "-c") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "Error: -c option requires an argument\n"); + print_usage(argv[0]); + return 1; + } + category = argv[i + 1]; + i += 2; + } else { + /* This is the message */ + message = argv[i]; + i++; + break; /* Only take first non-option as message */ + } + } + + /* Validate arguments */ + if (message == NULL || strlen(message) == 0) { + fprintf(stderr, "Error: Message is required\n"); + print_usage(argv[0]); + return 1; + } + + /* Write entry */ + if (!write_entry(category, message)) { + return 1; + } + + return 0; +} diff --git a/dumpnotes.c b/dumpnotes.c new file mode 100644 index 0000000..87313fc --- /dev/null +++ b/dumpnotes.c @@ -0,0 +1,194 @@ +#include +#include +#include + +#define MAX_LINE_LENGTH 256 +#define DATE_LENGTH 10 +#define TIME_LENGTH 5 +#define CATEGORY_LENGTH 10 +#define FREETEXT_MAX_LENGTH 80 +#define CNOTES_DIR ".local/share/cnotes" +#define CNOTES_FILE "cnotes.csv" + +typedef struct { + char date[DATE_LENGTH + 1]; + char time[TIME_LENGTH + 1]; + char category[CATEGORY_LENGTH + 1]; + char freetext[FREETEXT_MAX_LENGTH + 1]; +} Entry; + +/* 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); + 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"); + + /* Bottom border (double line) */ + 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); +} + +void print_footer(void) { + print_horizontal_line('+', '+', '+', '-'); +} + +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; + + 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; + char file_path[600]; + char line[MAX_LINE_LENGTH]; + Entry entry; + int line_count; + int first_entry; + + /* Get cnotes file path */ + if (!get_cnotes_path(file_path, sizeof(file_path))) { + return 1; + } + + file = fopen(file_path, "r"); + if (file == 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); + } + } + + if (!first_entry) { + print_footer(); + } else { + printf("No valid entries found.\n"); + } + + fclose(file); + return 0; +} diff --git a/platform.h b/platform.h new file mode 100644 index 0000000..70afbd2 --- /dev/null +++ b/platform.h @@ -0,0 +1,14 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +#ifdef _WIN32 + #define mkdir_portable(path) mkdir(path) + #define PATH_SEPARATOR '\\' + #define HOME_ENV "USERPROFILE" +#else + #define mkdir_portable(path) mkdir(path, 0755) + #define PATH_SEPARATOR '/' + #define HOME_ENV "HOME" +#endif + +#endif