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'