diff --git a/.travis.yml b/.travis.yml index 15cf9c92..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" + - "pip install -q behave python-dateutil" # command to run tests script: - python --version 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/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..9ebaf538 --- /dev/null +++ b/features/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry @@ -0,0 +1,29 @@ + + + + + Creation Date + 2013-01-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 without Timezone. + Starred + + Tags + + UUID + 4BB1F46946AD439996C9B59DE7C4DDC1 + + diff --git a/features/dayone.feature b/features/dayone.feature new file mode 100644 index 00000000..c8e987e1 --- /dev/null +++ b/features/dayone.feature @@ -0,0 +1,48 @@ +Feature: DayOne Ingetration + + Scenario: Loading a DayOne Journal + Given we use the config "dayone.json" + When we run "jrnl -from 'feb 2013'" + 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! + """ + + 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." + 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 + """ 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.""" diff --git a/features/steps/core.py b/features/steps/core.py index f6c54564..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: @@ -66,15 +68,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 +97,16 @@ 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}" 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): diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 9a7767e8..61d6c618 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -315,13 +315,14 @@ 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) 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) 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'