summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManuel Ebert <manuel@1450.me>2014-05-22 13:49:31 -0700
committerManuel Ebert <manuel@1450.me>2014-05-22 13:49:31 -0700
commit9618b74f8db84e7847bf15fa46838f100663db0a (patch)
tree37360fa16506f3e616aabdea46878e5b41da4db4
parent0961aa7610164c5c16cf64dab8102b6d3510a659 (diff)
parent2948f6dc69b34f27f5ea84583466ec2466012069 (diff)
Merge pull request #164 from maebert/1.8v1.8.0
Official support for python 3.4
-rw-r--r--.travis.yml2
-rw-r--r--CHANGELOG.md4
-rw-r--r--features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D511.doentry4
-rw-r--r--features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D512.doentry2
-rw-r--r--jrnl/DayOneJournal.py137
-rw-r--r--jrnl/Journal.py125
-rw-r--r--jrnl/__init__.py2
-rw-r--r--jrnl/cli.py27
-rw-r--r--setup.py1
9 files changed, 164 insertions, 140 deletions
diff --git a/.travis.yml b/.travis.yml
index 278906eb..35f34a63 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ python:
- "2.6"
- "2.7"
- "3.3"
-# - "3.4" # Not available on Travis yet, see https://github.com/travis-ci/travis-ci/issues/1989
+ - "3.4"
install:
- "pip install -e . --use-mirrors"
- "pip install pycrypto>=2.6 --use-mirrors"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2eb1a0be..cb89a41a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@ Changelog
=========
+### 1.8 (May 22, 2014)
+
+* __1.8.0__ Official support for python 3.4
+
### 1.7 (December 22, 2013)
* __1.7.22__ Fixed an issue with writing files when exporting entries containing non-ascii characters.
diff --git a/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D511.doentry b/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D511.doentry
index 745a08df..066821bb 100644
--- a/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D511.doentry
+++ b/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D511.doentry
@@ -1,4 +1,4 @@
-<?xxml version="1.0" encoding="UTF-8"?>
+<?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">
@@ -23,7 +23,7 @@
<key>Location</key>
<dict>
<key>Administrative Area</key>
- <string>Östergötlands län</string>
+ <string>Östergötlands län</string>
<key>Country</key>
<string>Sverige</string>
<key>Latitude</key>
diff --git a/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D512.doentry b/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D512.doentry
index f005a1ff..ea3efec5 100644
--- a/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D512.doentry
+++ b/features/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D512.doentry
@@ -19,7 +19,7 @@
<key>Location</key>
<dict>
<key>Administrative Area</key>
- <string>Östergötlands län</string>
+ <string>Östergötlands län</string>
<key>Country</key>
<string>Sverige</string>
<key>Latitude</key>
diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py
new file mode 100644
index 00000000..d5649eb3
--- /dev/null
+++ b/jrnl/DayOneJournal.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from __future__ import absolute_import
+from . import Entry
+from . import Journal
+import os
+import re
+from datetime import datetime
+import time
+import plistlib
+import pytz
+import uuid
+import tzlocal
+from xml.parsers.expat import ExpatError
+
+
+class DayOne(Journal.Journal):
+ """A special Journal handling DayOne files"""
+
+ # InvalidFileException was added to plistlib in Python3.4
+ PLIST_EXCEPTIONS = (ExpatError, plistlib.InvalidFileException) if hasattr(plistlib, "InvalidFileException") else ExpatError
+
+ def __init__(self, **kwargs):
+ self.entries = []
+ self._deleted_entries = []
+ super(DayOne, self).__init__(**kwargs)
+
+ def open(self):
+ filenames = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))]
+ self.entries = []
+ for filename in filenames:
+ with open(filename, 'rb') as plist_entry:
+ try:
+ dict_entry = plistlib.readPlist(plist_entry)
+ except self.PLIST_EXCEPTIONS:
+ pass
+ else:
+ try:
+ timezone = pytz.timezone(dict_entry['Time Zone'])
+ except (KeyError, pytz.exceptions.UnknownTimeZoneError):
+ timezone = tzlocal.get_localzone()
+ date = dict_entry['Creation Date']
+ date = date + timezone.utcoffset(date, is_dst=False)
+ raw = dict_entry['Entry Text']
+ sep = re.search("\n|[\?!.]+ +\n?", raw)
+ title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
+ entry = Entry.Entry(self, date, title, body, starred=dict_entry["Starred"])
+ entry.uuid = dict_entry["UUID"]
+ entry.tags = [self.config['tagsymbols'][0] + tag for tag in dict_entry.get("Tags", [])]
+ self.entries.append(entry)
+ self.sort()
+
+ def write(self):
+ """Writes only the entries that have been modified into plist files."""
+ for entry in self.entries:
+ if entry.modified:
+ if not hasattr(entry, "uuid"):
+ entry.uuid = uuid.uuid1().hex
+ utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple()))
+ filename = os.path.join(self.config['journal'], "entries", entry.uuid + ".doentry")
+ entry_plist = {
+ 'Creation Date': utc_time,
+ 'Starred': entry.starred if hasattr(entry, 'starred') else False,
+ 'Entry Text': entry.title + "\n" + entry.body,
+ 'Time Zone': str(tzlocal.get_localzone()),
+ 'UUID': entry.uuid,
+ 'Tags': [tag.strip(self.config['tagsymbols']) for tag in entry.tags]
+ }
+ plistlib.writePlist(entry_plist, filename)
+ for entry in self._deleted_entries:
+ filename = os.path.join(self.config['journal'], "entries", entry.uuid + ".doentry")
+ os.remove(filename)
+
+ def editable_str(self):
+ """Turns the journal into a string of entries that can be edited
+ manually and later be parsed with eslf.parse_editable_str."""
+ return u"\n".join([u"# {0}\n{1}".format(e.uuid, e.__unicode__()) for e in self.entries])
+
+ def parse_editable_str(self, edited):
+ """Parses the output of self.editable_str and updates it's entries."""
+ # Method: create a new list of entries from the edited text, then match
+ # UUIDs of the new entries against self.entries, updating the entries
+ # if the edited entries differ, and deleting entries from self.entries
+ # if they don't show up in the edited entries anymore.
+ date_length = len(datetime.today().strftime(self.config['timeformat']))
+
+ # Initialise our current entry
+ entries = []
+ current_entry = None
+
+ for line in edited.splitlines():
+ # try to parse line as UUID => new entry begins
+ line = line.rstrip()
+ m = re.match("# *([a-f0-9]+) *$", line.lower())
+ if m:
+ if current_entry:
+ entries.append(current_entry)
+ current_entry = Entry.Entry(self)
+ current_entry.modified = False
+ current_entry.uuid = m.group(1).lower()
+ else:
+ try:
+ new_date = datetime.strptime(line[:date_length], self.config['timeformat'])
+ if line.endswith("*"):
+ current_entry.starred = True
+ line = line[:-1]
+ current_entry.title = line[date_length + 1:]
+ current_entry.date = new_date
+ except ValueError:
+ if current_entry:
+ current_entry.body += line + "\n"
+
+ # Append last entry
+ if current_entry:
+ entries.append(current_entry)
+
+ # Now, update our current entries if they changed
+ for entry in entries:
+ entry.parse_tags()
+ matched_entries = [e for e in self.entries if e.uuid.lower() == entry.uuid]
+ if matched_entries:
+ # This entry is an existing entry
+ match = matched_entries[0]
+ if match != entry:
+ self.entries.remove(match)
+ entry.modified = True
+ self.entries.append(entry)
+ else:
+ # This entry seems to be new... save it.
+ entry.modified = True
+ self.entries.append(entry)
+ # Remove deleted entries
+ edited_uuids = [e.uuid for e in entries]
+ self._deleted_entries = [e for e in self.entries if e.uuid not in edited_uuids]
+ self.entries[:] = [e for e in self.entries if e.uuid in edited_uuids]
+ return entries
diff --git a/jrnl/Journal.py b/jrnl/Journal.py
index 74afecf7..35bdc395 100644
--- a/jrnl/Journal.py
+++ b/jrnl/Journal.py
@@ -5,13 +5,11 @@ from __future__ import absolute_import
from . import Entry
from . import util
import codecs
-import os
try: import parsedatetime.parsedatetime_consts as pdt
except ImportError: import parsedatetime as pdt
import re
from datetime import datetime
import dateutil
-import time
import sys
try:
from Crypto.Cipher import AES
@@ -20,11 +18,6 @@ try:
except ImportError:
crypto_installed = False
import hashlib
-import plistlib
-import pytz
-import uuid
-import tzlocal
-from xml.parsers.expat import ExpatError
class Journal(object):
@@ -328,121 +321,3 @@ class Journal(object):
for entry in mod_entries:
entry.modified = not any(entry == old_entry for old_entry in self.entries)
self.entries = mod_entries
-
-
-class DayOne(Journal):
- """A special Journal handling DayOne files"""
- def __init__(self, **kwargs):
- self.entries = []
- self._deleted_entries = []
- super(DayOne, self).__init__(**kwargs)
-
- def open(self):
- filenames = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))]
- self.entries = []
- for filename in filenames:
- with open(filename, 'rb') as plist_entry:
- try:
- dict_entry = plistlib.readPlist(plist_entry)
- except ExpatError:
- pass
- else:
- try:
- timezone = pytz.timezone(dict_entry['Time Zone'])
- except (KeyError, pytz.exceptions.UnknownTimeZoneError):
- timezone = tzlocal.get_localzone()
- date = dict_entry['Creation Date']
- date = date + timezone.utcoffset(date, is_dst=False)
- raw = dict_entry['Entry Text']
- sep = re.search("\n|[\?!.]+ +\n?", raw)
- title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
- entry = Entry.Entry(self, date, title, body, starred=dict_entry["Starred"])
- entry.uuid = dict_entry["UUID"]
- entry.tags = [self.config['tagsymbols'][0] + tag for tag in dict_entry.get("Tags", [])]
- self.entries.append(entry)
- self.sort()
-
- def write(self):
- """Writes only the entries that have been modified into plist files."""
- for entry in self.entries:
- if entry.modified:
- if not hasattr(entry, "uuid"):
- entry.uuid = uuid.uuid1().hex
- utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple()))
- filename = os.path.join(self.config['journal'], "entries", entry.uuid + ".doentry")
- entry_plist = {
- 'Creation Date': utc_time,
- 'Starred': entry.starred if hasattr(entry, 'starred') else False,
- 'Entry Text': entry.title+"\n"+entry.body,
- 'Time Zone': str(tzlocal.get_localzone()),
- 'UUID': entry.uuid,
- 'Tags': [tag.strip(self.config['tagsymbols']) for tag in entry.tags]
- }
- plistlib.writePlist(entry_plist, filename)
- for entry in self._deleted_entries:
- filename = os.path.join(self.config['journal'], "entries", entry.uuid+".doentry")
- os.remove(filename)
-
- def editable_str(self):
- """Turns the journal into a string of entries that can be edited
- manually and later be parsed with eslf.parse_editable_str."""
- return u"\n".join([u"# {0}\n{1}".format(e.uuid, e.__unicode__()) for e in self.entries])
-
- def parse_editable_str(self, edited):
- """Parses the output of self.editable_str and updates it's entries."""
- # Method: create a new list of entries from the edited text, then match
- # UUIDs of the new entries against self.entries, updating the entries
- # if the edited entries differ, and deleting entries from self.entries
- # if they don't show up in the edited entries anymore.
- date_length = len(datetime.today().strftime(self.config['timeformat']))
-
- # Initialise our current entry
- entries = []
- current_entry = None
-
- for line in edited.splitlines():
- # try to parse line as UUID => new entry begins
- line = line.rstrip()
- m = re.match("# *([a-f0-9]+) *$", line.lower())
- if m:
- if current_entry:
- entries.append(current_entry)
- current_entry = Entry.Entry(self)
- current_entry.modified = False
- current_entry.uuid = m.group(1).lower()
- else:
- try:
- new_date = datetime.strptime(line[:date_length], self.config['timeformat'])
- if line.endswith("*"):
- current_entry.starred = True
- line = line[:-1]
- current_entry.title = line[date_length+1:]
- current_entry.date = new_date
- except ValueError:
- if current_entry:
- current_entry.body += line + "\n"
-
- # Append last entry
- if current_entry:
- entries.append(current_entry)
-
- # Now, update our current entries if they changed
- for entry in entries:
- entry.parse_tags()
- matched_entries = [e for e in self.entries if e.uuid.lower() == entry.uuid]
- if matched_entries:
- # This entry is an existing entry
- match = matched_entries[0]
- if match != entry:
- self.entries.remove(match)
- entry.modified = True
- self.entries.append(entry)
- else:
- # This entry seems to be new... save it.
- entry.modified = True
- self.entries.append(entry)
- # Remove deleted entries
- edited_uuids = [e.uuid for e in entries]
- self._deleted_entries = [e for e in self.entries if e.uuid not in edited_uuids]
- self.entries[:] = [e for e in self.entries if e.uuid in edited_uuids]
- return entries
diff --git a/jrnl/__init__.py b/jrnl/__init__.py
index 1e1dc0ec..91b75919 100644
--- a/jrnl/__init__.py
+++ b/jrnl/__init__.py
@@ -8,7 +8,7 @@ jrnl is a simple journal application for your command line.
from __future__ import absolute_import
__title__ = 'jrnl'
-__version__ = '1.7.22'
+__version__ = '1.8.0'
__author__ = 'Manuel Ebert'
__license__ = 'MIT License'
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
diff --git a/jrnl/cli.py b/jrnl/cli.py
index 417398a4..4aa16588 100644
--- a/jrnl/cli.py
+++ b/jrnl/cli.py
@@ -9,10 +9,10 @@
from __future__ import absolute_import
from . import Journal
+from . import DayOneJournal
from . import util
from . import exporters
from . import install
-from . import __version__
import jrnl
import os
import argparse
@@ -43,12 +43,13 @@ def parse_args(args=None):
exporting.add_argument('--tags', dest='tags', action="store_true", help='Returns a list of all tags and number of occurences')
exporting.add_argument('--export', metavar='TYPE', dest='export', help='Export your journal to Markdown, JSON or Text', default=False, const=None)
exporting.add_argument('-o', metavar='OUTPUT', dest='output', help='The output of the file can be provided when using with --export', default=False, const=None)
- exporting.add_argument('--encrypt', metavar='FILENAME', dest='encrypt', help='Encrypts your existing journal with a new password', nargs='?', default=False, const=None)
- exporting.add_argument('--decrypt', metavar='FILENAME', dest='decrypt', help='Decrypts your journal and stores it in plain text', nargs='?', default=False, const=None)
+ exporting.add_argument('--encrypt', metavar='FILENAME', dest='encrypt', help='Encrypts your existing journal with a new password', nargs='?', default=False, const=None)
+ exporting.add_argument('--decrypt', metavar='FILENAME', dest='decrypt', help='Decrypts your journal and stores it in plain text', nargs='?', default=False, const=None)
exporting.add_argument('--edit', dest='edit', help='Opens your editor to edit the selected entries.', action="store_true")
return parser.parse_args(args)
+
def guess_mode(args, config):
"""Guesses the mode (compose, read or export) from the given arguments"""
compose = True
@@ -65,6 +66,7 @@ def guess_mode(args, config):
return compose, export
+
def encrypt(journal, filename=None):
""" Encrypt into new file. If filename is not set, we encrypt the journal file itself. """
password = util.getpass("Enter new password: ")
@@ -75,6 +77,7 @@ def encrypt(journal, filename=None):
util.set_keychain(journal.name, password)
util.prompt("Journal encrypted to {0}.".format(filename or journal.config['journal']))
+
def decrypt(journal, filename=None):
""" Decrypts into new file. If filename is not set, we encrypt the journal file itself. """
journal.config['encrypt'] = False
@@ -82,20 +85,21 @@ def decrypt(journal, filename=None):
journal.write(filename)
util.prompt("Journal decrypted to {0}.".format(filename or journal.config['journal']))
+
def touch_journal(filename):
"""If filename does not exist, touch the file"""
if not os.path.exists(filename):
util.prompt("[Journal created at {0}]".format(filename))
open(filename, 'a').close()
+
def list_journals(config):
"""List the journals specified in the configuration file"""
-
sep = "\n"
journal_list = sep.join(config['journals'])
-
return journal_list
+
def update_config(config, new_config, scope, force_local=False):
"""Updates a config dict with new values - either global if scope is None
or config['journals'][scope] is just a string pointing to a journal file,
@@ -108,6 +112,7 @@ def update_config(config, new_config, scope, force_local=False):
else:
config.update(new_config)
+
def run(manual_args=None):
args = parse_args(manual_args)
args.text = [p.decode('utf-8') if util.PY2 and not isinstance(p, unicode) else p for p in args.text]
@@ -158,7 +163,7 @@ def run(manual_args=None):
if os.path.isdir(config['journal']):
if config['journal'].strip("/").endswith(".dayone") or \
"entries" in os.listdir(config['journal']):
- journal = Journal.DayOne(**config)
+ journal = DayOneJournal.DayOne(**config)
else:
util.prompt(u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
sys.exit(1)
@@ -178,7 +183,7 @@ def run(manual_args=None):
elif config['editor']:
raw = util.get_text_from_editor(config)
else:
- raw = util.py23_read("[Compose Entry; " + _exit_multiline_code + " to finish writing]\n")
+ raw = util.py23_read("[Compose Entry; " + _exit_multiline_code + " to finish writing]\n")
if raw:
args.text = [raw]
else:
@@ -189,7 +194,7 @@ def run(manual_args=None):
raw = " ".join(args.text).strip()
if util.PY2 and type(raw) is not unicode:
raw = raw.decode(sys.getfilesystemencoding())
- entry = journal.new_entry(raw)
+ journal.new_entry(raw)
util.prompt("[Entry added to {0} journal]".format(journal_name))
journal.write()
else:
@@ -244,8 +249,10 @@ def run(manual_args=None):
num_deleted = old_num_entries - len(journal)
num_edited = len([e for e in journal.entries if e.modified])
prompts = []
- if num_deleted: prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
- if num_edited: prompts.append("{0} {1} modified".format(num_edited, "entry" if num_deleted == 1 else "entries"))
+ if num_deleted:
+ prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
+ if num_edited:
+ prompts.append("{0} {1} modified".format(num_edited, "entry" if num_deleted == 1 else "entries"))
if prompts:
util.prompt("[{0}]".format(", ".join(prompts).capitalize()))
journal.entries += other_entries
diff --git a/setup.py b/setup.py
index 477735f9..6b48675a 100644
--- a/setup.py
+++ b/setup.py
@@ -72,6 +72,7 @@ setup(
install_requires = [
"parsedatetime>=1.2",
"pytz>=2013b",
+ "six>=1.6.1",
"tzlocal>=1.1",
"keyring>=3.3",
"python-dateutil>=2.2"