mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-11 17:18:30 +02:00
Compare commits
19 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b45e52f743 | ||
|
474bf0a71a | ||
|
799ff762b2 | ||
|
43f1166ecf | ||
|
acf41a153a | ||
|
19c57ecf70 | ||
|
94b53b9247 | ||
|
9603f7d3ad | ||
|
cde55ad540 | ||
|
1013d77173 | ||
|
76881f66e9 | ||
|
e6cfeabc17 | ||
|
17b439eba4 | ||
|
c8e5d1ff34 | ||
|
bd6edffc81 | ||
|
e9f691e399 | ||
|
05e63dc76a | ||
|
c81f0e0c1d | ||
|
afcccb78c1 |
11 changed files with 78 additions and 31 deletions
|
@ -4,6 +4,8 @@ Changelog
|
||||||
|
|
||||||
### 1.9 (July 21, 2014)
|
### 1.9 (July 21, 2014)
|
||||||
|
|
||||||
|
* __1.9.7__ Fixes writing non-ascii entries on the prompt
|
||||||
|
* __1.9.6__ Fuzzy time parsing improvements (thanks to @pcarranza)
|
||||||
* __1.9.5__ Multi-word tags for DayOne Journals
|
* __1.9.5__ Multi-word tags for DayOne Journals
|
||||||
* __1.9.4__ Fixed: Order of journal entries in file correct after --edit'ing
|
* __1.9.4__ Fixed: Order of journal entries in file correct after --edit'ing
|
||||||
* __1.9.3__ Fixed: Tags at the beginning of lines
|
* __1.9.3__ Fixed: Tags at the beginning of lines
|
||||||
|
|
|
@ -30,7 +30,12 @@ A note on security
|
||||||
|
|
||||||
While jrnl follows best practises, true security is an illusion. Specifically, jrnl will leave traces in your memory and your shell history -- it's meant to keep journals secure in transit, for example when storing it on an `untrusted <http://techcrunch.com/2014/04/09/condoleezza-rice-joins-dropboxs-board/>`_ services such as Dropbox. If you're concerned about security, disable history logging for journal in your ``.bashrc`` ::
|
While jrnl follows best practises, true security is an illusion. Specifically, jrnl will leave traces in your memory and your shell history -- it's meant to keep journals secure in transit, for example when storing it on an `untrusted <http://techcrunch.com/2014/04/09/condoleezza-rice-joins-dropboxs-board/>`_ services such as Dropbox. If you're concerned about security, disable history logging for journal in your ``.bashrc`` ::
|
||||||
|
|
||||||
HISTINGNORE="jrnl *"
|
HISTIGNORE="jrnl *"
|
||||||
|
|
||||||
|
If you are using zsh instead of bash, you can get the same behaviour adding this to your ``zshrc`` ::
|
||||||
|
|
||||||
|
setopt HIST_IGNORE_SPACE
|
||||||
|
alias jrnl=" jrnl"
|
||||||
|
|
||||||
Manual decryption
|
Manual decryption
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
@ -6,7 +6,11 @@ Getting started
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Install *jrnl* using pip ::
|
On OS X, the easiest way to install *jrnl* is using `Homebrew <http://brew.sh/>`_ ::
|
||||||
|
|
||||||
|
brew install jrnl
|
||||||
|
|
||||||
|
On other platforms, install *jrnl* using pip ::
|
||||||
|
|
||||||
pip install jrnl
|
pip install jrnl
|
||||||
|
|
||||||
|
|
16
features/data/configs/multiple_without_default.json
Normal file
16
features/data/configs/multiple_without_default.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"default_hour": 9,
|
||||||
|
"timeformat": "%Y-%m-%d %H:%M",
|
||||||
|
"linewrap": 80,
|
||||||
|
"encrypt": false,
|
||||||
|
"editor": "",
|
||||||
|
"default_minute": 0,
|
||||||
|
"highlight": true,
|
||||||
|
"password": "",
|
||||||
|
"journals": {
|
||||||
|
"simple": "features/journals/simple.journal",
|
||||||
|
"work": "features/journals/work.journal",
|
||||||
|
"ideas": "features/journals/nothing.journal"
|
||||||
|
},
|
||||||
|
"tagsymbols": "@"
|
||||||
|
}
|
|
@ -34,3 +34,8 @@ Feature: Multiple journals
|
||||||
Then journal "ideas" should not exist
|
Then journal "ideas" should not exist
|
||||||
When we run "jrnl ideas 23 july 2012: sell my junk on ebay and make lots of money"
|
When we run "jrnl ideas 23 july 2012: sell my junk on ebay and make lots of money"
|
||||||
Then journal "ideas" should have 1 entry
|
Then journal "ideas" should have 1 entry
|
||||||
|
|
||||||
|
Scenario: Gracefully handle a config without a default journal
|
||||||
|
Given we use the config "multiple_without_default.json"
|
||||||
|
When we run "jrnl fork this repo and fix something"
|
||||||
|
Then we should see the message "You have not specified a journal. Either provide a default journal in your config file, or specify one of your journals on the command line."
|
||||||
|
|
|
@ -59,3 +59,10 @@ Feature: Zapped bugs should stay dead.
|
||||||
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
|
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
|
||||||
| I'm feeling sore because I forgot to stretch.
|
| I'm feeling sore because I forgot to stretch.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Scenario: Writing an entry at the prompt with non-ascii characters
|
||||||
|
# https://github.com/maebert/jrnl/issues/295
|
||||||
|
Given we use the config "basic.json"
|
||||||
|
When we run "jrnl" and enter "Crème brûlée & Mötorhead"
|
||||||
|
Then we should get no error
|
||||||
|
and the journal should contain "Crème brûlée & Mötorhead"
|
||||||
|
|
|
@ -2,9 +2,8 @@ from behave import *
|
||||||
from jrnl import cli, Journal, util
|
from jrnl import cli, Journal, util
|
||||||
from dateutil import parser as date_parser
|
from dateutil import parser as date_parser
|
||||||
import os
|
import os
|
||||||
import sys
|
import codecs
|
||||||
import json
|
import json
|
||||||
import pytz
|
|
||||||
import keyring
|
import keyring
|
||||||
keyring.set_keyring(keyring.backends.file.PlaintextKeyring())
|
keyring.set_keyring(keyring.backends.file.PlaintextKeyring())
|
||||||
try:
|
try:
|
||||||
|
@ -30,7 +29,7 @@ def _parse_args(command):
|
||||||
def read_journal(journal_name="default"):
|
def read_journal(journal_name="default"):
|
||||||
with open(cli.CONFIG_PATH) as config_file:
|
with open(cli.CONFIG_PATH) as config_file:
|
||||||
config = json.load(config_file)
|
config = json.load(config_file)
|
||||||
with open(config['journals'][journal_name]) as journal_file:
|
with codecs.open(config['journals'][journal_name], 'r', 'utf-8') as journal_file:
|
||||||
journal = journal_file.read()
|
journal = journal_file.read()
|
||||||
return journal
|
return journal
|
||||||
|
|
||||||
|
@ -57,7 +56,7 @@ def run_with_input(context, command, inputs=None):
|
||||||
buffer = StringIO(text.strip())
|
buffer = StringIO(text.strip())
|
||||||
util.STDIN = buffer
|
util.STDIN = buffer
|
||||||
try:
|
try:
|
||||||
cli.run(args or None)
|
cli.run(args)
|
||||||
context.exit_status = 0
|
context.exit_status = 0
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
context.exit_status = e.code
|
context.exit_status = e.code
|
||||||
|
@ -66,7 +65,7 @@ def run_with_input(context, command, inputs=None):
|
||||||
def run(context, command):
|
def run(context, command):
|
||||||
args = _parse_args(command)
|
args = _parse_args(command)
|
||||||
try:
|
try:
|
||||||
cli.run(args or None)
|
cli.run(args)
|
||||||
context.exit_status = 0
|
context.exit_status = 0
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
context.exit_status = e.code
|
context.exit_status = e.code
|
||||||
|
@ -124,10 +123,8 @@ def check_output(context, text=None):
|
||||||
def check_output_time_inline(context, text):
|
def check_output_time_inline(context, text):
|
||||||
out = context.stdout_capture.getvalue()
|
out = context.stdout_capture.getvalue()
|
||||||
local_tz = tzlocal.get_localzone()
|
local_tz = tzlocal.get_localzone()
|
||||||
utc_time = date_parser.parse(text)
|
local_time = date_parser.parse(text).astimezone(local_tz).strftime("%Y-%m-%d %H:%M")
|
||||||
date = utc_time + local_tz._utcoffset
|
assert local_time in out, local_time
|
||||||
local_date = date.strftime("%Y-%m-%d %H:%M")
|
|
||||||
assert local_date in out, local_date
|
|
||||||
|
|
||||||
@then('the output should contain "{text}"')
|
@then('the output should contain "{text}"')
|
||||||
def check_output_inline(context, text):
|
def check_output_inline(context, text):
|
||||||
|
@ -186,7 +183,7 @@ def config_var(context, key, value, journal=None):
|
||||||
@then('the journal should have {number:d} entry')
|
@then('the journal should have {number:d} entry')
|
||||||
@then('journal "{journal_name}" should have {number:d} entries')
|
@then('journal "{journal_name}" should have {number:d} entries')
|
||||||
@then('journal "{journal_name}" should have {number:d} entry')
|
@then('journal "{journal_name}" should have {number:d} entry')
|
||||||
def check_journal_content(context, number, journal_name="default"):
|
def check_num_entries(context, number, journal_name="default"):
|
||||||
journal = open_journal(journal_name)
|
journal = open_journal(journal_name)
|
||||||
assert len(journal.entries) == number
|
assert len(journal.entries) == number
|
||||||
|
|
||||||
|
|
|
@ -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.9.5'
|
__version__ = '1.9.7'
|
||||||
__author__ = 'Manuel Ebert'
|
__author__ = 'Manuel Ebert'
|
||||||
__license__ = 'MIT License'
|
__license__ = 'MIT License'
|
||||||
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
|
||||||
|
|
29
jrnl/cli.py
29
jrnl/cli.py
|
@ -156,10 +156,26 @@ def run(manual_args=None):
|
||||||
config.update(journal_conf)
|
config.update(journal_conf)
|
||||||
else: # But also just give them a string to point to the journal file
|
else: # But also just give them a string to point to the journal file
|
||||||
config['journal'] = journal_conf
|
config['journal'] = journal_conf
|
||||||
|
|
||||||
|
if config['journal'] is None:
|
||||||
|
util.prompt("You have not specified a journal. Either provide a default journal in your config file, or specify one of your journals on the command line.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
config['journal'] = os.path.expanduser(os.path.expandvars(config['journal']))
|
config['journal'] = os.path.expanduser(os.path.expandvars(config['journal']))
|
||||||
touch_journal(config['journal'])
|
touch_journal(config['journal'])
|
||||||
mode_compose, mode_export = guess_mode(args, config)
|
mode_compose, mode_export = guess_mode(args, config)
|
||||||
|
|
||||||
|
# open journal file or folder
|
||||||
|
if os.path.isdir(config['journal']):
|
||||||
|
if config['journal'].strip("/").endswith(".dayone") or \
|
||||||
|
"entries" in os.listdir(config['journal']):
|
||||||
|
journal = DayOneJournal.DayOne(**config)
|
||||||
|
else:
|
||||||
|
util.prompt("[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
journal = Journal.Journal(journal_name, **config)
|
||||||
|
|
||||||
# How to quit writing?
|
# How to quit writing?
|
||||||
if "win32" in sys.platform:
|
if "win32" in sys.platform:
|
||||||
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
|
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
|
||||||
|
@ -183,17 +199,6 @@ def run(manual_args=None):
|
||||||
else:
|
else:
|
||||||
mode_compose = False
|
mode_compose = False
|
||||||
|
|
||||||
# open journal file or folder
|
|
||||||
if os.path.isdir(config['journal']):
|
|
||||||
if config['journal'].strip("/").endswith(".dayone") or \
|
|
||||||
"entries" in os.listdir(config['journal']):
|
|
||||||
journal = DayOneJournal.DayOne(**config)
|
|
||||||
else:
|
|
||||||
util.prompt("[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
journal = Journal.Journal(journal_name, **config)
|
|
||||||
|
|
||||||
# Writing mode
|
# Writing mode
|
||||||
if mode_compose:
|
if mode_compose:
|
||||||
raw = " ".join(args.text).strip()
|
raw = " ".join(args.text).strip()
|
||||||
|
@ -259,7 +264,7 @@ def run(manual_args=None):
|
||||||
if num_deleted:
|
if num_deleted:
|
||||||
prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
|
prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
|
||||||
if num_edited:
|
if num_edited:
|
||||||
prompts.append("{0} {1} modified".format(num_edited, "entry" if num_deleted == 1 else "entries"))
|
prompts.append("{0} {1} modified".format(num_edited, "entry" if num_edited == 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
|
||||||
|
|
|
@ -48,7 +48,10 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if flag is 1: # Date found, but no time. Use the default time.
|
if flag is 1: # Date found, but no time. Use the default time.
|
||||||
date = datetime(*date[:3], hour=default_hour or 0, minute=default_minute or 0)
|
date = datetime(*date[:3],
|
||||||
|
hour=23 if inclusive else default_hour or 0,
|
||||||
|
minute=59 if inclusive else default_minute or 0,
|
||||||
|
second=59 if inclusive else 0)
|
||||||
else:
|
else:
|
||||||
date = datetime(*date[:6])
|
date = datetime(*date[:6])
|
||||||
|
|
||||||
|
|
13
jrnl/util.py
13
jrnl/util.py
|
@ -71,16 +71,19 @@ def py2encode(s):
|
||||||
|
|
||||||
def prompt(msg):
|
def prompt(msg):
|
||||||
"""Prints a message to the std err stream defined in util."""
|
"""Prints a message to the std err stream defined in util."""
|
||||||
|
if not msg:
|
||||||
|
return
|
||||||
if not msg.endswith("\n"):
|
if not msg.endswith("\n"):
|
||||||
msg += "\n"
|
msg += "\n"
|
||||||
STDERR.write(u(msg))
|
STDERR.write(u(msg))
|
||||||
|
|
||||||
def py23_input(msg=""):
|
def py23_input(msg=""):
|
||||||
STDERR.write(u(msg))
|
prompt(msg)
|
||||||
return STDIN.readline().strip()
|
return u(STDIN.readline()).strip()
|
||||||
|
|
||||||
def py23_read(msg=""):
|
def py23_read(msg=""):
|
||||||
return STDIN.read()
|
prompt(msg)
|
||||||
|
return u(STDIN.read())
|
||||||
|
|
||||||
def yesno(prompt, default=True):
|
def yesno(prompt, default=True):
|
||||||
prompt = prompt.strip() + (" [Y/n]" if default else " [y/N]")
|
prompt = prompt.strip() + (" [Y/n]" if default else " [y/N]")
|
||||||
|
@ -93,7 +96,7 @@ def load_and_fix_json(json_path):
|
||||||
"""
|
"""
|
||||||
with open(json_path) as f:
|
with open(json_path) as f:
|
||||||
json_str = f.read()
|
json_str = f.read()
|
||||||
config = fixed = None
|
config = None
|
||||||
try:
|
try:
|
||||||
return json.loads(json_str)
|
return json.loads(json_str)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
@ -113,7 +116,7 @@ def load_and_fix_json(json_path):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def get_text_from_editor(config, template=""):
|
def get_text_from_editor(config, template=""):
|
||||||
tmpfile = os.path.join(tempfile.mktemp(prefix="jrnl"))
|
_, tmpfile = os.path.join(tempfile.mkstemp(prefix="jrnl", text=True, suffix=".txt"))
|
||||||
with codecs.open(tmpfile, 'w', "utf-8") as f:
|
with codecs.open(tmpfile, 'w', "utf-8") as f:
|
||||||
if template:
|
if template:
|
||||||
f.write(template)
|
f.write(template)
|
||||||
|
|
Loading…
Add table
Reference in a new issue