From ac266fd3b10d9be2205b9df7ea2c8b77952c99b1 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 9 Aug 2012 17:54:17 +0200 Subject: [PATCH 1/4] New style class, new_entry returns entry --- jrnl/Journal.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 31f45ec0..4f411c98 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -24,7 +24,7 @@ try: except ImportError: clint = None -class Journal: +class Journal(object): def __init__(self, **kwargs): self.config = { 'journal': "journal.txt", @@ -243,7 +243,7 @@ class Journal: return date - def new_entry(self, raw, date=None): + def new_entry(self, raw, date=None, sort=True): """Constructs a new entry from some raw text input. If a date is given, it will parse and use this, otherwise scan for a date in the input first.""" @@ -265,5 +265,9 @@ class Journal: if not date: # Still nothing? Meh, just live in the moment. date = self.parse_date("now") - self.entries.append(Entry(self, date, title, body)) - self.sort() + entry = Entry(self, date, title, body) + self.entries.append(entry) + if sort: + self.sort() + return entry + From 9476d3f115b6cce05d4e8e40b8607a62f66cf67e Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 9 Aug 2012 18:33:00 +0200 Subject: [PATCH 2/4] Experimental DayOne Support Just point your jrnl file to a folder ending with `.dayone` and magic. Supports both reading and creation (and hence all other export magic, although this is untested). --- jrnl/Journal.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ jrnl/jrnl.py | 13 ++++++++++--- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 4f411c98..9d4daae6 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -23,6 +23,8 @@ try: import clint except ImportError: clint = None +import plistlib +import uuid class Journal(object): def __init__(self, **kwargs): @@ -271,3 +273,47 @@ class Journal(object): self.sort() return entry +class DayOne(Journal): + """A special Journal handling DayOne files""" + def __init__(self, **kwargs): + self.entries = [] + super(DayOne, self).__init__(**kwargs) + + def open(self): + files = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))] + return files + + def parse(self, filenames): + """Instead of parsing a string into an entry, this method will take a list + of filenames, interpret each as a plist file and create a new entry from that.""" + self.entries = [] + for filename in filenames: + with open(filename) as plist_entry: + dict_entry = plistlib.readPlist(plist_entry) + entry = self.new_entry(raw=dict_entry['Entry Text'], date=dict_entry['Creation Date'], sort=False) + entry.starred = dict_entry["Starred"] + entry.uuid = dict_entry["UUID"] + # We're using new_entry to create the Entry object, which adds the entry + # to self.entries already. However, in the original Journal.__init__, this + # method is expected to return a list of newly created entries, which is why + # we're returning the obvious. + return self.entries + + + def write(self): + """Writes only the entries that have been modified into plist files.""" + for entry in self.entries: + # Assumption: since jrnl can not manipulate existing entries, all entries + # that have a uuid will be old ones, and only the one that doesn't will + # have a new one! + if not hasattr(entry, "uuid"): + new_uuid = uuid.uuid1().hex + filename = os.path.join(self.config['journal'], "entries", uuid+".doentry") + entry_plist = { + 'Creation Date': entry.date, + 'Starred': entry.starred if hasattr(entry, 'starred') else False, + 'Entry Text': entry.title+"\n"+entry.body, + 'UUID': new_uuid + } + plistlib.writePlist(entry_plist, filename) + diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index e3749683..e0f7ef5c 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -31,6 +31,7 @@ def parse_args(): parser = argparse.ArgumentParser() composing = parser.add_argument_group('Composing', 'Will make an entry out of whatever follows as arguments') composing.add_argument('-date', dest='date', help='Date, e.g. "yesterday at 5pm"') + composing.add_argument('-star', dest='star', help='Stars an entry (DayOne journals only)', action="store_true") composing.add_argument('text', metavar='text', nargs="*", help='Log entry (or tags by which to filter in viewing mode)') reading = parser.add_argument_group('Reading', 'Specifying either of these parameters will display posts of your journal') @@ -154,8 +155,13 @@ def cli(): touch_journal(config['journal']) mode_compose, mode_export = guess_mode(args, config) - # open journal file - journal = Journal.Journal(**config) + # open journal file or folder + if os.path.isdir(config['journal']) and config['journal'].endswith(".dayone"): + journal = Journal.DayOne(**config) + else: + journal = Journal.Journal(**config) + + if mode_compose and not args.text: if config['editor']: raw = get_text_from_editor(config) @@ -169,7 +175,8 @@ def cli(): # Writing mode if mode_compose: raw = " ".join(args.text).strip() - journal.new_entry(raw, args.date) + entry = journal.new_entry(raw, args.date) + entry.starred = args.star print("[Entry added to {} journal]").format(journal_name) journal.write() From fd0d35e151969d8273b370cbf9e0be881da18405 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 9 Aug 2012 18:42:31 +0200 Subject: [PATCH 3/4] Smarter UUID creation for DayOne journals --- jrnl/Journal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 9d4daae6..be53823a 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -308,7 +308,7 @@ class DayOne(Journal): # have a new one! if not hasattr(entry, "uuid"): new_uuid = uuid.uuid1().hex - filename = os.path.join(self.config['journal'], "entries", uuid+".doentry") + filename = os.path.join(self.config['journal'], "entries", new_uuid+".doentry") entry_plist = { 'Creation Date': entry.date, 'Starred': entry.starred if hasattr(entry, 'starred') else False, From 370e703d117a155197db11ddc56b964f786ffa33 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 9 Aug 2012 18:43:57 +0200 Subject: [PATCH 4/4] Documentation on DayOne integration --- CHANGELOG.md | 1 + README.md | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e10e3b0a..825b382d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog * [Fixed] A bug where jrnl would not go into compose mode * [Improved] Each journal can have individual settings +* [New] Integrates seamlessly with DayOne ### 0.3.2 (July 5, 2012) diff --git a/README.md b/README.md index 452d102b..24bbf601 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ jrnl ==== -*jrnl* is a simple journal application for your command line. Journals are stored as human readable plain text files - you can put them into a Dropbox folder for instant syncinc and you can be assured that your journal will still be readable in 2050, when all your fancy iPad journal applications will long be forgotten. +*jrnl* is a simple journal application for your command line. Journals are stored as human readable plain text files - you can put them into a Dropbox folder for instant syncing and you can be assured that your journal will still be readable in 2050, when all your fancy iPad journal applications will long be forgotten. + +*jrnl* also plays nice with the fabulous [DayOne](http://dayoneapp.com/) and can read and write directly from and to DayOne Journals. Optionally, your journal can be encrypted using the [256-bit AES](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard). @@ -154,6 +156,18 @@ The configuration file is a simple JSON file with the following options. > > Or use the built-in prompt or an external editor to compose your entries. +### DayOne Integration + +Using your DayOne journal instead of a flat text file is dead simple - instead of pointing to a text file, set the `"journal"` key in your `.jrnl_conf` to point to your DayOne journal. This is a folder ending with `.dayone`, and it's located at + + * `~/Library/Application Support/Day One/` by default + * `~/Dropbox/Apps/Day One/` if you're syncing with Dropbox and + * `~/Library/Mobile Documents/5U8NS4GX82~com~dayoneapp~dayone/Documents/` if you're syncing with iCloud. + +Instead of all entries being in a single file, each entry will live in a separate `plist` file. You can also star entries when you write them: + + jrnl -star yesterday: Lunch with @Arthur + ### Multiple journal files You can configure _jrnl_ to use with multiple journals (eg. `private` and `work`) by defining more journals in your `.jrnl_config`, for example: