mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
commit
01ab0fee83
11 changed files with 232 additions and 73 deletions
|
@ -1,6 +1,13 @@
|
|||
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.6__ -v prints the current version, also better strings for windows users. Furthermore, jrnl/jrnl.py moved to jrnl/cli.py
|
||||
|
|
|
@ -6,12 +6,12 @@ Advanced Usage
|
|||
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``
|
||||
paths to your journal files
|
||||
- ``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``
|
||||
if ``true``, encrypts your journal using AES.
|
||||
- ``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.
|
||||
|
||||
Instead of all entries being in a single file, each entry will live in a separate `plist` file.
|
||||
|
||||
Multiple journal files
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
-------------------
|
||||
|
||||
::
|
||||
|
||||
jrnl --short
|
||||
|
||||
Will only display the date and title of each entry.
|
||||
|
|
|
@ -15,5 +15,9 @@ Optionally, your journal can be encrypted using the `256-bit AES <http://en.wiki
|
|||
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.
|
||||
|
||||
|
|
|
@ -83,5 +83,39 @@ the last five entries containing both ``@pineapple`` **and** ``@lubricant``. You
|
|||
|
||||
.. 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).
|
||||
|
||||
|
|
|
@ -13,3 +13,10 @@ Feature: Zapped bugs should stay dead.
|
|||
When we run "jrnl Herro"
|
||||
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"
|
||||
|
||||
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."
|
||||
|
|
|
@ -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(),
|
||||
|
|
160
jrnl/Journal.py
160
jrnl/Journal.py
|
@ -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
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line.
|
|||
"""
|
||||
|
||||
__title__ = 'jrnl'
|
||||
__version__ = '1.6.6'
|
||||
__version__ = '1.7.1'
|
||||
__author__ = 'Manuel Ebert'
|
||||
__license__ = 'MIT License'
|
||||
__copyright__ = 'Copyright 2013 Manuel Ebert'
|
||||
|
|
44
jrnl/cli.py
44
jrnl/cli.py
|
@ -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__":
|
||||
|
|
19
jrnl/util.py
19
jrnl/util.py
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue