From 6b742fd412b49665edd7d0be07124a5e9d9fa0f7 Mon Sep 17 00:00:00 2001 From: Thibaud Chupin Date: Sat, 23 Mar 2024 19:50:41 +0100 Subject: [PATCH 1/4] Allow custom file extensions for directory journal --- docs/journal-types.md | 4 +--- docs/reference-config-file.md | 9 +++++++++ jrnl/journals/FolderJournal.py | 19 ++++++++++++------- tests/bdd/features/file_storage.feature | 7 +++++++ .../configs/empty_folder_with_extension.yaml | 13 +++++++++++++ tests/unit/test_journals_folder_journal.py | 2 +- 6 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 tests/data/configs/empty_folder_with_extension.yaml diff --git a/docs/journal-types.md b/docs/journal-types.md index 8f5052153..3b3114560 100644 --- a/docs/journal-types.md +++ b/docs/journal-types.md @@ -22,9 +22,7 @@ use any extension. `jrnl` will automatically create the file when you save your first entry. ## Folder -The folder journal format organizes your entries into subfolders for the year -and month and `.txt` files for each day. If there are multiple entries in a day, -they all appear in the same `.txt` file. +The folder journal format organizes your entries into subfolders for the year and month, and a new file for each day. If there are multiple entries in a day, they all appear in the same file. The file extension (`.txt` by default) can be configured with the `extension` configuration key. The directory tree structure is in this format: `YYYY/MM/DD.txt`. For instance, if you have an entry on May 5th, 2021 in a folder journal at `~/folderjournal`, it will diff --git a/docs/reference-config-file.md b/docs/reference-config-file.md index aa64adb16..5aab5cd1f 100644 --- a/docs/reference-config-file.md +++ b/docs/reference-config-file.md @@ -57,6 +57,15 @@ for details. If `true`, encrypts your journal using AES. Do not change this value for journals that already have data in them. +### extension + +For [folder journals](journal-types.md#folder), control the extension of the day +files. By default it is `.txt`. + +!!! warning + Changing the extension after a folder journal has been created will prevent + `jrnl` from finding past entries. + ### template The path to a text file to use as a template for new entries. Only works when you have the `editor` field configured. If you use a template, the editor's diff --git a/jrnl/journals/FolderJournal.py b/jrnl/journals/FolderJournal.py index c6faf1e02..9cabbba97 100644 --- a/jrnl/journals/FolderJournal.py +++ b/jrnl/journals/FolderJournal.py @@ -17,7 +17,8 @@ DIGIT_PATTERN = "[0123456789]" YEAR_PATTERN = DIGIT_PATTERN * 4 MONTH_PATTERN = "[01]" + DIGIT_PATTERN -DAY_PATTERN = "[0123]" + DIGIT_PATTERN + ".txt" + +DEFAULT_EXTENSION = ".txt" class Folder(Journal): @@ -34,7 +35,8 @@ def open(self) -> "Folder": self.entries = [] if os.path.exists(self.config["journal"]): - filenames = Folder._get_files(self.config["journal"]) + file_extension = self.config.get("extension", DEFAULT_EXTENSION) + filenames = Folder._get_files(self, self.config["journal"], file_extension) for filename in filenames: with codecs.open(filename, "r", "utf-8") as f: journal = f.read() @@ -45,6 +47,8 @@ def open(self) -> "Folder": def write(self) -> None: """Writes only the entries that have been modified into proper files.""" + file_extension = self.config.get("extension", DEFAULT_EXTENSION) + # Create a list of dates of modified entries. Start with diff_entry_dates modified_dates = self._diff_entry_dates seen_dates = set(self._diff_entry_dates) @@ -63,7 +67,7 @@ def write(self) -> None: self.config["journal"], d.strftime("%Y"), d.strftime("%m"), - d.strftime("%d") + ".txt", + d.strftime("%d") + file_extension, ) dirname = os.path.dirname(filename) # create directory if it doesn't exist @@ -81,7 +85,7 @@ def write(self) -> None: journal_file.write(journal) # look for and delete empty files filenames = [] - filenames = Folder._get_files(self.config["journal"]) + filenames = Folder._get_files(self, self.config["journal"], file_extension) for filename in filenames: if os.stat(filename).st_size <= 0: os.remove(filename) @@ -121,12 +125,12 @@ def parse_editable_str(self, edited: str) -> None: self.entries = mod_entries @staticmethod - def _get_files(journal_path: str) -> list[str]: + def _get_files(self, journal_path: str, extension: str) -> list[str]: """Searches through sub directories starting with journal_path and find all text files that look like entries""" for year_folder in Folder._get_year_folders(pathlib.Path(journal_path)): for month_folder in Folder._get_month_folders(year_folder): - yield from Folder._get_day_files(month_folder) + yield from Folder._get_day_files(month_folder, extension) @staticmethod def _get_year_folders(path: pathlib.Path) -> list[pathlib.Path]: @@ -143,7 +147,8 @@ def _get_month_folders(path: pathlib.Path) -> list[pathlib.Path]: return @staticmethod - def _get_day_files(path: pathlib.Path) -> list[str]: + def _get_day_files(path: pathlib.Path, extension: str) -> list[str]: + DAY_PATTERN = "[0-3][0-9]" + extension for child in path.glob(DAY_PATTERN): if ( int(child.stem) > 0 diff --git a/tests/bdd/features/file_storage.feature b/tests/bdd/features/file_storage.feature index cecb21f65..68e5a4f51 100644 --- a/tests/bdd/features/file_storage.feature +++ b/tests/bdd/features/file_storage.feature @@ -10,6 +10,13 @@ Feature: Journals iteracting with the file system in a way that users can see And the journal directory should contain 2013/07/23.txt + Scenario: Adding entries to a Folder journal with a custom extension should generate date files + Given we use the config "empty_folder_with_extension.yaml" + When we run "jrnl 23 July 2013: Testing folder journal." + Then we should get no error + And the journal directory should contain + 2013/07/23.md + Scenario: Adding multiple entries to a Folder journal should generate multiple date files Given we use the config "empty_folder.yaml" When we run "jrnl 23 July 2013: Testing folder journal." diff --git a/tests/data/configs/empty_folder_with_extension.yaml b/tests/data/configs/empty_folder_with_extension.yaml new file mode 100644 index 000000000..2ba99ef3c --- /dev/null +++ b/tests/data/configs/empty_folder_with_extension.yaml @@ -0,0 +1,13 @@ +default_hour: 9 +default_minute: 0 +editor: '' +template: false +encrypt: false +extension: .md +highlight: true +journals: + default: features/journals/empty_folder_with_extension/ +linewrap: 80 +tagsymbols: '@' +timeformat: '%Y-%m-%d %H:%M' +indent_character: "|" diff --git a/tests/unit/test_journals_folder_journal.py b/tests/unit/test_journals_folder_journal.py index 09a3535f8..e6785df78 100644 --- a/tests/unit/test_journals_folder_journal.py +++ b/tests/unit/test_journals_folder_journal.py @@ -51,7 +51,7 @@ def test_get_day_files_expected_filtering(inputs_and_outputs): mock.patch("pathlib.Path.glob", return_value=glob_files), mock.patch.object(pathlib.Path, "is_file", return_value=True), ): - actual_output = list(Folder._get_day_files(year_month_path)) + actual_output = list(Folder._get_day_files(year_month_path, ".txt")) actual_output.sort() expected_output.sort() From 8d9c27ffd69ec2ac968d63e78216049db5a3965d Mon Sep 17 00:00:00 2001 From: Thibaud Chupin Date: Sat, 23 Mar 2024 20:43:07 +0100 Subject: [PATCH 2/4] Test for the creation of all the expected files The test previously only tested that one of the expected files was created. --- tests/bdd/features/file_storage.feature | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/bdd/features/file_storage.feature b/tests/bdd/features/file_storage.feature index 68e5a4f51..d56693a43 100644 --- a/tests/bdd/features/file_storage.feature +++ b/tests/bdd/features/file_storage.feature @@ -20,10 +20,11 @@ Feature: Journals iteracting with the file system in a way that users can see Scenario: Adding multiple entries to a Folder journal should generate multiple date files Given we use the config "empty_folder.yaml" When we run "jrnl 23 July 2013: Testing folder journal." - And we run "jrnl 3/7/2014: Second entry of journal." + And we run "jrnl 5/3/2014: Second entry of journal." Then we should get no error And the journal directory should contain 2013/07/23.txt + 2014/05/03.txt Scenario: If the journal and its parent directory don't exist, they should be created Given we use the config "missing_directory.yaml" From 8e5b5b6c1971721a1739701da9e4c6be065938c2 Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Mon, 3 Jun 2024 19:18:32 -0700 Subject: [PATCH 3/4] Skip Windows due to mysterious os.makedirs failure --- tests/bdd/features/file_storage.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bdd/features/file_storage.feature b/tests/bdd/features/file_storage.feature index d56693a43..9928558a1 100644 --- a/tests/bdd/features/file_storage.feature +++ b/tests/bdd/features/file_storage.feature @@ -10,6 +10,7 @@ Feature: Journals iteracting with the file system in a way that users can see And the journal directory should contain 2013/07/23.txt + @skip_win # https://github.com/jrnl-org/jrnl/issues/1894 Scenario: Adding entries to a Folder journal with a custom extension should generate date files Given we use the config "empty_folder_with_extension.yaml" When we run "jrnl 23 July 2013: Testing folder journal." From cbf4ed5c351d04b52fbc15ff43ca24b0a221fd7a Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 24 Aug 2024 13:58:33 -0700 Subject: [PATCH 4/4] Fix handling for complex extensions This also handles additional input for extensions in case the user isn't fully aware of how extensions work --- jrnl/journals/FolderJournal.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jrnl/journals/FolderJournal.py b/jrnl/journals/FolderJournal.py index 9cabbba97..00416e7db 100644 --- a/jrnl/journals/FolderJournal.py +++ b/jrnl/journals/FolderJournal.py @@ -17,6 +17,7 @@ DIGIT_PATTERN = "[0123456789]" YEAR_PATTERN = DIGIT_PATTERN * 4 MONTH_PATTERN = "[01]" + DIGIT_PATTERN +DAY_PATTERN = "[0-3][0-9]" DEFAULT_EXTENSION = ".txt" @@ -148,15 +149,15 @@ def _get_month_folders(path: pathlib.Path) -> list[pathlib.Path]: @staticmethod def _get_day_files(path: pathlib.Path, extension: str) -> list[str]: - DAY_PATTERN = "[0-3][0-9]" + extension - for child in path.glob(DAY_PATTERN): + for child in path.glob(DAY_PATTERN + extension): + day = str(child.name).replace(extension, "") if ( - int(child.stem) > 0 - and int(child.stem) <= 31 + int(day) > 0 + and int(day) <= 31 and time.is_valid_date( year=int(path.parent.name), month=int(path.name), - day=int(child.stem), + day=int(day), ) and child.is_file() ):