diff --git a/features/data/journals/empty_folder/empty.txt b/features/data/journals/empty_folder/empty similarity index 100% rename from features/data/journals/empty_folder/empty.txt rename to features/data/journals/empty_folder/empty diff --git a/features/dayone_regressions.feature b/features/dayone_regressions.feature index 62c8cc24..3e98f9e9 100644 --- a/features/dayone_regressions.feature +++ b/features/dayone_regressions.feature @@ -23,9 +23,3 @@ Feature: Zapped Dayone bugs stay dead! 2014-04-24 09:00 Ran 6.2 miles today in 1:02:03. | I'm feeling sore because I forgot to stretch. """ - - Scenario: Opening an folder that's not a DayOne folder gives a nice error message - Given we use the config "empty_folder.yaml" - When we run "jrnl Herro" - Then we should get an error - Then we should see the message "is a directory, but doesn't seem to be a DayOne journal either" diff --git a/features/folder.feature b/features/folder.feature new file mode 100644 index 00000000..650ac53e --- /dev/null +++ b/features/folder.feature @@ -0,0 +1,42 @@ +Feature: Testing a journal with a root directory and multiple files in the format of yyyy/mm/dd.txt + + Scenario: Opening an folder that's not a DayOne folder should treat as folder journal + Given we use the config "empty_folder.yaml" + When we run "jrnl 23 july 2013: Testing folder journal." + Then we should see the message "Entry added" + When we run "jrnl -1" + Then the output should be + """ + 2013-07-23 09:00 Testing folder journal. + """ + + Scenario: Adding entries to a Folder journal should generate date files + Given we use the config "empty_folder.yaml" + When we run "jrnl 23 July 2013: Testing folder journal." + Then we should see the message "Entry added" + When the journal directory is listed + Then the output should contain "2013/07/23.txt" or "2013\07\23.txt" + + + 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." + Then we should see the message "Entry added" + When the journal directory is listed + Then the output should contain "2013/07/23.txt" or "2013\07\23.txt" + Then the output should contain "2014/03/07.txt" or "2014\03\07.txt" + + Scenario: Out of order entries to a Folder journal should be listed in date order + Given we use the config "empty_folder.yaml" + When we run "jrnl 3/7/2014 4:37pm: Second entry of journal." + Then we should see the message "Entry added" + When we run "jrnl 23 July 2013: Testing folder journal." + Then we should see the message "Entry added" + When we run "jrnl -2" + Then the output should be + """ + 2013-07-23 09:00 Testing folder journal. + + 2014-03-07 16:37 Second entry of journal. + """ diff --git a/features/steps/core.py b/features/steps/core.py index 0a3841c4..82cae225 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -210,10 +210,11 @@ def check_output_time_inline(context, text): @then("the output should contain") @then('the output should contain "{text}"') -def check_output_inline(context, text=None): +@then('the output should contain "{text}" or "{text2}"') +def check_output_inline(context, text=None, text2=None): text = text or context.text out = context.stdout_capture.getvalue() - assert text in out, text + assert text in out or text2 in out, text or text2 @then('the output should not contain "{text}"') @@ -270,6 +271,17 @@ def check_journal_entries(context, number, journal_name="default"): assert len(journal.entries) == number +@when("the journal directory is listed") +def list_journal_directory(context, journal="default"): + files = [] + with open(install.CONFIG_FILE_PATH) as config_file: + config = yaml.load(config_file, Loader=yaml.FullLoader) + journal_path = config["journals"][journal] + for root, dirnames, f in os.walk(journal_path): + for file in f: + print(os.path.join(root, file)) + + @then("fail") def debug_fail(context): assert False diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index d6681a47..cc5af748 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -23,7 +23,7 @@ def make_key(password): length=32, # Salt is hard-coded salt=b"\xf2\xd5q\x0e\xc1\x8d.\xde\xdc\x8e6t\x89\x04\xce\xf8", - iterations=100000, + iterations=100_000, backend=default_backend(), ) key = kdf.derive(password) diff --git a/jrnl/FolderJournal.py b/jrnl/FolderJournal.py new file mode 100644 index 00000000..19519a14 --- /dev/null +++ b/jrnl/FolderJournal.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from __future__ import absolute_import, unicode_literals +from . import Entry +from . import Journal +import codecs +import os +import fnmatch + + +def get_files(journal_config): + """Searches through sub directories starting with journal_config and find all text files""" + filenames = [] + for root, dirnames, f in os.walk(journal_config): + for filename in fnmatch.filter(f, "*.txt"): + filenames.append(os.path.join(root, filename)) + return filenames + + +class Folder(Journal.Journal): + """A Journal handling multiple files in a folder""" + + def __init__(self, **kwargs): + self.entries = [] + self._diff_entry_dates = [] + super(Folder, self).__init__(**kwargs) + + def open(self): + filenames = [] + self.entries = [] + filenames = get_files(self.config["journal"]) + for filename in filenames: + with codecs.open(filename, "r", "utf-8") as f: + journal = f.read() + self.entries.extend(self._parse(journal)) + self.sort() + return self + + def write(self): + """Writes only the entries that have been modified into proper files.""" + # 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) + for e in self.entries: + if e.modified: + if e.date not in seen_dates: + modified_dates.append(e.date) + seen_dates.add(e.date) + + # For every date that had a modified entry, write to a file + for d in modified_dates: + write_entries = [] + filename = os.path.join( + self.config["journal"], + d.strftime("%Y"), + d.strftime("%m"), + d.strftime("%d") + ".txt", + ) + dirname = os.path.dirname(filename) + # create directory if it doesn't exist + if not os.path.exists(dirname): + os.makedirs(dirname) + for e in self.entries: + if ( + e.date.year == d.year + and e.date.month == d.month + and e.date.day == d.day + ): + write_entries.append(e) + journal = "\n".join([e.__str__() for e in write_entries]) + with codecs.open(filename, "w", "utf-8") as journal_file: + journal_file.write(journal) + # look for and delete empty files + filenames = [] + filenames = get_files(self.config["journal"]) + for filename in filenames: + if os.stat(filename).st_size <= 0: + # print("empty file: {}".format(filename)) + os.remove(filename) + + def parse_editable_str(self, edited): + """Parses the output of self.editable_str and updates it's entries.""" + mod_entries = self._parse(edited) + diff_entries = set(self.entries) - set(mod_entries) + for e in diff_entries: + self._diff_entry_dates.append(e.date) + # Match those entries that can be found in self.entries and set + # these to modified, so we can get a count of how many entries got + # modified and how many got deleted later. + for entry in mod_entries: + entry.modified = not any(entry == old_entry for old_entry in self.entries) + self.entries = mod_entries diff --git a/jrnl/Journal.py b/jrnl/Journal.py index e5bf4ecc..9d868807 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -378,12 +378,9 @@ def open_journal(name, config, legacy=False): return DayOneJournal.DayOne(**config).open() else: - print( - f"[Error: {config['journal']} is a directory, but doesn't seem to be a DayOne journal either.", - file=sys.stderr, - ) + from . import FolderJournal - sys.exit(1) + return FolderJournal.Folder(**config).open() if not config["encrypt"]: if legacy: