initial commit
This commit is contained in:
commit
7b25576232
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.idea/
|
||||
cmake-build-debug/
|
8
CMakeLists.txt
Normal file
8
CMakeLists.txt
Normal file
@ -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)
|
||||
|
178
addnote.c
Normal file
178
addnote.c
Normal file
@ -0,0 +1,178 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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;
|
||||
}
|
194
dumpnotes.c
Normal file
194
dumpnotes.c
Normal file
@ -0,0 +1,194 @@
|
||||
#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 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;
|
||||
}
|
14
platform.h
Normal file
14
platform.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user