log

Minimalist command line note manager
git clone https://codeberg.org/night0721/log
Log | Files | Refs | README | LICENSE

log.c (13201B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <errno.h>
      5 #include <ftw.h>
      6 #include <unistd.h>
      7 #include <time.h>
      8 #include <dirent.h>
      9 #include <sys/stat.h>
     10 #include <libgen.h>
     11 #include <linux/limits.h>
     12 
     13 #include "config.h"
     14 
     15 char *get_log_dir() {
     16     char *xdg_data_home = getenv("XDG_DATA_HOME");
     17     char *dirname = "log";
     18     char *log_dir = NULL;
     19     if (xdg_data_home == NULL) {
     20         xdg_data_home = getenv("HOME");
     21         if (xdg_data_home == NULL) {
     22             fprintf(stderr, "log: HOME and XDG_DATA_HOME environment variable not set\n");
     23             exit(EXIT_FAILURE);
     24         }
     25     }
     26     log_dir = malloc(sizeof(char) * (strlen(xdg_data_home) + strlen(dirname) + 2));
     27     if (log_dir == NULL) {
     28         fprintf(stderr, "log: Error allocating memory\n");
     29         exit(EXIT_FAILURE);
     30     }
     31     sprintf(log_dir, "%s/%s", xdg_data_home, dirname);
     32     struct stat dir_stat;
     33     if (!((stat(log_dir, &dir_stat) == 0) && S_ISDIR(dir_stat.st_mode))) { // check defined path is directory
     34         if (mkdir(log_dir, S_IRWXU)) { // 700
     35             fprintf(stderr, "log: Cannot create log directory\n");
     36             exit(EXIT_FAILURE);
     37         }
     38     }
     39     return log_dir;
     40 }
     41 
     42 int compare(const void *a, const void *b) {
     43     return strcmp(*(const char **)a, *(const char **)b);
     44 }
     45 
     46 void tree(const char *basepath, int depth) {
     47     char path[1000];
     48     struct dirent *dp;
     49     DIR *dir = opendir(basepath);
     50 
     51     if (!dir)
     52         return;
     53 
     54     char *files[1000];
     55     int file_count = 0;
     56 
     57     while ((dp = readdir(dir)) != NULL) {
     58         if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) {
     59             files[file_count] = strdup(dp->d_name);
     60             file_count++;
     61         }
     62     }
     63 
     64     qsort(files, file_count, sizeof(char *), compare);
     65 
     66     for (int i = 0; i < file_count; i++) {
     67         for (int j = 0; j < depth - 1; j++) {
     68             printf("│   ");
     69         }
     70 
     71         if (depth > 0) {
     72             printf("├── ");
     73         }
     74 
     75         printf("%s\n", files[i]);
     76 
     77         strcpy(path, basepath);
     78         strcat(path, "/");
     79         strcat(path, files[i]);
     80 
     81         tree(path, depth + 1);
     82 
     83         free(files[i]);
     84     }
     85 
     86     closedir(dir);
     87 }
     88 
     89 int list_dir(const char *dir) {
     90     if (dir == NULL || *dir == '\0') {
     91         fprintf(stderr, "log: Invalid directory path\n");
     92         return 1;
     93     }
     94     struct stat dir_stat;
     95     if (stat(dir, &dir_stat) == -1) {
     96         perror("log");
     97         return errno;
     98     }
     99     if (S_ISDIR(dir_stat.st_mode)) {
    100         tree(dir, 0);
    101         return 0;
    102 
    103     } else {
    104         fprintf(stderr, "log: %s is not a directory\n", dir);
    105         return 1;
    106     }
    107 }
    108 
    109 void find_note(const char *basepath, const char *filename, char **filepaths, int *filecount) {
    110     DIR *dir;
    111     struct dirent *entry;
    112 
    113     if (!(dir = opendir(basepath))) {
    114         return;
    115     }
    116 
    117     while ((entry = readdir(dir)) != NULL) {
    118         if (entry->d_type == DT_DIR) {
    119             if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
    120                 continue;
    121             }
    122             char path[1024];
    123             snprintf(path, sizeof(path), "%s/%s", basepath, entry->d_name);
    124             find_note(path, filename, filepaths, filecount);
    125         } else {
    126             char *ext = strrchr(entry->d_name, '.');
    127             if (ext == NULL) {
    128                 continue;
    129             }
    130             // remove extension
    131             char *bname = strdup(entry->d_name);
    132             bname[strlen(bname) - strlen(DEFAULT_EXT)] = '\0';
    133             if (strcmp(bname, filename) == 0) {
    134                 int count = *filecount;
    135                 size_t len = strlen(basepath) + strlen(bname) + 2 + strlen(DEFAULT_EXT);
    136                 filepaths[count] = malloc(sizeof(char) * len);
    137                 snprintf(filepaths[count], len, "%s/%s%s", basepath, bname, DEFAULT_EXT);
    138                 *filecount += 1;
    139             }
    140         }
    141     }
    142     closedir(dir);
    143 }
    144 
    145 // check if note exists, if yes return the path
    146 char *check_note_exist(char *filename) {
    147     char *log_dir = get_log_dir();
    148     char **file_paths = malloc(sizeof(char *) * 128); // max 128 same file names
    149     int file_count = 0;
    150     find_note(log_dir, (const char *) filename, file_paths, &file_count);
    151     if (file_count == 0) {
    152         free(log_dir);
    153         return NULL;
    154     } else if (file_count == 1) {
    155         char *filepath = malloc(sizeof(char) * (strlen(file_paths[0]) + 1));
    156         if (filepath == NULL) {
    157             fprintf(stderr, "log: Error allocating memory\n");
    158             free(log_dir);
    159             exit(EXIT_FAILURE);
    160         }
    161         strcpy(filepath, file_paths[0]);
    162         free(log_dir);
    163         return filepath;
    164     } else {
    165         printf("Multiple files found with the same name:\n");
    166         for (int i = 0; i < file_count; i++) {
    167             printf("%d. %s\n", i + 1, file_paths[i]);
    168         }
    169         printf("Enter the number corresponding to the file you want to use: ");
    170         int choice;
    171         scanf("%d", &choice);
    172         if (choice < 1 || choice > file_count) {
    173             fprintf(stderr, "log: Invalid choice\n");
    174             free(log_dir);
    175             return NULL;
    176         }
    177         char *filepath = malloc(sizeof(char) * (strlen(file_paths[choice - 1]) + 1));
    178         if (filepath == NULL) {
    179             fprintf(stderr, "log: Error allocating memory\n");
    180             free(log_dir);
    181             exit(EXIT_FAILURE);
    182         }
    183         strcpy(filepath, file_paths[choice - 1]);
    184         free(log_dir);
    185         return filepath;
    186     }
    187 }
    188 
    189 void add_boiler_plate(FILE *file, const char *filename, const char *ext) {
    190     time_t t = time(NULL);
    191     struct tm tm = *localtime(&t);
    192     if (strcmp(ext, ".md") == 0) {
    193         fprintf(file, "---\n");
    194         fprintf(file, "title: %s\n", filename);
    195         fprintf(file, "description: \n");
    196         fprintf(file, "tags: \n");
    197         fprintf(file, "created: \"%d-%02d-%02d\"\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
    198         fprintf(file, "lastmod: \"%d-%02d-%02d\"\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
    199         fprintf(file, "---\n");
    200     } else if (strcmp(ext, ".txt") == 0) {
    201         fprintf(file, "Title: %s\n", filename);
    202         fprintf(file, "Description: \n");
    203         fprintf(file, "Tags: \n");
    204         fprintf(file, "Created: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    205         fprintf(file, "Last Modified: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    206     } else if (strcmp(ext, ".org") == 0) {
    207         fprintf(file, "#+TITLE: %s\n", filename);
    208         fprintf(file, "#+DESCRIPTION: \n");
    209         fprintf(file, "#+TAGS: \n");
    210         fprintf(file, "#+CREATED: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    211         fprintf(file, "#+LASTMOD: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    212     } else if (strcmp(ext, ".html") == 0) {
    213         fprintf(file, "<!DOCTYPE html>\n");
    214         fprintf(file, "<html>\n");
    215         fprintf(file, "\t<head>\n");
    216         fprintf(file, "\t\t<title>%s</title>\n", filename);
    217         fprintf(file, "\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n");
    218         fprintf(file, "\t\t<meta charset=\"UTF-8\">\n");
    219         fprintf(file, "\t</head>\n");
    220         fprintf(file, "\t<body>\n");
    221         fprintf(file, "\t\t<h1>%s</h1>\n", filename);
    222         fprintf(file, "\t\t<p>Description: </p>\n");
    223         fprintf(file, "\t\t<p>Tags: </p>\n");
    224         fprintf(file, "\t\t<p>Created: %d-%02d-%02d %02d:%02d:%02d</p>\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    225         fprintf(file, "\t\t<p>Last Modified: %d-%02d-%02d %02d:%02d:%02d</p>\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    226         fprintf(file, "\t</body>\n");
    227         fprintf(file, "</html>\n");
    228     } else {
    229         fprintf(stderr, "log: Unsupported file extension\n");
    230     }
    231 }
    232 void print_note(char *filename) {
    233     if (filename == NULL) {
    234         fprintf(stderr, "log: filename is required\n");
    235         return;
    236     }
    237     char *filepath = check_note_exist(filename);
    238     if (filepath != NULL) {
    239         FILE *file = fopen(filepath, "r");
    240         if (file == NULL) {
    241             perror("log");
    242             free(filepath);
    243             return;
    244         }
    245         char *line = NULL;
    246         size_t len = 0;
    247         while (getline(&line, &len, file) != -1) {
    248             printf("%s", line);
    249         }
    250         free(line);
    251         fclose(file);
    252     } else {
    253         fprintf(stderr, "log: %s is not in log\n", filename);
    254     }
    255 }
    256 
    257 void edit_note(char *filename) {
    258     char *filepath = check_note_exist(filename);
    259     char *editor = getenv("EDITOR");
    260     if (editor == NULL) {
    261         fprintf(stderr, "log: EDITOR environment variable must be set\n");
    262         free(filepath);
    263         return;
    264     }
    265     if (filepath != NULL) {
    266         char *cmd = malloc(sizeof(char) * (strlen(editor) + strlen(filepath) + 2));
    267         if (cmd == NULL) {
    268             fprintf(stderr, "log: Error allocating memory\n");
    269             free(filepath);
    270             return;
    271         }
    272         sprintf(cmd, "%s %s", editor, filepath);
    273         system(cmd);
    274         free(cmd);
    275     } else {
    276         char *log_dir = get_log_dir();
    277         filepath = malloc(sizeof(char) * (strlen(log_dir) + strlen(filename) + 2 + strlen(DEFAULT_EXT)));
    278         if (filepath == NULL) {
    279             fprintf(stderr, "log: Error allocating memory\n");
    280             free(log_dir);
    281             return;
    282         }
    283         sprintf(filepath, "%s/%s%s", log_dir, filename, DEFAULT_EXT);
    284         FILE *file = fopen(filepath, "w");
    285         if (file == NULL) {
    286             perror("log");
    287             free(filepath);
    288             return;
    289         }
    290         add_boiler_plate(file, filename, DEFAULT_EXT);
    291         fclose(file);
    292         char *cmd = malloc(sizeof(char) * (strlen(editor) + strlen(filepath) + 2));
    293         if (cmd == NULL) {
    294             fprintf(stderr, "log: Error allocating memory\n");
    295             free(filepath);
    296             return;
    297         }
    298         sprintf(cmd, "%s %s", editor, filepath);
    299         system(cmd);
    300         free(cmd);
    301     }
    302 }
    303 
    304 void remove_note(char *filename) {
    305     char *filepath = check_note_exist(filename);
    306     if (filepath != NULL) {
    307         if (remove(filepath) == -1) {
    308             perror("log");
    309         } else {
    310             printf("log: %s is now removed from log\n", filename);
    311         }
    312     } else {
    313         fprintf(stderr, "log: %s is not in log\n", filename);
    314     }
    315 }
    316 
    317 void search_note(char *filename) {
    318     char *filepath = check_note_exist(filename);
    319     if (filepath != NULL) {
    320         printf("Path of %s: %s\n", filename, filepath);
    321     } else {
    322         fprintf(stderr, "log: %s is not in log\n", filename);
    323     }
    324 }
    325 
    326 void usage(char **argv) {
    327     fprintf(stderr, "Usage: %s [-l] [-i] [-v] [-h]\n", argv[0]);
    328     fprintf(stderr, "       %s [-I|-Q|-R|-S] [filename]\n", argv[0]);
    329 }
    330 
    331 int main(int argc, char **argv) {
    332     int res;
    333     char *log_dir = get_log_dir();
    334     if (log_dir == NULL) {
    335         return 1;
    336     }
    337 
    338     if (argc == 2) {
    339         if (strcmp(argv[1], "-l") == 0) {
    340             // read dir and print recursively
    341             res = list_dir(log_dir);
    342             free(log_dir);
    343             return res;
    344         } else if (strcmp(argv[1], "-i") == 0) {
    345             printf("log directory: %s\n", log_dir);
    346         } else if (strcmp(argv[1], "-v") == 0) {
    347             printf("log: %s\n", VERSION);
    348         } else if (strcmp(argv[1], "-h") == 0) {
    349             usage(argv);
    350             printf("log is a minimalistic command line note manager written in C.\n");
    351             printf("Options:\n");
    352             printf("  -l\t\tLists the notes in the directory in a tree format.\n");
    353             printf("  -i\t\tPrints the directory where the notes are stored.\n");
    354             printf("  -v\t\tPrints the version of the program.\n");
    355             printf("  -h\t\tShow the help message.\n");
    356             printf("  -I [filename]\tCreates a new note with the name specified in the filename.\n");
    357             printf("  -Q [filename]\tSearches for the note with specified name and prints its path to the stdout.\n");
    358             printf("  -R [filename]\tRemoves the note specified in the filename.\n");
    359             printf("  -S [filename]\tPrints the content of the note specified in the filename.\n");
    360         } else {
    361             usage(argv);
    362             return 0;
    363         }
    364     } else if (argc == 3) {
    365         if (strcmp(argv[1], "-I") == 0) {
    366             if (argv[2] != NULL && argv[2][0] != '-') {
    367                 edit_note(argv[2]);
    368             } else {
    369                 printf("log: filename cannot be start with '-'\n");
    370                 usage(argv);
    371             }
    372         } else if (strcmp(argv[1], "-Q") == 0) {
    373             search_note(argv[2]);
    374         } else if (strcmp(argv[1], "-R") == 0) {
    375             remove_note(argv[2]);
    376         } else if (strcmp(argv[1], "-S") == 0) {
    377             print_note(argv[2]);
    378         } else {
    379             usage(argv);
    380             return 0;
    381         }
    382     } else {
    383         usage(argv);
    384         return 0;
    385     }
    386 }