From 542781d62c1d05ed37199b9c8657357e5041772d Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Sat, 29 Apr 2023 15:05:22 -0700 Subject: [PATCH] Explicitly check for valid dates in FolderJournal and add unit test --- jrnl/journals/FolderJournal.py | 23 +++++---- jrnl/time.py | 11 ++++ tests/unit/test_journals_folder_journal.py | 60 ++++++++++++++++++++++ 3 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 tests/unit/test_journals_folder_journal.py diff --git a/jrnl/journals/FolderJournal.py b/jrnl/journals/FolderJournal.py index 5ee9d0b5..020b57e6 100644 --- a/jrnl/journals/FolderJournal.py +++ b/jrnl/journals/FolderJournal.py @@ -13,20 +13,16 @@ from .Journal import Journal if TYPE_CHECKING: from jrnl.journals import Entry -# Search patterns +# glob search patterns DIGIT_PATTERN = "[0123456789]" YEAR_PATTERN = DIGIT_PATTERN * 4 -MONTH_PATTERN = DIGIT_PATTERN * 2 -DAY_PATTERN = (DIGIT_PATTERN * 2) + ".txt" +MONTH_PATTERN = "[01]" + DIGIT_PATTERN +DAY_PATTERN = "[0123]" + DIGIT_PATTERN + ".txt" -# Entire range of possible folder/file names for months and days -MONTH_FOLDERS = [str(m).zfill(2) for m in range(1, 13)] -DAY_FILE_STEMS = [str(d).zfill(2) for d in range(1, 32)] class Folder(Journal): """A Journal handling multiple files in a folder""" - def __init__(self, name: str = "default", **kwargs): self.entries = [] self._diff_entry_dates = [] @@ -134,14 +130,14 @@ class Folder(Journal): @staticmethod def _get_year_folders(path: pathlib.Path) -> list[pathlib.Path]: for child in path.glob(YEAR_PATTERN): - if child.name.isdigit() and len(child.name) == 4 and child.is_dir(): + if child.is_dir(): yield child return @staticmethod def _get_month_folders(path: pathlib.Path) -> list[pathlib.Path]: for child in path.glob(MONTH_PATTERN): - if child.name in MONTH_FOLDERS and path.is_dir(): + if int(child.name) > 0 and int(child.name) <= 12 and path.is_dir(): yield child return @@ -149,8 +145,13 @@ class Folder(Journal): def _get_day_files(path: pathlib.Path) -> list[str]: for child in path.glob(DAY_PATTERN): if ( - child.stem in DAY_FILE_STEMS - and child.match("*.txt") + int(child.stem) > 0 + and int(child.stem) <= 31 + and time.is_valid_date( + year=int(path.parent.name), + month=int(path.name), + day=int(child.stem), + ) and child.is_file() ): yield str(child) diff --git a/jrnl/time.py b/jrnl/time.py index 514d94f2..15e3ec4c 100644 --- a/jrnl/time.py +++ b/jrnl/time.py @@ -89,3 +89,14 @@ def parse( if dt.days < -28 and not year_present: date = date.replace(date.year - 1) return date + + +def is_valid_date(year, month, day): + """ + Checks if a string can be parsed as a date + """ + try: + datetime.datetime(year, month, day) + return True + except ValueError: + return False diff --git a/tests/unit/test_journals_folder_journal.py b/tests/unit/test_journals_folder_journal.py new file mode 100644 index 00000000..66f6e819 --- /dev/null +++ b/tests/unit/test_journals_folder_journal.py @@ -0,0 +1,60 @@ +# Copyright © 2012-2023 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html + +import pathlib +from unittest import mock + +import pytest + +import jrnl +from jrnl.journals.FolderJournal import Folder + + +@pytest.mark.parametrize( + "inputs_and_outputs", + [ + [ + "/2020/01", + ["02.txt", "03.txt", "31.txt"], + ["/2020/01/02.txt", "/2020/01/03.txt", "/2020/01/31.txt"], + ], + [ + "/2020/02", # leap year + ["02.txt", "03.txt", "28.txt", "29.txt", "31.txt", "39.txt"], + [ + "/2020/02/02.txt", + "/2020/02/03.txt", + "/2020/02/28.txt", + "/2020/02/29.txt", + ], + ], + [ + "/2100/02", # not a leap year + ["01.txt", "28.txt", "29.txt", "39.txt"], + ["/2100/02/01.txt", "/2100/02/28.txt"], + ], + [ + "/2023/04", + ["29.txt", "30.txt", "31.txt", "39.txt"], + ["/2023/04/29.txt", "/2023/04/30.txt"], + ], + ], +) +def test_get_day_files_expected_filtering(inputs_and_outputs): + year_month_path, glob_filenames, expected_output = inputs_and_outputs + + year_month_path = pathlib.Path(year_month_path) + + glob_files = map(lambda x: year_month_path / x, glob_filenames) + expected_output = list(map(lambda x: str(pathlib.PurePath(x)), expected_output)) + + with ( + 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.sort() + + expected_output.sort() + + assert actual_output == expected_output