mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
Merge 651f9e5213
into 5b5570f41e
This commit is contained in:
commit
f44545d1c0
1 changed files with 98 additions and 21 deletions
107
jrnl.py
107
jrnl.py
|
@ -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:
|
||||||
|
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: ")
|
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()
|
||||||
|
|
Loading…
Add table
Reference in a new issue