Updated docs from master

This commit is contained in:
Manuel Ebert 2013-12-22 18:46:49 +01:00
parent 82f9a713dc
commit 7d577baf66
54 changed files with 451 additions and 156 deletions

View file

@ -9,10 +9,11 @@ class Entry:
def __init__(self, journal, date=None, title="", body="", starred=False):
self.journal = journal # Reference to journal mainly to access it's config
self.date = date or datetime.now()
self.title = title.strip()
self.body = body.strip()
self.title = title.strip("\n ")
self.body = body.strip("\n ")
self.tags = self.parse_tags()
self.starred = starred
self.modified = False
def parse_tags(self):
fulltext = " ".join([self.title, self.body]).lower()
@ -67,6 +68,18 @@ class Entry:
def __repr__(self):
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):
return {
'title': self.title.strip(),

Binary file not shown.

View file

@ -11,6 +11,7 @@ try: import parsedatetime.parsedatetime_consts as pdt
except ImportError: import parsedatetime.parsedatetime as pdt
import re
from datetime import datetime
import dateutil
import time
import sys
try:
@ -50,9 +51,11 @@ class Journal(object):
self.search_tags = None # Store tags we're highlighting
self.name = name
journal_txt = self.open()
self.entries = self.parse(journal_txt)
self.sort()
self.open()
def __len__(self):
"""Returns the number of entries"""
return len(self.entries)
def _colorize(self, string):
if colorama:
@ -115,9 +118,10 @@ class Journal(object):
else:
with codecs.open(filename, "r", "utf-8") as f:
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"""
# Entries start with a line that looks like 'date title' - let's figure out how
@ -128,7 +132,7 @@ class Journal(object):
entries = []
current_entry = None
for line in journal.splitlines():
for line in journal_txt.splitlines():
try:
# try to parse line as date => new entry begins
line = line.strip()
@ -184,7 +188,7 @@ class Journal(object):
def write(self, filename=None):
"""Dumps the journal into the config file, overwriting it"""
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']:
journal = self._encrypt(journal)
with open(filename, 'wb') as journal_file:
@ -249,7 +253,12 @@ class Journal(object):
elif isinstance(date_str, datetime):
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.
try: # Try and parse this as a single year
@ -281,20 +290,15 @@ class Journal(object):
raw = raw.replace('\\n ', '\n').replace('\\n', '\n')
starred = False
# Split raw text into title and body
title_end = len(raw)
for separator in ["\n", ". ", "? ", "! "]:
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()
sep = re.search("[\n!?.]+", raw)
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
starred = False
if not date:
if title.find(":") > 0:
starred = "*" in title[:title.find(":")]
date = self.parse_date(title[:title.find(":")])
if title.find(": ") > 0:
starred = "*" in title[:title.find(": ")]
date = self.parse_date(title[:title.find(": ")])
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("*"):
starred = True
title = title[1:].strip()
@ -304,25 +308,36 @@ class Journal(object):
if not date: # Still nothing? Meh, just live in the moment.
date = self.parse_date("now")
entry = Entry.Entry(self, date, title, body, starred=starred)
entry.modified = True
self.entries.append(entry)
if sort:
self.sort()
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):
"""A special Journal handling DayOne files"""
def __init__(self, **kwargs):
self.entries = []
self._deleted_entries = []
super(DayOne, self).__init__(**kwargs)
def open(self):
files = [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."""
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:
@ -333,34 +348,97 @@ class DayOne(Journal):
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"]
raw = dict_entry['Entry Text']
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.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
# we're returning the obvious.
return self.entries
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:
# Assumption: since jrnl can not manipulate existing entries, all entries
# that have a uuid will be old ones, and only the one that doesn't will
# have a new one!
if not hasattr(entry, "uuid"):
if entry.modified:
if not hasattr(entry, "uuid"):
entry.uuid = uuid.uuid1().hex
utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple()))
new_uuid = uuid.uuid1().hex
filename = os.path.join(self.config['journal'], "entries", new_uuid+".doentry")
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': util.get_local_timezone(),
'UUID': new_uuid,
'UUID': entry.uuid,
'Tags': [tag.strip(self.config['tagsymbols']) for tag in entry.tags]
}
# print entry_plist
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

Binary file not shown.

View file

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

Binary file not shown.

View file

@ -20,8 +20,6 @@ except (SystemError, ValueError):
import install
import jrnl
import os
import tempfile
import subprocess
import argparse
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('--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('--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)
@ -59,7 +57,7 @@ def guess_mode(args, config):
"""Guesses the mode (compose, read or export) from the given arguments"""
compose = True
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
export = True
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
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):
""" Encrypt into new file. If filename is not set, we encrypt the journal file itself. """
password = util.getpass("Enter new password: ")
@ -164,11 +148,10 @@ def run(manual_args=None):
else:
journal = Journal.Journal(journal_name, **config)
# How to quit writing?
if "win32" in sys.platform:
# for Windows systems
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
else:
# for *nix systems (and others?)
_exit_multiline_code = "press Ctrl+D"
if mode_compose and not args.text:
@ -176,7 +159,7 @@ def run(manual_args=None):
# Piping data into jrnl
raw = util.py23_read()
elif config['editor']:
raw = get_text_from_editor(config)
raw = util.get_text_from_editor(config)
else:
raw = util.py23_read("[Compose Entry; " + _exit_multiline_code + " to finish writing]\n")
if raw:
@ -193,6 +176,7 @@ def run(manual_args=None):
util.prompt("[Entry added to {0} journal]".format(journal_name))
journal.write()
else:
old_entries = journal.entries
journal.filter(tags=args.text,
start_date=args.start_date, end_date=args.end_date,
strict=args.strict,
@ -231,10 +215,20 @@ def run(manual_args=None):
update_config(original_config, {"encrypt": False}, journal_name, force_local=True)
install.save_config(original_config, config_path=CONFIG_PATH)
elif args.delete_last:
last_entry = journal.entries.pop()
util.prompt("[Deleted Entry:]")
print(last_entry.pprint())
elif args.edit:
other_entries = [e for e in old_entries if e not in journal.entries]
# Edit
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()
if __name__ == "__main__":

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -9,6 +9,9 @@ import pytz
try: import simplejson as json
except ImportError: import json
import re
import tempfile
import subprocess
import codecs
PY3 = sys.version_info[0] == 3
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]")
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

Binary file not shown.