Merge pull request #118 from maebert/develop

Develop
This commit is contained in:
Manuel Ebert 2013-12-22 09:42:14 -08:00
commit 01ab0fee83
11 changed files with 232 additions and 73 deletions

View file

@ -1,6 +1,13 @@
Changelog Changelog
========= =========
### 1.7 (December 22, 2013)
* __1.7.1__ Fixes issues with parsing time information in entries.
* __1.7.0__ Edit encrypted or DayOne journals with `jrnl --edit`.
### 1.6 (November 5, 2013) ### 1.6 (November 5, 2013)
* __1.6.6__ -v prints the current version, also better strings for windows users. Furthermore, jrnl/jrnl.py moved to jrnl/cli.py * __1.6.6__ -v prints the current version, also better strings for windows users. Furthermore, jrnl/jrnl.py moved to jrnl/cli.py

View file

@ -6,12 +6,12 @@ Advanced Usage
Configuration File Configuration File
------------------- -------------------
You can configure the way jrnl behaves in a configuration file. By default, this is `~/.jrnl_conf`. If you have the `XDG_CONFIG_HOME` variable set, the configuration file will be saved under `$XDG_CONFIG_HOME/jrnl`. The configuration file is a simple JSON file with the following options. You can configure the way jrnl behaves in a configuration file. By default, this is ``~/.jrnl_conf``. If you have the ``XDG_CONFIG_HOME`` variable set, the configuration file will be saved under ``$XDG_CONFIG_HOME/jrnl``. The configuration file is a simple JSON file with the following options.
- ``journals`` - ``journals``
paths to your journal files paths to your journal files
- ``editor`` - ``editor``
if set, executes this command to launch an external editor for writing your entries, e.g. ``vim`` or ``subl -w`` (note the ``-w`` flag to make sure _jrnl_ waits for Sublime Text to close the file before writing into the journal. If you're using MacVim, that would be ``mvim -f``). if set, executes this command to launch an external editor for writing your entries, e.g. ``vim`` or ``subl -w`` (note the ``-w`` flag to make sure *jrnl* waits for Sublime Text to close the file before writing into the journal. If you're using MacVim, that would be ``mvim -f``).
- ``encrypt`` - ``encrypt``
if ``true``, encrypts your journal using AES. if ``true``, encrypts your journal using AES.
- ``tagsymbols`` - ``tagsymbols``
@ -51,6 +51,7 @@ Using your DayOne journal instead of a flat text file is dead simple -- instead
* ``~/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. Instead of all entries being in a single file, each entry will live in a separate `plist` file.
Multiple journal files Multiple journal files
---------------------- ----------------------

View file

@ -15,6 +15,8 @@ you'll get a list of all tags you used in your journal, sorted by most frequent.
List of all entries List of all entries
------------------- -------------------
::
jrnl --short jrnl --short
Will only display the date and title of each entry. Will only display the date and title of each entry.

View file

