mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-17 19:48:31 +02:00
Merge remote-tracking branch 'upstream/master' into parse-dates
This commit is contained in:
commit
92258f0b08
9 changed files with 65 additions and 30 deletions
|
@ -4,6 +4,11 @@ Changelog
|
||||||
|
|
||||||
### 1.7 (December 22, 2013)
|
### 1.7 (December 22, 2013)
|
||||||
|
|
||||||
|
* __1.7.15__ More unicode fixes
|
||||||
|
* __1.7.14__ Fix for trailing whitespaces (eg. when writing markdown code block)
|
||||||
|
* __1.7.13__ Fix for UTF-8 in DayOne journals
|
||||||
|
* __1.7.12__ Fixes a bug where filtering by tags didn't work for DayOne journals
|
||||||
|
* __1.7.11__ `-ls` will list all available journals (Thanks @jtan189)
|
||||||
* __1.7.10__ Supports `-3` as a shortcut for `-n 3` and updates to tzlocal 1.1
|
* __1.7.10__ Supports `-3` as a shortcut for `-n 3` and updates to tzlocal 1.1
|
||||||
* __1.7.9__ Fix a logic bug so that jrnl -h and jrnl -v are possible even if jrnl not configured yet.
|
* __1.7.9__ Fix a logic bug so that jrnl -h and jrnl -v are possible even if jrnl not configured yet.
|
||||||
* __1.7.8__ Upgrade to parsedatetime 1.2
|
* __1.7.8__ Upgrade to parsedatetime 1.2
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -8,7 +8,7 @@ clean:
|
||||||
rm -rf _build
|
rm -rf _build
|
||||||
rm -rf _sources
|
rm -rf _sources
|
||||||
rm -rf _static
|
rm -rf _static
|
||||||
rm *.html
|
rm -f *.html
|
||||||
|
|
||||||
|
|
||||||
# Build GitHub Page from docs
|
# Build GitHub Page from docs
|
||||||
|
|
|
@ -7,6 +7,14 @@ Basic Usage
|
||||||
|
|
||||||
We intentionally break a convention on command line arguments: all arguments starting with a `single dash` will `filter` your journal before viewing it, and can be combined arbitrarily. Arguments with a `double dash` will control how your journal is displayed or exported and are mutually exclusive (ie. you can only specify one way to display or export your journal at a time).
|
We intentionally break a convention on command line arguments: all arguments starting with a `single dash` will `filter` your journal before viewing it, and can be combined arbitrarily. Arguments with a `double dash` will control how your journal is displayed or exported and are mutually exclusive (ie. you can only specify one way to display or export your journal at a time).
|
||||||
|
|
||||||
|
Listing Journals
|
||||||
|
----------------
|
||||||
|
|
||||||
|
You can list the journals accessible by jrnl::
|
||||||
|
|
||||||
|
jrnl -ls
|
||||||
|
|
||||||
|
The journals displayed correspond to those specified in the jrnl configuration file.
|
||||||
|
|
||||||
Composing Entries
|
Composing Entries
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
@ -33,8 +33,8 @@ Feature: DayOne Ingetration
|
||||||
When we run "jrnl --tags"
|
When we run "jrnl --tags"
|
||||||
Then the output should be
|
Then the output should be
|
||||||
"""
|
"""
|
||||||
work : 1
|
@work : 1
|
||||||
play : 1
|
@play : 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Scenario: Saving tags from a DayOne Journal
|
Scenario: Saving tags from a DayOne Journal
|
||||||
|
@ -43,6 +43,14 @@ Feature: DayOne Ingetration
|
||||||
and we run "jrnl --tags"
|
and we run "jrnl --tags"
|
||||||
Then the output should be
|
Then the output should be
|
||||||
"""
|
"""
|
||||||
work : 2
|
@work : 2
|
||||||
play : 1
|
@play : 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: Filtering by tags from a DayOne Journal
|
||||||
|
Given we use the config "dayone.json"
|
||||||
|
When we run "jrnl @work"
|
||||||
|
Then the output should be
|
||||||
|
"""
|
||||||
|
2013-05-17 11:39 This entry has tags!
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,12 +5,13 @@ import re
|
||||||
import textwrap
|
import textwrap
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class Entry:
|
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("\n ")
|
self.title = title.rstrip("\n ")
|
||||||
self.body = body.strip("\n ")
|
self.body = body.rstrip("\n ")
|
||||||
self.tags = self.parse_tags()
|
self.tags = self.parse_tags()
|
||||||
self.starred = starred
|
self.starred = starred
|
||||||
self.modified = False
|
self.modified = False
|
||||||
|
@ -27,12 +28,11 @@ class Entry:
|
||||||
title = date_str + " " + self.title
|
title = date_str + " " + self.title
|
||||||
if self.starred:
|
if self.starred:
|
||||||
title += " *"
|
title += " *"
|
||||||
body = self.body.strip()
|
|
||||||
|
|
||||||
return u"{title}{sep}{body}\n".format(
|
return u"{title}{sep}{body}\n".format(
|
||||||
title=title,
|
title=title,
|
||||||
sep="\n" if self.body else "",
|
sep="\n" if self.body else "",
|
||||||
body=body
|
body=self.body
|
||||||
)
|
)
|
||||||
|
|
||||||
def pprint(self, short=False):
|
def pprint(self, short=False):
|
||||||
|
@ -47,11 +47,11 @@ class Entry:
|
||||||
initial_indent="| ",
|
initial_indent="| ",
|
||||||
subsequent_indent="| ",
|
subsequent_indent="| ",
|
||||||
drop_whitespace=False)
|
drop_whitespace=False)
|
||||||
for line in self.body.strip().splitlines()
|
for line in self.body.rstrip().splitlines()
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
title = date_str + " " + self.title
|
title = date_str + " " + self.title
|
||||||
body = self.body.strip()
|
body = self.body
|
||||||
|
|
||||||
# Suppress bodies that are just blanks and new lines.
|
# Suppress bodies that are just blanks and new lines.
|
||||||
has_body = len(self.body) > 20 or not all(char in (" ", "\n") for char in self.body)
|
has_body = len(self.body) > 20 or not all(char in (" ", "\n") for char in self.body)
|
||||||
|
@ -71,7 +71,7 @@ class Entry:
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, Entry) \
|
if not isinstance(other, Entry) \
|
||||||
or self.title.strip() != other.title.strip() \
|
or self.title.strip() != other.title.strip() \
|
||||||
or self.body.strip() != other.body.strip() \
|
or self.body.rstrip() != other.body.rstrip() \
|
||||||
or self.date != other.date \
|
or self.date != other.date \
|
||||||
or self.starred != other.starred:
|
or self.starred != other.starred:
|
||||||
return False
|
return False
|
||||||
|
@ -82,8 +82,8 @@ class Entry:
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'title': self.title.strip(),
|
'title': self.title,
|
||||||
'body': self.body.strip(),
|
'body': self.body,
|
||||||
'date': self.date.strftime("%Y-%m-%d"),
|
'date': self.date.strftime("%Y-%m-%d"),
|
||||||
'time': self.date.strftime("%H:%M"),
|
'time': self.date.strftime("%H:%M"),
|
||||||
'starred': self.starred
|
'starred': self.starred
|
||||||
|
@ -91,8 +91,8 @@ class Entry:
|
||||||
|
|
||||||
def to_md(self):
|
def to_md(self):
|
||||||
date_str = self.date.strftime(self.journal.config['timeformat'])
|
date_str = self.date.strftime(self.journal.config['timeformat'])
|
||||||
body_wrapper = "\n\n" if self.body.strip() else ""
|
body_wrapper = "\n\n" if self.body else ""
|
||||||
body = body_wrapper + self.body.strip()
|
body = body_wrapper + self.body
|
||||||
space = "\n"
|
space = "\n"
|
||||||
md_head = "###"
|
md_head = "###"
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import pytz
|
||||||
import uuid
|
import uuid
|
||||||
import tzlocal
|
import tzlocal
|
||||||
|
|
||||||
|
|
||||||
class Journal(object):
|
class Journal(object):
|
||||||
def __init__(self, name='default', **kwargs):
|
def __init__(self, name='default', **kwargs):
|
||||||
self.config = {
|
self.config = {
|
||||||
|
@ -125,9 +126,9 @@ class Journal(object):
|
||||||
current_entry = None
|
current_entry = None
|
||||||
|
|
||||||
for line in journal_txt.splitlines():
|
for line in journal_txt.splitlines():
|
||||||
|
line = line.rstrip()
|
||||||
try:
|
try:
|
||||||
# try to parse line as date => new entry begins
|
# try to parse line as date => new entry begins
|
||||||
line = line.strip()
|
|
||||||
new_date = datetime.strptime(line[:date_length], self.config['timeformat'])
|
new_date = datetime.strptime(line[:date_length], self.config['timeformat'])
|
||||||
|
|
||||||
# parsing successful => save old entry and create new one
|
# parsing successful => save old entry and create new one
|
||||||
|
@ -351,7 +352,7 @@ 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
|
||||||
sep = re.search("[\n!?.]+", raw)
|
sep = re.search("\n|[\?.]+", raw)
|
||||||
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
|
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (raw, "")
|
||||||
starred = False
|
starred = False
|
||||||
if not date:
|
if not date:
|
||||||
|
@ -390,6 +391,7 @@ class Journal(object):
|
||||||
entry.modified = not any(entry == old_entry for old_entry in self.entries)
|
entry.modified = not any(entry == old_entry for old_entry in self.entries)
|
||||||
self.entries = mod_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):
|
||||||
|
@ -414,7 +416,7 @@ class DayOne(Journal):
|
||||||
title, body = (raw[:sep.end()], raw[sep.end():]) if sep else (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 = 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 = [self.config['tagsymbols'][0] + tag for tag in dict_entry.get("Tags", [])]
|
||||||
self.entries.append(entry)
|
self.entries.append(entry)
|
||||||
self.sort()
|
self.sort()
|
||||||
|
|
||||||
|
@ -442,7 +444,7 @@ class DayOne(Journal):
|
||||||
def editable_str(self):
|
def editable_str(self):
|
||||||
"""Turns the journal into a string of entries that can be edited
|
"""Turns the journal into a string of entries that can be edited
|
||||||
manually and later be parsed with eslf.parse_editable_str."""
|
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])
|
return u"\n".join([u"# {0}\n{1}".format(e.uuid, e.__unicode__()) for e in self.entries])
|
||||||
|
|
||||||
def parse_editable_str(self, edited):
|
def parse_editable_str(self, edited):
|
||||||
"""Parses the output of self.editable_str and updates it's entries."""
|
"""Parses the output of self.editable_str and updates it's entries."""
|
||||||
|
@ -458,7 +460,7 @@ class DayOne(Journal):
|
||||||
|
|
||||||
for line in edited.splitlines():
|
for line in edited.splitlines():
|
||||||
# try to parse line as UUID => new entry begins
|
# try to parse line as UUID => new entry begins
|
||||||
line = line.strip()
|
line = line.rstrip()
|
||||||
m = re.match("# *([a-f0-9]+) *$", line.lower())
|
m = re.match("# *([a-f0-9]+) *$", line.lower())
|
||||||
if m:
|
if m:
|
||||||
if current_entry:
|
if current_entry:
|
||||||
|
@ -502,4 +504,3 @@ class DayOne(Journal):
|
||||||
self._deleted_entries = [e for e in self.entries if e.uuid not in edited_uuids]
|
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]
|
self.entries[:] = [e for e in self.entries if e.uuid in edited_uuids]
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ jrnl is a simple journal application for your command line.
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
__title__ = 'jrnl'
|
__title__ = 'jrnl'
|
||||||
__version__ = '1.7.10'
|
__version__ = '1.7.15'
|
||||||
__author__ = 'Manuel Ebert'
|
__author__ = 'Manuel Ebert'
|
||||||
__license__ = 'MIT License'
|
__license__ = 'MIT License'
|
||||||
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
||||||
|
|
21
jrnl/cli.py
21
jrnl/cli.py
|
@ -26,6 +26,7 @@ PYCRYPTO = install.module_exists("Crypto")
|
||||||
def parse_args(args=None):
|
def parse_args(args=None):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('-v', '--version', dest='version', action="store_true", help="prints version information and exits")
|
parser.add_argument('-v', '--version', dest='version', action="store_true", help="prints version information and exits")
|
||||||
|
parser.add_argument('-ls', dest='ls', action="store_true", help="displays accessible journals")
|
||||||
|
|
||||||
composing = parser.add_argument_group('Composing', 'To write an entry simply write it on the command line, e.g. "jrnl yesterday at 1pm: Went to the gym."')
|
composing = parser.add_argument_group('Composing', 'To write an entry simply write it on the command line, e.g. "jrnl yesterday at 1pm: Went to the gym."')
|
||||||
composing.add_argument('text', metavar='', nargs="*")
|
composing.add_argument('text', metavar='', nargs="*")
|
||||||
|
@ -58,7 +59,7 @@ def guess_mode(args, config):
|
||||||
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)):
|
||||||
# Any sign of displaying stuff?
|
# Any sign of displaying stuff?
|
||||||
compose = False
|
compose = False
|
||||||
elif args.text and all(word[0] in config['tagsymbols'] for word in " ".join(args.text).split()):
|
elif args.text and all(word[0] in config['tagsymbols'] for word in u" ".join(args.text).split()):
|
||||||
# No date and only tags?
|
# No date and only tags?
|
||||||
compose = False
|
compose = False
|
||||||
|
|
||||||
|
@ -87,6 +88,14 @@ def touch_journal(filename):
|
||||||
util.prompt("[Journal created at {0}]".format(filename))
|
util.prompt("[Journal created at {0}]".format(filename))
|
||||||
open(filename, 'a').close()
|
open(filename, 'a').close()
|
||||||
|
|
||||||
|
def list_journals(config):
|
||||||
|
"""List the journals specified in the configuration file"""
|
||||||
|
|
||||||
|
sep = "\n"
|
||||||
|
journal_list = sep.join(config['journals'])
|
||||||
|
|
||||||
|
return journal_list
|
||||||
|
|
||||||
def update_config(config, new_config, scope, force_local=False):
|
def update_config(config, new_config, scope, force_local=False):
|
||||||
"""Updates a config dict with new values - either global if scope is None
|
"""Updates a config dict with new values - either global if scope is None
|
||||||
or config['journals'][scope] is just a string pointing to a journal file,
|
or config['journals'][scope] is just a string pointing to a journal file,
|
||||||
|
@ -113,6 +122,10 @@ def run(manual_args=None):
|
||||||
config = util.load_and_fix_json(CONFIG_PATH)
|
config = util.load_and_fix_json(CONFIG_PATH)
|
||||||
install.upgrade_config(config, config_path=CONFIG_PATH)
|
install.upgrade_config(config, config_path=CONFIG_PATH)
|
||||||
|
|
||||||
|
if args.ls:
|
||||||
|
print(util.py2encode(list_journals(config)))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
original_config = config.copy()
|
original_config = config.copy()
|
||||||
# check if the configuration is supported by available modules
|
# check if the configuration is supported by available modules
|
||||||
if config['encrypt'] and not PYCRYPTO:
|
if config['encrypt'] and not PYCRYPTO:
|
||||||
|
@ -147,7 +160,7 @@ def run(manual_args=None):
|
||||||
"entries" in os.listdir(config['journal']):
|
"entries" in os.listdir(config['journal']):
|
||||||
journal = Journal.DayOne(**config)
|
journal = Journal.DayOne(**config)
|
||||||
else:
|
else:
|
||||||
util.prompt("[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
|
util.prompt(u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
journal = Journal.Journal(journal_name, **config)
|
journal = Journal.Journal(journal_name, **config)
|
||||||
|
@ -228,8 +241,8 @@ def run(manual_args=None):
|
||||||
num_deleted = old_num_entries - len(journal)
|
num_deleted = old_num_entries - len(journal)
|
||||||
num_edited = len([e for e in journal.entries if e.modified])
|
num_edited = len([e for e in journal.entries if e.modified])
|
||||||
prompts = []
|
prompts = []
|
||||||
if num_deleted: prompts.append("{0} entries deleted".format(num_deleted))
|
if num_deleted: prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
|
||||||
if num_edited: prompts.append("{0} entries modified".format(num_edited))
|
if num_edited: prompts.append("{0} {1} modified".format(num_edited, "entry" if num_deleted == 1 else "entries"))
|
||||||
if prompts:
|
if prompts:
|
||||||
util.prompt("[{0}]".format(", ".join(prompts).capitalize()))
|
util.prompt("[{0}]".format(", ".join(prompts).capitalize()))
|
||||||
journal.entries += other_entries
|
journal.entries += other_entries
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -61,7 +61,7 @@ def get_version(filename="jrnl/__init__.py"):
|
||||||
conditional_dependencies = {
|
conditional_dependencies = {
|
||||||
"pyreadline>=2.0": "win32" in sys.platform,
|
"pyreadline>=2.0": "win32" in sys.platform,
|
||||||
"colorama>=0.2.5": "win32" in sys.platform,
|
"colorama>=0.2.5": "win32" in sys.platform,
|
||||||
"argparse==1.2.1": sys.version.startswith("2.6")
|
"argparse>=1.1.0": sys.version.startswith("2.6")
|
||||||
}
|
}
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
Loading…
Add table
Reference in a new issue