diff --git a/README.md b/README.md index 367d9c2..c05991a 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,4 @@ Change `$/.config/notewrapper/config.json`. If it does not exist. On building, i - [ ] Fixe all the small stuff marked //(TODO LATER) in the code - [ ] Add a way to delete notes - [ ] Comply with GPL-3 notice (add info about no waranty, etc.) +- [ ] A converter for both type of journals diff --git a/config.json b/config.json index e542f77..2ead78a 100644 --- a/config.json +++ b/config.json @@ -3,5 +3,7 @@ "render": true, "jumpToEndOfFileOnLaunch": true, "editor": "neovim", - "journalRegex": ".*journal.*" + "journalRegex": ".*journal.*", + "dateEntry": "# %a %d %m %Y", + "newLineOnOpening": true } diff --git a/src/main.c b/src/main.c index 76a95c3..4ca0be9 100644 --- a/src/main.c +++ b/src/main.c @@ -83,25 +83,43 @@ int main(int argc, char *argv[]) { cJSON *shouldRenderJSON = cJSON_GetObjectItem(json, "render"); if (shouldRenderJSON && cJSON_IsBool(shouldRenderJSON)) { shouldRender = cJSON_IsTrue(shouldRenderJSON) ? 1 : 0; - } + } else {debug("config.json did not contained a correct \"render\" value. The default is 1.");} + int shouldJumpToEnd = 1; cJSON *shouldJumpToEndJSON = cJSON_GetObjectItem(json, "jumpToEndOfFileOnLaunch"); if (shouldJumpToEndJSON && cJSON_IsBool(shouldJumpToEndJSON)) { shouldJumpToEnd = cJSON_IsTrue(shouldJumpToEndJSON) ? 1 : 0; - } + } else {debug("config.json did not contained a correct \"jumpToEndOfFileOnLaunch\" value. The default is 1.");} + char *editorToOpen = "neovim"; // default cJSON *editorToOpenJSON = cJSON_GetObjectItem(json, "editor"); - if (editorToOpenJSON || cJSON_IsString(editorToOpenJSON)) { + if (editorToOpenJSON && cJSON_IsString(editorToOpenJSON)) { debug("Editor in config.json is %s", editorToOpenJSON->valuestring); error(!isStringInArray(editorToOpenJSON->valuestring, supportedEditor, numEditors), "user", "%s (fetched from config.json) is not a supported editor."); editorToOpen = strdup(editorToOpenJSON->valuestring); // we must strdup and not just = as we will free all the json after (before parsing args) - } + } else {debug("config.json did not contained a correct \"editor\" value. The default is neovim.");} + cJSON *journalRegexJSON = cJSON_GetObjectItem(json, "journalRegex"); char *journalRegex = ".*journal.*"; // default regex pattern for the journal - if (journalRegexJSON || cJSON_IsString(journalRegexJSON)) { + if (journalRegexJSON && cJSON_IsString(journalRegexJSON)) { debug("The regex in config.json is %s", journalRegexJSON->valuestring); journalRegex = strdup(journalRegexJSON->valuestring); - } + } else {debug("config.json did not contained a correct \"journalRegex\" value. The default is .*journal.*");} + + char *timeFormat = "# \%a \%d \%m \%Y";// default + cJSON *timeFormatJSON = cJSON_GetObjectItem(json, "dateEntry"); + if (timeFormatJSON && cJSON_IsString(timeFormatJSON)) { + debug("The time format in config.json is %s", timeFormatJSON->valuestring); + timeFormat = strdup(timeFormatJSON->valuestring); + } else {debug("config.json did not contained a correct \"dateEntry\" value. The default is \"# \%a \%d \%m \%Y\".");} + + int newLineOnOpening = 1; + cJSON *newLineOnOpeningJSON = cJSON_GetObjectItem(json, "newLineOnOpening"); + if (newLineOnOpeningJSON && cJSON_IsBool(newLineOnOpeningJSON)) { + debug("The value for newLineOnOpening in config.json is %d", cJSON_IsTrue(newLineOnOpeningJSON)); + newLineOnOpening = cJSON_IsTrue(newLineOnOpeningJSON) ? 1 : 0; + } else {debug("config.json did not contained a correct \"newLineOnOpening\" value. The default is true.");} + //cleans up cJSON_Delete(json); free(data); @@ -258,13 +276,30 @@ note_selection: if (strcmp(noteSelected, "Create new note") != 0 && strcmp(noteSelected,"Back to vault selection") != 0 && strcmp(noteSelected, "Delete vault") != 0 && strcmp(noteSelected,"Quit (Ctrl+C)") != 0) { open_note: bypassNoteSelection = HASH_MACRO; // we must reset bypassNoteSelection to avoid getting into an infinite loop of bypassing the note selection - char fullPath[PATH_MAX]; // (TODO LATER) Find a more appropriate and descriptive name for the variable + char *fullPath = malloc(PATH_MAX); // (TODO LATER) Find a more appropriate and descriptive name for the variable sprintf(fullPath, "%s/%s/%s", notesDirectoryString, vaultSelected, noteSelected); // (TODO LATER) change all sprintf to snprintf which checks for buffer size + // if it is a journal we must update it before + regex_t regex; + int regexReturn = regcomp(®ex, journalRegex, 0); + error(regexReturn, "program", "Regex compilation failed."); + regexReturn = regexec(®ex, noteSelected, 0, NULL, 0); + if (!regexReturn) { // if the regex matches -> it's a journal + debug("%s is a journal. Updating it...", noteSelected); + fullPath = updateJournal(fullPath, noteSelected, timeFormat, shouldDebug); // we return the path. As if it is a divided journal we must point to the correct entry + } + if (newLineOnOpening) { //(TODO LATER) For some reason this does not applies to journals? + appendToFile(fullPath, "\n", shouldDebug); + } openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug); + free(fullPath); } else if (strcmp(noteSelected,"Create new note") == 0) { note_creation: char *pathForNoteCreation = createNewNote(notesDirectoryString, vaultSelected, bypassNoteSelection, shouldDebug); + // (TODO LATER) Handle journal creation bypassNoteSelection = HASH_MACRO; // we must reset bypassNoteSelection to avoid getting into an infinite loop of bypassing the note selection + if (newLineOnOpening) { + appendToFile(pathForNoteCreation, "\n", shouldDebug); + } openEditor(pathForNoteCreation, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug); //free(pathForNoteCreation); } else if (strcmp(noteSelected,"Back to vault selection") == 0) { diff --git a/src/notes.c b/src/notes.c index 92a953a..0198254 100644 --- a/src/notes.c +++ b/src/notes.c @@ -1,6 +1,96 @@ #include "notes.h" -char** getVaultsFromDirectory(char *dirString, size_t *count, int shouldDebug) { +char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) { + debug("Searching %s for journals", vault); + // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ + struct dirent *vaultEntry; + char tempPath[PATH_MAX]; + snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry + DIR *vaultDirectory = opendir(tempPath); + error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath); + char **journalsArray = NULL; // will contain all the notes + size_t journalsCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc + + // https://stackoverflow.com/a/1085120 for regex code + regex_t regex; + int regexReturn; + // compiles the regex + regexReturn = regcomp(®ex, journalRegex, 0); + error(regexReturn, "program", "Regex could not compile. Perhaps there is an error with the regex string"); + debug("Regex compiled succesfully"); + // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html + // for readdir() + debug("┌------------------------------------------\nDetected files and dirs from the vault"); + while ((vaultEntry = readdir(vaultDirectory)) != NULL) { // we iterate over every entry from the dir. So files and dirs (. and .. included) + altDebug("%s ", vaultEntry->d_name); + // (TODO LATER) check if it is a file or a dir + if (vaultEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files) + regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0); + if (!regexReturn) { // if the regex matches + altDebug("matched with the regex. It is a journal.\n"); + journalsArray = realloc(journalsArray, (journalsCount + 1)*sizeof(char*)); // resize notesArray so that + journalsArray[journalsCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray + journalsCount++; + } else { + altDebug("did not matched with the regex. It is a note.\n"); + } + } else { + altDebug(" was ignored\n"); + } + } + altDebug("└ ------------------------------\n"); + // free's some used memory + closedir(vaultDirectory); + *count = journalsCount; // passes the number of files + return journalsArray; +} + +char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) { + // this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones) + // (TODO LATER) Check how it handles non .md files + // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ + debug("Searching %s for notes", vault); + struct dirent *vaultEntry; // (TODO LATER) change name of these variables. notesDirectory is dumb as it is the directory of vaults + char tempPath[PATH_MAX]; + snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry + DIR *vaultDirectory = opendir(tempPath); + error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath); + char **notesArray = NULL; // will contain all the notes + size_t notesCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc + // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html + // for readdir() + debug("┌------------------------------\nDetected Files and dirs from the vault:"); + while ((vaultEntry = readdir(vaultDirectory)) != NULL) { + + altDebug("%s ", vaultEntry->d_name); + // for the regex code https://stackoverflow.com/a/1085120 + regex_t regex; + int regexReturn; + // compiles the regex + regexReturn = regcomp(®ex, journalRegex, 0); + if (vaultEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files) + char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght + snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s/%s", pathToVault, vault, vaultEntry->d_name); // sets the full absolute path to fullPathEntry + // check if it matches the regex. If it does not match regexReturn != 0. + regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0); + struct stat metadataPathEntry; + if (stat(fullPathEntry, &metadataPathEntry) == 0 && regexReturn && S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a file + notesArray = realloc(notesArray, (notesCount + 1)*sizeof(char*)); // resize notesArray so that + notesArray[notesCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray + notesCount++; + altDebug("did not match with the regex. It is a note.\n"); + } else if (!regexReturn) {altDebug("matched with the regex. It is a journal.\n");} + } else {altDebug("was ignored\n");} + } + altDebug("└ ------------------------------\n"); + // (TODO LATER) Alphabetically sort them + // free's some used memory + closedir(vaultDirectory); + *count = notesCount; // passes the number of files + return notesArray; +} + +char **getVaultsFromDirectory(char *dirString, size_t *count, int shouldDebug) { // (TODO LATER) it might be a good idea to check if these directories exist // (TODO LATER) expand ~ as it does not work with opendir() // this function is inputed a path to a directory (which comes usually from the config file) and outpus all the suitable directories (so not the hidden ones) which will serve as separate vaults for notes @@ -37,93 +127,27 @@ char** getVaultsFromDirectory(char *dirString, size_t *count, int shouldDebug) { return dirsArray; } +char *updateJournal(char *path, char *journal, char *timeFormat, int shouldDebug) { + //(TODO LATER) Per journal format configuration + // (TODO LATER) with all the usage of PATH_MAX we should really calculate max inputed path or something like this + path[PATH_MAX] = '\0'; // it assures it is a string (Most cases this does nothing). But rewriting at least one bytes make the compile happy. He doesn't want to return an unchanged input. + debug("Handling the journal %s", path); -char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) { - // this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones) - // (TODO LATER) Check how it handles non .md files - // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ - debug("Searching %s for notes", vault); - struct dirent *vaultEntry; // (TODO LATER) change name of these variables. notesDirectory is dumb as it is the directory of vaults - char tempPath[PATH_MAX]; - snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry - DIR *vaultDirectory = opendir(tempPath); - error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath); - char **notesArray = NULL; // will contain all the notes - size_t notesCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc - // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html - // for readdir() - debug("┌------------------------------\nDetected Files and dirs from the vault:"); - while ((vaultEntry = readdir(vaultDirectory)) != NULL) { + char *date = getFormatedTime(timeFormat, shouldDebug); + debug("Formated time for the journal's entry is %s", date); - altDebug("%s ", vaultEntry->d_name); - // for the regex code https://stackoverflow.com/a/1085120 - regex_t regex; - int regexReturn; - // compiles the regex - regexReturn = regcomp(®ex, journalRegex, 0); - if (vaultEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files) - char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght - snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s/%s", pathToVault, vault, vaultEntry->d_name); // sets the full absolute path to fullPathEntry - // check if it matches the regex. If it does not match regexReturn != 0. - regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0); - struct stat metadataPathEntry; - if (stat(fullPathEntry, &metadataPathEntry) == 0 && regexReturn && !S_ISDIR(metadataPathEntry.st_mode)) { // if this entry is a file - notesArray = realloc(notesArray, (notesCount + 1)*sizeof(char*)); // resize notesArray so that - notesArray[notesCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray - notesCount++; - altDebug("did not match with the regex. It is a note.\n"); - } else if (!regexReturn) {altDebug("matched with the regex. It is a journal.\n");} - } else {altDebug("was ignored\n");} - } - altDebug("└ ------------------------------\n"); - // (TODO LATER) Alphabetically sort them - // free's some used memory - closedir(vaultDirectory); - *count = notesCount; // passes the number of files - return notesArray; -} - - -char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) { - debug("Searching %s for journals", vault); - // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ - struct dirent *vaultEntry; - char tempPath[PATH_MAX]; - snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry - DIR *vaultDirectory = opendir(tempPath); - error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath); - char **journalsArray = NULL; // will contain all the notes - size_t journalsCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc - - // https://stackoverflow.com/a/1085120 for regex code - regex_t regex; - int regexReturn; - // compiles the regex - regexReturn = regcomp(®ex, journalRegex, 0); - error(regexReturn, "program", "Regex could not compile. Perhaps there is an error with the regex string"); - debug("Regex compiled succesfully"); - // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html - // for readdir() - debug("┌------------------------------------------\nDetected files and dirs from the vault"); - while ((vaultEntry = readdir(vaultDirectory)) != NULL) { // we iterate over every entry from the dir. So files and dirs (. and .. included) - altDebug("%s ", vaultEntry->d_name); - if (vaultEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files) - regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0); - if (!regexReturn) { // if the regex matches - altDebug("matched with the regex. It is a journal.\n"); - journalsArray = realloc(journalsArray, (journalsCount + 1)*sizeof(char*)); // resize notesArray so that - journalsArray[journalsCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray - journalsCount++; - } else { - altDebug("did not matched with the regex. It is a note.\n"); - } - } else { - altDebug(" was ignored\n"); - } - } - altDebug("└ ------------------------------\n"); - // free's some used memory - closedir(vaultDirectory); - *count = journalsCount; // passes the number of files - return journalsArray; + struct stat metadata; + error(stat(path, &metadata), "program", "stat() failed to get information about %s", path); + if (S_ISREG(metadata.st_mode)) { + debug("%s is a unified journal.", path); + if (!isStringInFile(path, date, shouldDebug)) { // if there is no entry for current date + appendToFile(path, date, shouldDebug); + } // if there is an entry do nothing + } else if (S_ISDIR(metadata.st_mode)) { + debug("%s is a divided journal.", path); + + } else { + error(1, "program", "%s is not a file and not a directory.", path); + } + return path; } diff --git a/src/notes.h b/src/notes.h index 6d286d1..86ffea4 100644 --- a/src/notes.h +++ b/src/notes.h @@ -2,13 +2,21 @@ #define NOTES_H #include "utils.h" #include "ui.h" -char **getVaultsFromDirectory(char *dirString, size_t *count, int debug); -// this function is inputed a path to a directory (which comes usually from the config file) and outpus all the suitable directories (so not the hidden ones) which will serve as separate vaults for notes -char **getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int debug); -// this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones) -// journalRegex is the regex code for the journals. If a note matches this code, it is treated as a journal and it is not outputed from this function. -char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int debug); +char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug); // gets the journals from the vault // to distinguish journals from note we use regex. // Note: we must handle them separatly as journals can either be a single file (which will treat specially) or a directory + +char **getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug); +// this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones) +// journalRegex is the regex code for the journals. If a note matches this code, it is treated as a journal and it is not outputed from this function. +char **getVaultsFromDirectory(char *dirString, size_t *count, int shouldDebug); +// this function is inputed a path to a directory (which comes usually from the config file) and outpus all the suitable directories (so not the hidden ones) which will serve as separate vaults for notes +char *updateJournal(char *path, char *journal, char *timeFormat, int shouldDebug); +// path is the path to the file +// journal is the name of the file +// handles both type of journal (divided and unified) +// creates new entry with date +// for divided select if we want to acces to a new entry or a old one +// returns the path to the file that needs to be opened #endif diff --git a/src/utils.c b/src/utils.c index a2c9e5b..7b60f9b 100644 --- a/src/utils.c +++ b/src/utils.c @@ -81,6 +81,16 @@ int doesEditorExist (char *editorToCheck, int shouldDebug) { // Some exectua return 0; // program not found } +char *getFormatedTime(char *format, int shouldDebug) { + debug("Inputed time format is %s", format); + time_t t = time(NULL); + struct tm *tm = localtime(&t); + char *buf = malloc(BUFFER_SIZE); + strftime(buf, BUFFER_SIZE, format, tm); + debug("Formated time is %s", buf); + return buf; +} + int isStringInArray(const char *string, const char **array, const int len) { for (int i = 0; i < len; i++) { if (strcmp(string, array[i]) == 0) { @@ -90,6 +100,30 @@ int isStringInArray(const char *string, const char **array, const int len) { return 0; } +int isStringInFile(const char *path, const char *string, const int shouldDebug) { + FILE *file = fopen(path, "r"); + error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, path); + char buf[BUFFER_SIZE]; + + while (fgets(buf, BUFFER_SIZE, file) != NULL) { + if (strstr(buf, string) != NULL) { + debug("%s contains the substring %s", buf, string); + return 1; + } + } + debug("The string %s is not inside %s", string, path); + return 0; +} + +void appendToFile(const char *path, const char *string, const int shouldDebug) { + // (TODO LATER) we should check if it is not already at the end and not append. (Usefull for \n) + FILE *file = fopen(path, "a"); + error(file == NULL, "program", "could not open %s", path); + fprintf(file, "%s", string); + fclose(file); + debug("%s was appended to %s succesfully", string, path); +} + void sanitize(char *string) { for (int i = 0; i < strlen(string); i++) { if ((!isalnum((unsigned char)string[i]) && strchr("/\\:*?\"\'<>\n\r\t", string[i])) || ((i == 0 || i == 1) && string[i] == '.')) { // replace unwanted chars by '_'. '.' is replaced if it is only the first two chars diff --git a/src/utils.h b/src/utils.h index 9fcbbf8..a4d1a2e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -16,6 +16,8 @@ #include #include #include +#include +#define BUFFER_SIZE 256 //standard buffer size. #define debug(message, ...) \ _debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__) #define altDebug(message, ...) \ @@ -27,6 +29,7 @@ extern const char *supportedEditor[]; // array of supported editors extern const int numEditors; // number of supported editors +//(TODO LATER) organise this mess int compareString(const void *a, const void *b); //compares two strings alphabetically. //this function is used for qsort @@ -44,6 +47,10 @@ int isStringInArray(const char *string, const char **array, const int len); // Returns 1 if the string is in the array // Returns 0 if the string is not in the array // If you want to only check the first n elements of the array, pass n as len +int isStringInFile(const char *path, const char *string, const int shouldDebug); +// returns 1 if the string is in the file +// returns 0 if the string is not in the file +void appendToFile(const char *path, const char *string, const int shouldDebug); void sanitize(char *string); // replace unwanted chars by '_'. '.' is replaced if it is only the first two chars int rmrf(char *path); @@ -54,4 +61,8 @@ int openEditor(char *path, char *editor, int render, int endOfFile, int debug); // render: if we render the .md file with Vivify // endOfFile: if we put the cursor at the end of the file when opening // The program resumes when the editor is closed +char *getFormatedTime(char *format, int shouldDebug); +// see https://pubs.opengroup.org/onlinepubs/7908799/xsh/strftime.html? for formats +// returns a string (buffered at 256 chars) +// you should not forgot to free this string as it is in the heap #endif