Merge pull request #90 from maebert/#89-dayone

#89 dayone
This commit is contained in:
Manuel Ebert 2013-08-17 18:25:20 -07:00
commit d50e55480a
13 changed files with 245 additions and 16 deletions

View file

@ -5,7 +5,7 @@ python:
- "3.3" - "3.3"
install: install:
- "pip install -q -r requirements.txt --use-mirrors" - "pip install -q -r requirements.txt --use-mirrors"
- "pip install -q behave" - "pip install -q behave python-dateutil"
# command to run tests # command to run tests
script: script:
- python --version - python --version

View file

@ -1,11 +1,19 @@
Changelog 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. * [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 * [Fixed] Fixed a bug introduced in 1.5.0 that caused the entire journal to be printed after composing an entry

View file

@ -204,11 +204,11 @@ The configuration file is a simple JSON file with the following options.
### DayOne Integration ### 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 * `~/Library/Application Support/Day One/` by default
* `~/Dropbox/Apps/Day One/` if you're syncing with Dropbox and * `~/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/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: 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:

View file

@ -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": "@"
}

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Creation Date</key>
<date>2013-05-17T18:39:20Z</date>
<key>Creator</key>
<dict>
<key>Device Agent</key>
<string>Macintosh/MacBookAir5,2</string>
<key>Generation Date</key>
<date>2013-08-17T18:39:20Z</date>
<key>Host Name</key>
<string>Egeria</string>
<key>OS Agent</key>
<string>Mac OS X/10.8.4</string>
<key>Software Agent</key>
<string>Day One (Mac)/1.8</string>
</dict>
<key>Entry Text</key>
<string>This entry has tags!</string>
<key>Starred</key>
<false/>
<key>Tags</key>
<array>
<string>work</string>
<string>play</string>
</array>
<key>Time Zone</key>
<string>America/Los_Angeles</string>
<key>UUID</key>
<string>044F3747A38546168B572C2E3F217FA2</string>
</dict>
</plist>

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Creation Date</key>
<date>2013-06-17T18:38:29Z</date>
<key>Creator</key>
<dict>
<key>Device Agent</key>
<string>Macintosh/MacBookAir5,2</string>
<key>Generation Date</key>
<date>2013-08-17T18:38:29Z</date>
<key>Host Name</key>
<string>Egeria</string>
<key>OS Agent</key>
<string>Mac OS X/10.8.4</string>
<key>Software Agent</key>
<string>Day One (Mac)/1.8</string>
</dict>
<key>Entry Text</key>
<string>This entry has a location.</string>
<key>Location</key>
<dict>
<key>Administrative Area</key>
<string>California</string>
<key>Country</key>
<string>Germany</string>
<key>Latitude</key>
<real>52.4979764</real>
<key>Locality</key>
<string>Berlin</string>
<key>Longitude</key>
<real>13.2404758</real>
<key>Place Name</key>
<string>Abandoned Spy Tower</string>
</dict>
<key>Starred</key>
<false/>
<key>Tags</key>
<array/>
<key>Time Zone</key>
<string>Europe/Berlin</string>
<key>UUID</key>
<string>0BDDD6CDA43C4A9AA2681517CC35AD9D</string>
</dict>
</plist>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Creation Date</key>
<date>2013-07-17T18:38:08Z</date>
<key>Creator</key>
<dict>
<key>Device Agent</key>
<string>Macintosh/MacBookAir5,2</string>
<key>Generation Date</key>
<date>2013-08-17T18:38:08Z</date>
<key>Host Name</key>
<string>Egeria</string>
<key>OS Agent</key>
<string>Mac OS X/10.8.4</string>
<key>Software Agent</key>
<string>Day One (Mac)/1.8</string>
</dict>
<key>Entry Text</key>
<string>This entry is starred!</string>
<key>Starred</key>
<true/>
<key>Tags</key>
<array/>
<key>Time Zone</key>
<string>America/Los_Angeles</string>
<key>UUID</key>
<string>422BC895507944A291E6FC44FC6B8BFC</string>
</dict>
</plist>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Creation Date</key>
<date>2013-01-17T18:37:50Z</date>
<key>Creator</key>
<dict>
<key>Device Agent</key>
<string>Macintosh/MacBookAir5,2</string>
<key>Generation Date</key>
<date>2013-08-17T18:37:50Z</date>
<key>Host Name</key>
<string>Egeria</string>
<key>OS Agent</key>
<string>Mac OS X/10.8.4</string>
<key>Software Agent</key>
<string>Day One (Mac)/1.8</string>
</dict>
<key>Entry Text</key>
<string>This is a DayOne entry without Timezone.</string>
<key>Starred</key>
<false/>
<key>Tags</key>
<array/>
<key>UUID</key>
<string>4BB1F46946AD439996C9B59DE7C4DDC1</string>
</dict>
</plist>

