Merge pull request #5 from dedan/master

here all the changes we made together
This commit is contained in:
Manuel Ebert 2012-04-15 06:49:55 -07:00
commit 6e640dec3d

109
jrnl.py
View file

@ -1,20 +1,30 @@
#!/usr/bin/python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
import os
import tempfile
import parsedatetime.parsedatetime as pdt import parsedatetime.parsedatetime as pdt
import parsedatetime.parsedatetime_consts as pdc import parsedatetime.parsedatetime_consts as pdc
import subprocess
import re import re
import argparse import argparse
from datetime import datetime from datetime import datetime
import time import time
import json import json
import sys
import readline, glob
from Crypto.Cipher import AES
import getpass
import mimetypes
config = { default_config = {
'journal': "/home/manuel/Dropbox/Notes/journal.txt", 'journal': os.path.expanduser("~/journal.txt"),
'editor': "",
'encrypt': False,
'key': "",
'default_hour': 9, 'default_hour': 9,
'default_minute': 0, 'default_minute': 0,
'timeformat': "%Y-%m-%d %H:%M", 'timeformat': "%Y-%m-%d %H:%M",
'tagsymbols': '#@' 'tagsymbols': '@'
} }
class Entry: class Entry:
@ -63,10 +73,17 @@ class Journal:
consts = pdc.Constants() consts = pdc.Constants()
consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday
self.dateparse = pdt.Calendar(consts) self.dateparse = pdt.Calendar(consts)
self.crypto = None
self.entries = self.open() self.entries = self.open()
self.sort() self.sort()
def _block_tail(self, s, b=16, force=False):
"""Appends spaces to a string until length is a multiple of b"""
if force and len(s) % 16 == 0:
return s + " "*16
return s + " "*(b - len(s) % b)
def open(self, filename=None): def open(self, filename=None):
"""Opens the journal file defined in the config and parses it into a list of Entries. """Opens the journal file defined in the config and parses it into a list of Entries.
Entries have the form (date, title, body).""" Entries have the form (date, title, body)."""
@ -80,8 +97,28 @@ class Journal:
entries = [] entries = []
current_entry = None current_entry = None
journal_file = open(filename) with open(filename) as f:
for line in journal_file.readlines(): if config['encrypt']:
journal_encrypted = f.read()
key = config['key'] or getpass.getpass()
key = self._block_tail(key)
self.crypto = AES.new(key, AES.MODE_ECB)
journal_plain = self.crypto.decrypt(journal_encrypted)
# encrypted files should end with spaces. No spaces, no luck.
attempts = 1
while journal_plain and attempts < 3 and journal_plain[-1] != " ":
attempts += 1
key = getpass.getpass('Wrong password. Try again: ')
key = self._block_tail(key)
self.crypto = AES.new(key, AES.MODE_ECB)
journal_plain = self.crypto.decrypt(journal_encrypted)
if attempts >= 3:
print("Extremely wrong password.")
sys.exit(-1)
else:
journal_plain = f.read()
for line in journal_plain.split(os.linesep):
if line: if line:
try: try:
new_date = datetime.fromtimestamp(time.mktime(time.strptime(line[:date_length], config['timeformat']))) new_date = datetime.fromtimestamp(time.mktime(time.strptime(line[:date_length], config['timeformat'])))
@ -97,7 +134,6 @@ class Journal:
# Append last entry # Append last entry
if current_entry: if current_entry:
entries.append(current_entry) entries.append(current_entry)
journal_file.close()
for entry in entries: for entry in entries:
entry.parse_tags() entry.parse_tags()
return entries return entries
@ -117,10 +153,13 @@ class Journal:
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_file = open(filename, 'w') journal_plain = os.linesep.join([str(e) for e in self.entries])
for entry in self.entries: with open(filename, 'w') as journal_file:
journal_file.write(str(entry)+"\n") if self.crypto:
journal_file.close() journal_padded = self._block_tail(journal_plain, force=True)
journal_file.write(self.crypto.encrypt(journal_padded))
else:
journal_file.write(journal_plain)
def sort(self): def sort(self):
"""Sorts the Journal's entries by date""" """Sorts the Journal's entries by date"""
@ -198,6 +237,36 @@ class Journal:
self.sort() self.sort()
if __name__ == "__main__": if __name__ == "__main__":
config_path = os.path.expanduser('~/.jrnl_config')
if not os.path.exists(config_path):
def autocomplete(text, state):
expansions = glob.glob(os.path.expanduser(text)+'*')
expansions = [e+"/" if os.path.isdir(e) else e for e in expansions]
expansions.append(None)
return expansions[state]
readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(autocomplete)
path_query = 'Path to your journal file (leave blank for ~/journal.txt): '
journal_path = raw_input(path_query).strip() or os.path.expanduser('~/journal.txt')
default_config['journal'] = os.path.expanduser(journal_path)
key = getpass.getpass("Enter password for journal (leave blank for no encryption): ")
if key:
default_config['encrypt'] = True
print("Journal will be encrypted.")
print("If you want to, you can store your password in .jrnl_config and will never be bothered about it again.")
open(default_config['journal'], 'a').close() # Touch to make sure it's there
with open(config_path, 'w') as f:
json.dump(default_config, f, indent=2)
config = default_config
if key:
config['key'] = key
else:
with open(config_path) as f:
config = json.load(f)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
composing = parser.add_argument_group('Composing', 'Will make an entry out of whatever follows as arguments') composing = parser.add_argument_group('Composing', 'Will make an entry out of whatever follows as arguments')
composing.add_argument('-date', dest='date', help='Date, e.g. "yesterday at 5pm"') composing.add_argument('-date', dest='date', help='Date, e.g. "yesterday at 5pm"')
@ -211,9 +280,6 @@ if __name__ == "__main__":
reading.add_argument('-json', dest='json', action="store_true", help='Returns a JSON-encoded version of the Journal') reading.add_argument('-json', dest='json', action="store_true", help='Returns a JSON-encoded version of the Journal')
args = parser.parse_args() args = parser.parse_args()
# open journal
journal = Journal(config=config)
# Guess mode # Guess mode
compose = True compose = True
if args.start_date or args.end_date or args.limit or args.json or args.strict: if args.start_date or args.end_date or args.limit or args.json or args.strict:
@ -225,12 +291,23 @@ if __name__ == "__main__":
# No text? Query # No text? Query
if compose and not args.text: if compose and not args.text:
raw = raw_input("Compose Entry: ") if config['editor']:
tmpfile = os.path.join(tempfile.gettempdir(), "jrnl")
subprocess.call(config['editor'].split() + [tmpfile])
with open(tmpfile) as f:
raw = f.read()
os.remove(tmpfile)
else:
raw = raw_input("Compose Entry: ")
if raw: if raw:
args.text = [raw] args.text = [raw]
else: else:
compose = False compose = False
# open journal
journal = Journal(config=config)
# Writing mode # Writing mode
if compose: if compose:
raw = " ".join(args.text).strip() raw = " ".join(args.text).strip()