@ -15,5 +15,9 @@ Optionally, your journal can be encrypted using the `256-bit AES <http://en.wiki
Why keep a journal? Why keep a journal?
------------------- -------------------
Journals aren't only for 13-year old girls and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't. For personal use, make it a good habit to write at least 20 words a day. Just to reflect what made this day special, why you haven't wasted it. For professional use, consider a text-based journal to be the perfect complement to your GTD todo list - a documentation of what and how you've done it. Journals aren't only for 13-year old girls and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't.
For personal use, make it a good habit to write at least 20 words a day. Just to reflect what made this day special, why you haven't wasted it.
For professional use, consider a text-based journal to be the perfect complement to your GTD todo list - a documentation of what and how you've done it. Or use it as a quick way to keep a change log. Or use it to keep a lab book.

View file

@ -83,5 +83,39 @@ the last five entries containing both ``@pineapple`` **and** ``@lubricant``. You
.. note:: .. note::
``jrnl @pinkie @WorldDomination`` will switch to viewing mode because although _no_ command line arguments are given, all the input strings look like tags - *jrnl* will assume you want to filter by tag. ``jrnl @pinkie @WorldDomination`` will switch to viewing mode because although **no** command line arguments are given, all the input strings look like tags - *jrnl* will assume you want to filter by tag.
Editing older entries
---------------------
You can edit selected entries after you wrote them. This is particularly useful when your journal file is encrypted or if you're using a DayOne journal. To use this feature, you need to have an editor configured in your journal configuration file (see :doc:`advanced usage <advanced>`)::
jrnl -until 1950 @texas -and @history --edit
Will open your editor with all entries tagged with ``@texas`` and ``@history`` before 1950. You can make any changes to them you want; after you save the file and close the editor, your journal will be updated.
Of course, if you are using multiple journals, you can also edit e.g. the latest entry of your work journal with ``jrnl work -n 1 --edit``. In any case, this will bring up your editor and save (and, if applicable, encrypt) your edited journal after you save and exit the editor.
You can also use this feature for deleting entries from your journal::
jrnl @girlfriend -until 'june 2012' --edit
Just select all text, press delete, and everything is gone...
Editing DayOne Journals
~~~~~~~~~~~~~~~~~~~~~~~
DayOne journals can be edited exactly the same way, however the output looks a little bit different because of the way DayOne stores its entries:
.. code-block:: output
# af8dbd0d43fb55458f11aad586ea2abf
2013-05-02 15:30 I told everyone I built my @robot wife for sex.
But late at night when we're alone we mostly play Battleship.
# 2391048fe24111e1983ed49a20be6f9e
2013-08-10 03:22 I had all kinds of plans in case of a @zombie attack.
I just figured I'd be on the other side.
The long strings starting with hash symbol are the so-called UUIDs, unique identifiers for each entry. Don't touch them. If you do, then the old entry would get deleted and a new one written, which means that you could DayOne loose data that jrnl can't handle (such as as the entry's geolocation).

View file

@ -13,3 +13,10 @@ Feature: Zapped bugs should stay dead.
When we run "jrnl Herro" When we run "jrnl Herro"
Then we should get an error Then we should get an error
Then we should see the message "is a directory, but doesn't seem to be a DayOne journal either" Then we should see the message "is a directory, but doesn't seem to be a DayOne journal either"
Scenario: Date with time should be parsed correctly
# https://github.com/maebert/jrnl/issues/117
Given we use the config "basic.json"
When we run "jrnl 2013-11-30 15:42: Project Started."
Then we should see the message "Entry added"
and the journal should contain "2013-11-30 15:42 Project Started."

View file

@ -9,10 +9,11 @@ class Entry:
def __init__(self, journal, date=None, title="", body="", starred=False): def __init__(self, journal, date=None, title="", body="", starred=False):
self.journal = journal # Reference to journal mainly to access it's config self.journal = journal # Reference to journal mainly to access it's config
self.date = date or datetime.now() self.date = date or datetime.now()
self.title = title.strip() self.title = title.strip("\n ")
self.body = body.strip() self.body = body.strip("\n ")
self.tags = self.parse_tags() self.tags = self.parse_tags()
self.starred = starred self.starred = starred
self.modified = False
def parse_tags(self): def parse_tags(self):
fulltext = " ".join([self.title, self.body]).lower() fulltext = " ".join([self.title, self.body]).lower()
@ -67,6 +68,18 @@ class Entry:
def __repr__(self): def __repr__(self):
return "<Entry '{0}' on {1}>".format(self.title.strip(), self.date.strftime("%Y-%m-%d %H:%M")) return "<Entry '{0}' on {1}>".format(self.title.strip(), self.date.strftime("%Y-%m-%d %H:%M"))
def __eq__(self, other):
if not isinstance(other, Entry) \
or self.title.strip() != other.title.strip() \
or self.body.strip() != other.body.strip() \
or self.date != other.date \
or self.starred != other.starred:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def to_dict(self): def to_dict(self):
return { return {
'title': self.title.strip(), 'title': self.title.strip(),

View file

@ -11,6 +11,7 @@ try: import parsedatetime.parsedatetime_consts as pdt
except ImportError: import parsedatetime.parsedatetime as pdt except ImportError: import parsedatetime.parsedatetime as pdt
import re import re
from datetime import datetime from datetime import datetime
import dateutil
import time import time
import sys import sys
try: try:
@ -50,9 +51,11 @@ class Journal(object):
self.search_tags = None # Store tags we're highlighting self.search_tags = None # Store tags we're highlighting
self.name = name self.name = name
journal_txt = self.open() self.open()
self.entries = self.parse(journal_txt)
self.sort() def __len__(self):
"""Returns the number of entries"""
return len(self.entries)
def _colorize(self, string): def _colorize(self, string):
if colorama: if colorama:
@ -115,9 +118,10 @@ class Journal(object):
else: else:
with codecs.open(filename, "r", "utf-8") as f: with codecs.open(filename, "r", "utf-8") as f:
journal = f.read() journal = f.read()
return journal self.entries = self._parse(journal)
self.sort()
def parse(self, journal): def _parse(self, journal_txt):
"""Parses a journal that's stored in a string and returns a list of entries""" """Parses a journal that's stored in a string and returns a list of entries"""
# Entries start with a line that looks like 'date title' - let's figure out how # Entries start with a line that looks like 'date title' - let's figure out how
@ -128,7 +132,7 @@ class Journal(object):
entries = [] entries = []
current_entry = None current_entry = None
for line in journal.splitlines(): for line in journal_txt.splitlines():
try: try:
# try to parse line as date => new entry begins # try to parse line as date => new entry begins
line = line.strip() line = line.strip()
@ -184,7 +188,7 @@ class Journal(object):
def write(self, filename=None): def write(self, filename=None):
"""Dumps the journal into the config file, overwriting it""" """Dumps the journal into the config file, overwriting it"""
filename = filename or self.config['journal'] filename = filename or self.config['journal']
journal = "\n".join([e.__unicode__() for e in self.entries]) journal = u"\n".join([e.__unicode__() for e in self.entries])
if self.config['encrypt']: if self.config['encrypt']:
journal = self._encrypt(journal) journal = self._encrypt(journal)
with open(filename, 'wb') as journal_file: with open(filename, 'wb') as journal_file:
@ -249,7 +253,12 @@ class Journal(object):
elif isinstance(date_str, datetime): elif isinstance(date_str, datetime):
return date_str return date_str
date, flag = self.dateparse.parse(date_str) try:
date = dateutil.parser.parse(date_str)
flag = 1 if date.hour == 0 and date.minute == 0 else 2
date = date.timetuple()
except:
date, flag = self.dateparse.parse(date_str)
if not flag: # Oops, unparsable. if not flag: # Oops, unparsable.
try: # Try and parse this as a single year try: # Try and parse this as a single year
@ -281,20 +290,15 @@ class Journal(object):
raw = raw.replace('\\n ', '\n').replace('\\n', '\n') raw = raw.replace('\\n ', '\n').replace('\\n', '\n')
starred = False starred = False
# Split raw text into title and body # Split raw text into title and body
title_end = len(raw) sep = re.search("[\n!?.]+", raw)
for separator in ["\n", ". ", "? ", "! "]: title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
sep_pos = raw.find(separator)
if 1 < sep_pos < title_end:
title_end = sep_pos
title = raw[:title_end+1]
body = raw[title_end+1:].strip()
starred = False starred = False
if not date: if not date:
if title.find(":") > 0: if title.find(": ") > 0:
starred = "*" in title[:title.find(":")] starred = "*" in title[:title.find(": ")]
date = self.parse_date(title[:title.find(":")]) date = self.parse_date(title[:title.find(": ")])
if date or starred: # Parsed successfully, strip that from the raw text if date or starred: # Parsed successfully, strip that from the raw text
title = title[title.find(":")+1:].strip() title = title[title.find(": ")+1:].strip()
elif title.strip().startswith("*"): elif title.strip().startswith("*"):
starred = True starred = True
title = title[1:].strip() title = title[1:].strip()
@ -304,25 +308,36 @@ class Journal(object):
if not date: # Still nothing? Meh, just live in the moment. if not date: # Still nothing? Meh, just live in the moment.
date = self.parse_date("now") date = self.parse_date("now")
entry = Entry.Entry(self, date, title, body, starred=starred) entry = Entry.Entry(self, date, title, body, starred=starred)
entry.modified = True
self.entries.append(entry) self.entries.append(entry)
if sort: if sort:
self.sort() self.sort()
return entry return entry
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([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."""
mod_entries = self._parse(edited)
# Match those entries that can be found in self.entries and set
# these to modified, so we can get a count of how many entries got
# modified and how many got deleted later.
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): class DayOne(Journal):
"""A special Journal handling DayOne files""" """A special Journal handling DayOne files"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.entries = [] self.entries = []
self._deleted_entries = []
super(DayOne, self).__init__(**kwargs) super(DayOne, self).__init__(**kwargs)
def open(self): def open(self):
files = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))] filenames = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))]
return files
def parse(self, filenames):
"""Instead of parsing a string into an entry, this method will take a list
of filenames, interpret each as a plist file and create a new entry from that."""
self.entries = [] self.entries = []
for filename in filenames: for filename in filenames:
with open(filename, 'rb') as plist_entry: with open(filename, 'rb') as plist_entry:
@ -333,34 +348,97 @@ class DayOne(Journal):
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) raw = dict_entry['Entry Text']
entry.starred = dict_entry["Starred"] sep = re.search("[\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.uuid = dict_entry["UUID"]
entry.tags = dict_entry.get("Tags", []) entry.tags = dict_entry.get("Tags", [])
# We're using new_entry to create the Entry object, which adds the entry self.entries.append(entry)
# to self.entries already. However, in the original Journal.__init__, this self.sort()
# method is expected to return a list of newly created entries, which is why
# we're returning the obvious.
return self.entries
def write(self): def write(self):
"""Writes only the entries that have been modified into plist files.""" """Writes only the entries that have been modified into plist files."""
for entry in self.entries: for entry in self.entries:
# Assumption: since jrnl can not manipulate existing entries, all entries if entry.modified:
# that have a uuid will be old ones, and only the one that doesn't will if not hasattr(entry, "uuid"):
# have a new one! entry.uuid = uuid.uuid1().hex
if not hasattr(entry, "uuid"):
utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple())) utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple()))
new_uuid = uuid.uuid1().hex filename = os.path.join(self.config['journal'], "entries", entry.uuid+".doentry")
filename = os.path.join(self.config['journal'], "entries", new_uuid+".doentry")
entry_plist = { entry_plist = {
'Creation Date': utc_time, 'Creation Date': utc_time,
'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': entry.uuid,
'Tags': [tag.strip(self.config['tagsymbols']) for tag in entry.tags] 'Tags': [tag.strip(self.config['tagsymbols']) for tag in entry.tags]
} }
# print entry_plist
plistlib.writePlist(entry_plist, filename) 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(["# {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.strip()
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

View file

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

View file

@ -20,8 +20,6 @@ except (SystemError, ValueError):
import install import install
import jrnl import jrnl
import os import os
import tempfile
import subprocess
import argparse import argparse
import sys import sys
@ -51,7 +49,7 @@ def parse_args(args=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('-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('--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('--decrypt', metavar='FILENAME', dest='decrypt', help='Decrypts your journal and stores it in plain text', nargs='?', default=False, const=None)
exporting.add_argument('--delete-last', dest='delete_last', help='Deletes the last entry from your journal file.', action="store_true") exporting.add_argument('--edit', dest='edit', help='Opens your editor to edit the selected entries.', action="store_true")
return parser.parse_args(args) return parser.parse_args(args)
@ -59,7 +57,7 @@ def guess_mode(args, config):
"""Guesses the mode (compose, read or export) from the given arguments""" """Guesses the mode (compose, read or export) from the given arguments"""
compose = True compose = True
export = False export = False
if args.decrypt is not False or args.encrypt is not False or args.export is not False or any((args.short, args.tags, args.delete_last)): if args.decrypt is not False or args.encrypt is not False or args.export is not False or any((args.short, args.tags, args.edit)):
compose = False compose = False
export = True export = True
elif any((args.start_date, args.end_date, args.limit, args.strict, args.starred)): elif any((args.start_date, args.end_date, args.limit, args.strict, args.starred)):
@ -71,20 +69,6 @@ def guess_mode(args, config):
return compose, export return compose, export
def get_text_from_editor(config):
tmpfile = os.path.join(tempfile.gettempdir(), "jrnl")
subprocess.call(config['editor'].split() + [tmpfile])
if os.path.exists(tmpfile):
with open(tmpfile) as f:
raw = f.read()
os.remove(tmpfile)
else:
util.prompt('[Nothing saved to file]')
raw = ''
return raw
def encrypt(journal, filename=None): def encrypt(journal, filename=None):
""" Encrypt into new file. If filename is not set, we encrypt the journal file itself. """ """ Encrypt into new file. If filename is not set, we encrypt the journal file itself. """
password = util.getpass("Enter new password: ") password = util.getpass("Enter new password: ")
@ -164,11 +148,10 @@ def run(manual_args=None):
else: else:
journal = Journal.Journal(journal_name, **config) journal = Journal.Journal(journal_name, **config)
# How to quit writing?
if "win32" in sys.platform: if "win32" in sys.platform:
# for Windows systems
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter" _exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
else: else:
# for *nix systems (and others?)
_exit_multiline_code = "press Ctrl+D" _exit_multiline_code = "press Ctrl+D"
if mode_compose and not args.text: if mode_compose and not args.text:
@ -176,7 +159,7 @@ def run(manual_args=None):
# Piping data into jrnl # Piping data into jrnl
raw = util.py23_read() raw = util.py23_read()
elif config['editor']: elif config['editor']:
raw = get_text_from_editor(config) raw = util.get_text_from_editor(config)
else: 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: if raw:
@ -193,6 +176,7 @@ def run(manual_args=None):
util.prompt("[Entry added to {0} journal]".format(journal_name)) util.prompt("[Entry added to {0} journal]".format(journal_name))
journal.write() journal.write()
else: else:
old_entries = journal.entries
journal.filter(tags=args.text, journal.filter(tags=args.text,
start_date=args.start_date, end_date=args.end_date, start_date=args.start_date, end_date=args.end_date,
strict=args.strict, strict=args.strict,
@ -231,10 +215,20 @@ def run(manual_args=None):
update_config(original_config, {"encrypt": False}, journal_name, force_local=True) update_config(original_config, {"encrypt": False}, journal_name, force_local=True)
install.save_config(original_config, config_path=CONFIG_PATH) install.save_config(original_config, config_path=CONFIG_PATH)
elif args.delete_last: elif args.edit:
last_entry = journal.entries.pop() other_entries = [e for e in old_entries if e not in journal.entries]
util.prompt("[Deleted Entry:]") # Edit
print(last_entry.pprint()) old_num_entries = len(journal)
edited = util.get_text_from_editor(config, journal.editable_str())
journal.parse_editable_str(edited)
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} entries deleted".format(num_deleted))
if num_edited: prompts.append("{0} entries modified".format(num_edited))
if prompts:
util.prompt("[{0}]".format(", ".join(prompts).capitalize()))
journal.entries += other_entries
journal.write() journal.write()
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -9,6 +9,9 @@ import pytz
try: import simplejson as json try: import simplejson as json
except ImportError: import json except ImportError: import json
import re import re
import tempfile
import subprocess
import codecs
PY3 = sys.version_info[0] == 3 PY3 = sys.version_info[0] == 3
PY2 = sys.version_info[0] == 2 PY2 = sys.version_info[0] == 2
@ -121,3 +124,19 @@ def load_and_fix_json(json_path):
prompt("[Entry was NOT added to your journal]") prompt("[Entry was NOT added to your journal]")
sys.exit(1) sys.exit(1)
def get_text_from_editor(config, template=""):
tmpfile = os.path.join(tempfile.gettempdir(), "jrnl")
if template:
with codecs.open(tmpfile, 'w', "utf-8") as f:
f.write(template)
subprocess.call(config['editor'].split() + [tmpfile])
if os.path.exists(tmpfile):
with codecs.open(tmpfile, "r", "utf-8") as f:
raw = f.read()
os.remove(tmpfile)
else:
prompt('[Nothing saved to file]')
raw = ''
return raw