diff --git a/CHANGELOG.md b/CHANGELOG.md
index 402ca773..0f0379f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ Changelog
### 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.4__ Fixed: Order of journal entries in file correct after --edit'ing
* __1.9.3__ Fixed: Tags at the beginning of lines
diff --git a/docs/encryption.rst b/docs/encryption.rst
index 37d341a8..8ca1faba 100644
--- a/docs/encryption.rst
+++ b/docs/encryption.rst
@@ -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 `_ 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
-----------------
diff --git a/docs/installation.rst b/docs/installation.rst
index 46df9b3a..9cadd548 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -6,7 +6,11 @@ Getting started
Installation
------------
-Install *jrnl* using pip ::
+On OS X, the easiest way to install *jrnl* is using `Homebrew `_ ::
+
+ brew install jrnl
+
+On other platforms, install *jrnl* using pip ::
pip install jrnl
diff --git a/features/data/configs/multiple_without_default.json b/features/data/configs/multiple_without_default.json
new file mode 100644
index 00000000..042e843a
--- /dev/null
+++ b/features/data/configs/multiple_without_default.json
@@ -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": "@"
+}
diff --git a/features/multiple_journals.feature b/features/multiple_journals.feature
index 0510209b..7c77ff72 100644
--- a/features/multiple_journals.feature
+++ b/features/multiple_journals.feature
@@ -34,3 +34,8 @@ Feature: Multiple journals
Then journal "ideas" should not exist
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
+
+ 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."
diff --git a/features/regression.feature b/features/regression.feature
index 1672afb4..f975a4b1 100644
--- a/features/regression.feature
+++ b/features/regression.feature
@@ -59,3 +59,10 @@ Feature: Zapped bugs should stay dead.
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
| 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"
diff --git a/features/steps/core.py b/features/steps/core.py
index c4aa2f59..d0ea8460 100644
--- a/features/steps/core.py
+++ b/features/steps/core.py
@@ -2,9 +2,8 @@ from behave import *
from jrnl import cli, Journal, util
from dateutil import parser as date_parser
import os
-import sys
+import codecs
import json
-import pytz
import keyring
keyring.set_keyring(keyring.backends.file.PlaintextKeyring())
try:
@@ -30,7 +29,7 @@ def _parse_args(command):
def read_journal(journal_name="default"):
with open(cli.CONFIG_PATH) as 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()
return journal
@@ -57,7 +56,7 @@ def run_with_input(context, command, inputs=None):
buffer = StringIO(text.strip())
util.STDIN = buffer
try:
- cli.run(args or None)
+ cli.run(args)
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code
@@ -66,7 +65,7 @@ def run_with_input(context, command, inputs=None):
def run(context, command):
args = _parse_args(command)
try:
- cli.run(args or None)
+ cli.run(args)
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code
@@ -124,10 +123,8 @@ def check_output(context, text=None):
def check_output_time_inline(context, text):
out = context.stdout_capture.getvalue()
local_tz = tzlocal.get_localzone()
- utc_time = date_parser.parse(text)
- date = utc_time + local_tz._utcoffset
- local_date = date.strftime("%Y-%m-%d %H:%M")
- assert local_date in out, local_date
+ local_time = date_parser.parse(text).astimezone(local_tz).strftime("%Y-%m-%d %H:%M")
+ assert local_time in out, local_time
@then('the output should contain "{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('journal "{journal_name}" should have {number:d} entries')
@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)
assert len(journal.entries) == number
diff --git a/jrnl/__init__.py b/jrnl/__init__.py
index 030a9d4b..29a9cef8 100644
--- a/jrnl/__init__.py
+++ b/jrnl/__init__.py
@@ -8,7 +8,7 @@ jrnl is a simple journal application for your command line.
from __future__ import absolute_import
__title__ = 'jrnl'
-__version__ = '1.9.5'
+__version__ = '1.9.7'
__author__ = 'Manuel Ebert'
__license__ = 'MIT License'
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'
diff --git a/jrnl/cli.py b/jrnl/cli.py
index fe105e89..b7b980b4 100644
--- a/jrnl/cli.py
+++ b/jrnl/cli.py
@@ -156,10 +156,26 @@ def run(manual_args=None):
config.update(journal_conf)
else: # But also just give them a string to point to the journal file
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']))
touch_journal(config['journal'])
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?
if "win32" in sys.platform:
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
@@ -183,17 +199,6 @@ def run(manual_args=None):
else:
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
if mode_compose:
raw = " ".join(args.text).strip()
@@ -259,7 +264,7 @@ def run(manual_args=None):
if num_deleted:
prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
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:
util.prompt("[{0}]".format(", ".join(prompts).capitalize()))
journal.entries += other_entries
diff --git a/jrnl/time.py b/jrnl/time.py
index 531293de..378d4c92 100644
--- a/jrnl/time.py
+++ b/jrnl/time.py
@@ -48,7 +48,10 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None):
return None
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:
date = datetime(*date[:6])
diff --git a/jrnl/util.py b/jrnl/util.py
index b06113c2..db8b0af7 100644
--- a/jrnl/util.py
+++ b/jrnl/util.py
@@ -71,16 +71,19 @@ def py2encode(s):
def prompt(msg):
"""Prints a message to the std err stream defined in util."""
+ if not msg:
+ return
if not msg.endswith("\n"):
msg += "\n"
STDERR.write(u(msg))
def py23_input(msg=""):
- STDERR.write(u(msg))
- return STDIN.readline().strip()
+ prompt(msg)
+ return u(STDIN.readline()).strip()
def py23_read(msg=""):
- return STDIN.read()
+ prompt(msg)
+ return u(STDIN.read())
def yesno(prompt, default=True):
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:
json_str = f.read()
- config = fixed = None
+ config = None
try:
return json.loads(json_str)
except ValueError as e:
@@ -113,7 +116,7 @@ def load_and_fix_json(json_path):
sys.exit(1)
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:
if template:
f.write(template)