48
features/dayone.feature Normal file
View file

@ -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
"""

View file

@ -18,7 +18,11 @@ def before_scenario(context, scenario):
if not os.path.exists(working_dir): if not os.path.exists(working_dir):
os.mkdir(working_dir) os.mkdir(working_dir)
for filename in os.listdir(original): 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): def after_scenario(context, scenario):
"""After each scenario, restore all test data and remove working_dirs.""" """After each scenario, restore all test data and remove working_dirs."""

View file

@ -1,8 +1,10 @@
from behave import * from behave import *
from jrnl import jrnl, Journal from jrnl import jrnl, Journal, util
from dateutil import parser as date_parser
import os import os
import sys import sys
import json import json
import pytz
try: try:
from io import StringIO from io import StringIO
except ImportError: except ImportError:
@ -66,15 +68,15 @@ def no_error(context):
@then('the output should be parsable as json') @then('the output should be parsable as json')
def check_output_json(context): def check_output_json(context):
out = context.stdout_capture.getvalue() 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 {number:d} elements')
@then('"{field}" in the json output should have 1 element') @then('"{field}" in the json output should have 1 element')
def check_output_field(context, field, number=1): def check_output_field(context, field, number=1):
out = context.stdout_capture.getvalue() out = context.stdout_capture.getvalue()
out_json = json.loads(out) out_json = json.loads(out)
assert field in out_json assert field in out_json, [field, out_json]
assert len(out_json[field]) == number assert len(out_json[field]) == number, len(out_json[field])
@then('"{field}" in the json output should not contain "{key}"') @then('"{field}" in the json output should not contain "{key}"')
def check_output_field_not_key(context, field, key): def check_output_field_not_key(context, field, key):
@ -95,7 +97,16 @@ def check_output(context):
text = context.text.strip().splitlines() text = context.text.strip().splitlines()
out = context.stdout_capture.getvalue().strip().splitlines() out = context.stdout_capture.getvalue().strip().splitlines()
for line_text, line_out in zip(text, out): 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}"') @then('the output should contain "{text}"')
def check_output_inline(context, text): def check_output_inline(context, text):

View file

@ -315,13 +315,14 @@ class DayOne(Journal):
dict_entry = plistlib.readPlist(plist_entry) dict_entry = plistlib.readPlist(plist_entry)
try: try:
timezone = pytz.timezone(dict_entry['Time Zone']) timezone = pytz.timezone(dict_entry['Time Zone'])
except pytz.exceptions.UnknownTimeZoneError: except (KeyError, pytz.exceptions.UnknownTimeZoneError):
timezone = pytz.timezone(util.get_local_timezone()) timezone = pytz.timezone(util.get_local_timezone())
date = dict_entry['Creation Date'] date = dict_entry['Creation Date']
date = date + timezone.utcoffset(date) date = date + timezone.utcoffset(date)
entry = self.new_entry(raw=dict_entry['Entry Text'], date=date, sort=False) entry = self.new_entry(raw=dict_entry['Entry Text'], date=date, sort=False)
entry.starred = dict_entry["Starred"] entry.starred = dict_entry["Starred"]
entry.uuid = dict_entry["UUID"] 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 # 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 # 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 # 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, 'Starred': entry.starred if hasattr(entry, 'starred') else False,
'Entry Text': entry.title+"\n"+entry.body, 'Entry Text': entry.title+"\n"+entry.body,
'Time Zone': util.get_local_timezone(), '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) plistlib.writePlist(entry_plist, filename)

View file

@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line.
""" """
__title__ = 'jrnl' __title__ = 'jrnl'
__version__ = '1.5.2' __version__ = '1.5.4'
__author__ = 'Manuel Ebert' __author__ = 'Manuel Ebert'
__license__ = 'MIT License' __license__ = 'MIT License'
__copyright__ = 'Copyright 2013 Manuel Ebert' __copyright__ = 'Copyright 2013 Manuel Ebert'