From 1a31c10ca792d26de0043c00d5f14f2d58e76474 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 11:54:21 -0700 Subject: [PATCH 1/9] Fixes #87 --- jrnl/Journal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 9a7767e8..3d8eb479 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -315,7 +315,7 @@ class DayOne(Journal): dict_entry = plistlib.readPlist(plist_entry) try: timezone = pytz.timezone(dict_entry['Time Zone']) - except pytz.exceptions.UnknownTimeZoneError: + except (KeyError, pytz.exceptions.UnknownTimeZoneError): timezone = pytz.timezone(util.get_local_timezone()) date = dict_entry['Creation Date'] date = date + timezone.utcoffset(date) From 0fa981357eb6ec43e7c59087cc173a29c97c0ba0 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 11:59:51 -0700 Subject: [PATCH 2/9] Initial support for DayOne integration testing --- features/data/configs/dayone.json | 14 ++++++ .../044F3747A38546168B572C2E3F217FA2.doentry | 34 ++++++++++++++ .../0BDDD6CDA43C4A9AA2681517CC35AD9D.doentry | 46 +++++++++++++++++++ .../422BC895507944A291E6FC44FC6B8BFC.doentry | 31 +++++++++++++ .../4BB1F46946AD439996C9B59DE7C4DDC1.doentry | 29 ++++++++++++ features/dayone.feature | 17 +++++++ features/environment.py | 6 ++- 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 features/data/configs/dayone.json create mode 100644 features/data/journals/dayone.dayone/entries/044F3747A38546168B572C2E3F217FA2.doentry create mode 100644 features/data/journals/dayone.dayone/entries/0BDDD6CDA43C4A9AA2681517CC35AD9D.doentry create mode 100644 features/data/journals/dayone.dayone/entries/422BC895507944A291E6FC44FC6B8BFC.doentry create mode 100644 features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry create mode 100644 features/dayone.feature diff --git a/features/data/configs/dayone.json b/features/data/configs/dayone.json new file mode 100644 index 00000000..2baf0080 --- /dev/null +++ b/features/data/configs/dayone.json @@ -0,0 +1,14 @@ +{ + "default_hour": 9, + "timeformat": "%Y-%m-%d %H:%M", + "linewrap": 80, + "encrypt": false, + "editor": "", + "default_minute": 0, + "highlight": true, + "password": "", + "journals": { + "default": "features/journals/dayone.dayone" + }, + "tagsymbols": "@" +} diff --git a/features/data/journals/dayone.dayone/entries/044F3747A38546168B572C2E3F217FA2.doentry b/features/data/journals/dayone.dayone/entries/044F3747A38546168B572C2E3F217FA2.doentry new file mode 100644 index 00000000..2bb8e3c3 --- /dev/null +++ b/features/data/journals/dayone.dayone/entries/044F3747A38546168B572C2E3F217FA2.doentry @@ -0,0 +1,34 @@ + + + + + Creation Date + 2013-05-17T18:39:20Z + Creator + + Device Agent + Macintosh/MacBookAir5,2 + Generation Date + 2013-08-17T18:39:20Z + Host Name + Egeria + OS Agent + Mac OS X/10.8.4 + Software Agent + Day One (Mac)/1.8 + + Entry Text + This entry has tags! + Starred + + Tags + + work + play + + Time Zone + America/Los_Angeles + UUID + 044F3747A38546168B572C2E3F217FA2 + + diff --git a/features/data/journals/dayone.dayone/entries/0BDDD6CDA43C4A9AA2681517CC35AD9D.doentry b/features/data/journals/dayone.dayone/entries/0BDDD6CDA43C4A9AA2681517CC35AD9D.doentry new file mode 100644 index 00000000..927de884 --- /dev/null +++ b/features/data/journals/dayone.dayone/entries/0BDDD6CDA43C4A9AA2681517CC35AD9D.doentry @@ -0,0 +1,46 @@ + + + + + Creation Date + 2013-06-17T18:38:29Z + Creator + + Device Agent + Macintosh/MacBookAir5,2 + Generation Date + 2013-08-17T18:38:29Z + Host Name + Egeria + OS Agent + Mac OS X/10.8.4 + Software Agent + Day One (Mac)/1.8 + + Entry Text + This entry has a location. + Location + + Administrative Area + California + Country + Germany + Latitude + 52.4979764 + Locality + Berlin + Longitude + 13.2404758 + Place Name + Abandoned Spy Tower + + Starred + + Tags + + Time Zone + Europe/Berlin + UUID + 0BDDD6CDA43C4A9AA2681517CC35AD9D + + diff --git a/features/data/journals/dayone.dayone/entries/422BC895507944A291E6FC44FC6B8BFC.doentry b/features/data/journals/dayone.dayone/entries/422BC895507944A291E6FC44FC6B8BFC.doentry new file mode 100644 index 00000000..16260763 --- /dev/null +++ b/features/data/journals/dayone.dayone/entries/422BC895507944A291E6FC44FC6B8BFC.doentry @@ -0,0 +1,31 @@ + + + + + Creation Date + 2013-07-17T18:38:08Z + Creator + + Device Agent + Macintosh/MacBookAir5,2 + Generation Date + 2013-08-17T18:38:08Z + Host Name + Egeria + OS Agent + Mac OS X/10.8.4 + Software Agent + Day One (Mac)/1.8 + + Entry Text + This entry is starred! + Starred + + Tags + + Time Zone + America/Los_Angeles + UUID + 422BC895507944A291E6FC44FC6B8BFC + + diff --git a/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry b/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry new file mode 100644 index 00000000..977026d7 --- /dev/null +++ b/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry @@ -0,0 +1,29 @@ + + + + + Creation Date + 2013-08-17T18:37:50Z + Creator + + Device Agent + Macintosh/MacBookAir5,2 + Generation Date + 2013-08-17T18:37:50Z + Host Name + Egeria + OS Agent + Mac OS X/10.8.4 + Software Agent + Day One (Mac)/1.8 + + Entry Text + This is a DayOne entry. + Starred + + Tags + + UUID + 4BB1F46946AD439996C9B59DE7C4DDC1 + + diff --git a/features/dayone.feature b/features/dayone.feature new file mode 100644 index 00000000..898699e1 --- /dev/null +++ b/features/dayone.feature @@ -0,0 +1,17 @@ +Feature: DayOne Ingetration + + Scenario: Loading a DayOne Journal + Given we use the config "dayone.json" + When we run "jrnl -until now" + Then we should get no error + and the output should be + """ + 2013-05-17 11:39 This entry has tags! + + 2013-06-17 20:38 This entry has a location. + + 2013-07-17 11:38 This entry is starred! + + 2013-08-17 11:37 This is a DayOne entry. + """ + diff --git a/features/environment.py b/features/environment.py index 89125fca..c5b96721 100644 --- a/features/environment.py +++ b/features/environment.py @@ -18,7 +18,11 @@ def before_scenario(context, scenario): if not os.path.exists(working_dir): os.mkdir(working_dir) for filename in os.listdir(original): - shutil.copy2(os.path.join(original, filename), working_dir) + source = os.path.join(original, filename) + if os.path.isdir(source): + shutil.copytree(source, os.path.join(working_dir, filename)) + else: + shutil.copy2(source, working_dir) def after_scenario(context, scenario): """After each scenario, restore all test data and remove working_dirs.""" From 46da1f077e1029260b12fa593451379486fc1556 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 12:06:52 -0700 Subject: [PATCH 3/9] Support for DayOne tagging Closes #83 --- jrnl/Journal.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 3d8eb479..61d6c618 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -322,6 +322,7 @@ class DayOne(Journal): entry = self.new_entry(raw=dict_entry['Entry Text'], date=date, sort=False) entry.starred = dict_entry["Starred"] entry.uuid = dict_entry["UUID"] + entry.tags = dict_entry.get("Tags", []) # 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 @@ -343,6 +344,9 @@ class DayOne(Journal): 'Starred': entry.starred if hasattr(entry, 'starred') else False, 'Entry Text': entry.title+"\n"+entry.body, 'Time Zone': util.get_local_timezone(), - 'UUID': new_uuid + 'UUID': new_uuid, + 'Tags': [tag.strip(self.config['tagsymbols']) for tag in entry.tags] } + # print entry_plist + plistlib.writePlist(entry_plist, filename) From 66ea7e2b1e30dcd9a182bd23aa13164c992311f7 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 12:06:59 -0700 Subject: [PATCH 4/9] Tests for DayOne Tagging --- features/dayone.feature | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/features/dayone.feature b/features/dayone.feature index 898699e1..27f795f5 100644 --- a/features/dayone.feature +++ b/features/dayone.feature @@ -15,3 +15,30 @@ Feature: DayOne Ingetration 2013-08-17 11:37 This is a DayOne entry. """ + Scenario: Writing into Dayone + Given we use the config "dayone.json" + When we run "jrnl 01 may 1979: Being born hurts." + and we run "jrnl -until 1980" + Then the output should be + """ + 1979-05-01 09:00 Being born hurts. + """ + + Scenario: Loading tags from a DayOne Journal + Given we use the config "dayone.json" + When we run "jrnl --tags" + Then the output should be + """ + work : 1 + play : 1 + """ + + Scenario: Saving tags from a DayOne Journal + Given we use the config "dayone.json" + When we run "jrnl A hard day at @work" + and we run "jrnl --tags" + Then the output should be + """ + work : 2 + play : 1 + """ From c88f7fe69066e0155ba44648ed5186153bc27e7e Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 12:09:57 -0700 Subject: [PATCH 5/9] Version bump and doc fixes --- CHANGELOG.md | 12 ++++++++++-- README.md | 8 ++++---- jrnl/__init__.py | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cebd5e32..e5ec166f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,19 @@ Changelog ========= -### 1.5.2 +#### 1.5.4 + +* [New] DayOne journals can now handle tags + +#### 1.5.3 + +* [Fixed] DayOne integration with older DayOne Journals + +#### 1.5.2 * [Improved] Soft-deprecated `-to` for filtering by time and introduces `-until` instead. -### 1.5.1 +#### 1.5.1 * [Fixed] Fixed a bug introduced in 1.5.0 that caused the entire journal to be printed after composing an entry diff --git a/README.md b/README.md index e92ba029..52d65b52 100644 --- a/README.md +++ b/README.md @@ -204,11 +204,11 @@ The configuration file is a simple JSON file with the following options. ### 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 +Using your DayOne journal instead of a flat text file is dead simple - instead of pointing to a text file, change 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. +* `~/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: diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 4ee7ee31..01168bec 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line. """ __title__ = 'jrnl' -__version__ = '1.5.2' +__version__ = '1.5.4' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 Manuel Ebert' From 552d2a00ffa4e1131a1b49385c4a22a9a147363e Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 12:18:48 -0700 Subject: [PATCH 6/9] Some testing goodies --- features/steps/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/features/steps/core.py b/features/steps/core.py index f6c54564..1b476a2a 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -66,15 +66,15 @@ def no_error(context): @then('the output should be parsable as json') def check_output_json(context): out = context.stdout_capture.getvalue() - assert json.loads(out) + assert json.loads(out), out @then('"{field}" in the json output should have {number:d} elements') @then('"{field}" in the json output should have 1 element') def check_output_field(context, field, number=1): out = context.stdout_capture.getvalue() out_json = json.loads(out) - assert field in out_json - assert len(out_json[field]) == number + assert field in out_json [field, out_json] + assert len(out_json[field]) == number, len(out_json[field]) @then('"{field}" in the json output should not contain "{key}"') def check_output_field_not_key(context, field, key): @@ -95,7 +95,7 @@ def check_output(context): text = context.text.strip().splitlines() out = context.stdout_capture.getvalue().strip().splitlines() for line_text, line_out in zip(text, out): - assert line_text.strip() == line_out.strip() + assert line_text.strip() == line_out.strip(), [line_text.strip(), line_out.strip()] @then('the output should contain "{text}"') def check_output_inline(context, text): From 2a981d51e29e6f495a83d39d2b7d5d58d0ea12e5 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 13:19:54 -0700 Subject: [PATCH 7/9] Tests for parsing DayOne entries without Timezone --- .../4BB1F46946AD439996C9B59DE7C4DDC1.doentry | 4 ++-- features/dayone.feature | 10 +++++++--- features/steps/core.py | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry b/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry index 977026d7..9ebaf538 100644 --- a/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry +++ b/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry @@ -3,7 +3,7 @@ Creation Date - 2013-08-17T18:37:50Z + 2013-01-17T18:37:50Z Creator Device Agent @@ -18,7 +18,7 @@ Day One (Mac)/1.8 Entry Text - This is a DayOne entry. + This is a DayOne entry without Timezone. Starred Tags diff --git a/features/dayone.feature b/features/dayone.feature index 27f795f5..c8e987e1 100644 --- a/features/dayone.feature +++ b/features/dayone.feature @@ -2,7 +2,7 @@ Feature: DayOne Ingetration Scenario: Loading a DayOne Journal Given we use the config "dayone.json" - When we run "jrnl -until now" + When we run "jrnl -from 'feb 2013'" Then we should get no error and the output should be """ @@ -11,10 +11,14 @@ Feature: DayOne Ingetration 2013-06-17 20:38 This entry has a location. 2013-07-17 11:38 This entry is starred! - - 2013-08-17 11:37 This is a DayOne entry. """ + Scenario: Entries without timezone information will be intepreted in the current timezone + Given we use the config "dayone.json" + When we run "jrnl -until 'feb 2013'" + Then we should get no error + and the output should contain "2013-01-17T18:37Z" in the local time + Scenario: Writing into Dayone Given we use the config "dayone.json" When we run "jrnl 01 may 1979: Being born hurts." diff --git a/features/steps/core.py b/features/steps/core.py index 1b476a2a..66e6a07f 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -1,8 +1,10 @@ from behave import * -from jrnl import jrnl, Journal +from jrnl import jrnl, Journal, util +from dateutil import parser as date_parser import os import sys import json +import pytz try: from io import StringIO except ImportError: @@ -73,7 +75,7 @@ def check_output_json(context): def check_output_field(context, field, number=1): out = context.stdout_capture.getvalue() out_json = json.loads(out) - assert field in out_json [field, out_json] + assert field in out_json, [field, out_json] assert len(out_json[field]) == number, len(out_json[field]) @then('"{field}" in the json output should not contain "{key}"') @@ -97,6 +99,15 @@ def check_output(context): for line_text, line_out in zip(text, out): assert line_text.strip() == line_out.strip(), [line_text.strip(), line_out.strip()] +@then('the output should contain "{text}" in the local time') +def check_output_time_inline(context, text): + out = context.stdout_capture.getvalue() + local_tz = pytz.timezone(util.get_local_timezone()) + utc_time = date_parser.parse(text) + date = utc_time + local_tz._utcoffset + local_date = date.strftime("%Y-%m-%d %H:%M") + assert local_date in out, local_date + @then('the output should contain "{text}"') def check_output_inline(context, text): out = context.stdout_capture.getvalue() From cc978cbc0014d081bfb1b22dfe07725184fa98e4 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 13:24:51 -0700 Subject: [PATCH 8/9] Updates travis build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 15cf9c92..87e5f3b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: - "3.3" install: - "pip install -q -r requirements.txt --use-mirrors" - - "pip install -q behave" + - "pip install -q behave dateutil" # command to run tests script: - python --version From 788a8a8549a70adf08f5b90ab91ab0976dabc692 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 17 Aug 2013 18:14:06 -0700 Subject: [PATCH 9/9] dateutil -> python-dateutil --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 87e5f3b7..58acbc98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: - "3.3" install: - "pip install -q -r requirements.txt --use-mirrors" - - "pip install -q behave dateutil" + - "pip install -q behave python-dateutil" # command to run tests script: - python --version