From afcccb78c13e899f7c87b1dbdbc06bb34409e247 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Sep 2014 08:35:39 -0700 Subject: [PATCH 01/34] Fix docs typo --- docs/encryption.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/encryption.rst b/docs/encryption.rst index 37d341a8..e4a10368 100644 --- a/docs/encryption.rst +++ b/docs/encryption.rst @@ -30,7 +30,7 @@ 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 *" Manual decryption ----------------- From c81f0e0c1dd9f40fe1f286d77a3aba0565713993 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Mon, 22 Sep 2014 07:42:00 -0400 Subject: [PATCH 02/34] Added how to ignore history appending for zsh --- docs/encryption.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/encryption.rst b/docs/encryption.rst index e4a10368..8ca1faba 100644 --- a/docs/encryption.rst +++ b/docs/encryption.rst @@ -32,6 +32,11 @@ While jrnl follows best practises, true security is an illusion. Specifically, j 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 ----------------- From e9f691e3997fde872435a101f348990013c82b9c Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Thu, 25 Sep 2014 10:04:17 -0400 Subject: [PATCH 03/34] Fixed -on today option parsing --- jrnl/time.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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]) From c8e5d1ff3415aaeee9a09b3ef9258cfb38f5c03b Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 30 Sep 2014 10:16:50 -0700 Subject: [PATCH 04/34] util fixes --- jrnl/util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jrnl/util.py b/jrnl/util.py index b06113c2..946fdb90 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -71,15 +71,18 @@ 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)) + prompt(msg) return STDIN.readline().strip() def py23_read(msg=""): + prompt(msg) return STDIN.read() def yesno(prompt, default=True): @@ -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: From 17b439eba49ea155b3aff27b8ce205f87c19259b Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 30 Sep 2014 10:17:07 -0700 Subject: [PATCH 05/34] version bump --- CHANGELOG.md | 1 + jrnl/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 402ca773..d513a596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changelog ### 1.9 (July 21, 2014) +* __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/jrnl/__init__.py b/jrnl/__init__.py index 030a9d4b..062ca255 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.6' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 - 2014 Manuel Ebert' From e6cfeabc178a1922af5822fc3e429b28b0bc2e59 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 2 Oct 2014 10:28:39 -0700 Subject: [PATCH 06/34] Installation instructions for homebrew --- docs/installation.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 From 76881f66e93b715a3db1e744f3ed020338e989fb Mon Sep 17 00:00:00 2001 From: Will Barrett Date: Mon, 6 Oct 2014 16:45:25 -0400 Subject: [PATCH 07/34] Handle situation where a specified jounal is not found. --- .../data/configs/multiple_without_default.json | 16 ++++++++++++++++ features/multiple_journals.feature | 5 +++++ jrnl/cli.py | 5 +++++ 3 files changed, 26 insertions(+) create mode 100644 features/data/configs/multiple_without_default.json 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/jrnl/cli.py b/jrnl/cli.py index fe105e89..ce54c4a2 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -156,6 +156,11 @@ 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) From 1013d77173a9623836dcc1601dca4a735f97839c Mon Sep 17 00:00:00 2001 From: jfunction Date: Wed, 15 Oct 2014 14:23:03 +0200 Subject: [PATCH 08/34] Fixed a bug whereby editing a single entry resulted in the plural form being used. Now editing one entry results in "[1 entry modified]" --- jrnl/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index fe105e89..0c2f90bd 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -259,7 +259,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 From 94b53b9247aa5863579976dd1e2d74ba0d55af4d Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 21 Oct 2014 15:33:28 +0200 Subject: [PATCH 09/34] Safer temp file creation --- jrnl/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/util.py b/jrnl/util.py index 946fdb90..49a2b467 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -116,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) From 19c57ecf70a48495a1bbe9704cd30805372eef84 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 21 Oct 2014 15:35:48 +0200 Subject: [PATCH 10/34] Open journal before writing --- jrnl/cli.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index e7f7d83d..b7b980b4 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -165,6 +165,17 @@ def run(manual_args=None): 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" @@ -188,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() From acf41a153ad01c923f1afa230bde4544eda463ec Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 21 Oct 2014 18:27:56 +0200 Subject: [PATCH 11/34] Timezone parsing fix --- features/steps/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/features/steps/core.py b/features/steps/core.py index c4aa2f59..9b0679e0 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -124,10 +124,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): From 43f1166ecfe4ac18089d6ed48ab06f3d51652eed Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 11:30:06 +0100 Subject: [PATCH 12/34] Tests for writing non-unicode entries on prompt --- features/regression.feature | 7 +++++++ features/steps/core.py | 11 +++++------ 2 files changed, 12 insertions(+), 6 deletions(-) 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 9b0679e0..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 @@ -184,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 From 799ff762b2d7ccfffee0cac40b360ab9789a6004 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 11:30:11 +0100 Subject: [PATCH 13/34] Fix for writing non-unicode entries on prompt --- jrnl/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jrnl/util.py b/jrnl/util.py index 49a2b467..db8b0af7 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -79,11 +79,11 @@ def prompt(msg): def py23_input(msg=""): prompt(msg) - return STDIN.readline().strip() + return u(STDIN.readline()).strip() def py23_read(msg=""): prompt(msg) - return STDIN.read() + return u(STDIN.read()) def yesno(prompt, default=True): prompt = prompt.strip() + (" [Y/n]" if default else " [y/N]") From 474bf0a71abd72b9c713f41aeb4be5c648f3c30f Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 11:30:15 +0100 Subject: [PATCH 14/34] version bump --- CHANGELOG.md | 1 + jrnl/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d513a596..0f0379f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ 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 diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 062ca255..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.6' +__version__ = '1.9.7' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 - 2014 Manuel Ebert' From e77037b0f1ef9e107276997208d607244853bd5b Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 12:35:31 +0100 Subject: [PATCH 15/34] github_release plug on setup.py --- setup.py | 102 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 21 deletions(-) diff --git a/setup.py b/setup.py index 1578b452..2abfa6cd 100644 --- a/setup.py +++ b/setup.py @@ -51,13 +51,8 @@ except ImportError: readline_available = False -if sys.argv[-1] == 'publish': - os.system("python setup.py sdist upload") - sys.exit() - base_dir = os.path.dirname(os.path.abspath(__file__)) - def get_version(filename="jrnl/__init__.py"): with open(os.path.join(base_dir, filename)) as initfile: for line in initfile.readlines(): @@ -65,6 +60,71 @@ def get_version(filename="jrnl/__init__.py"): if m: return m.group(1) + +def get_changelog(filename="CHANGELOG.md"): + changelog = {} + current_version = None + with open(os.path.join(base_dir, filename)) as changelog_file: + for line in changelog_file.readlines(): + if line.startswith("* __"): + parts = line.strip("* ").split(" ", 1) + if len(parts) == 2: + current_version, changes = parts[0].strip("_\n"), parts[1] + changelog[current_version] = [changes.strip()] + else: + current_version = parts[0].strip("_\n") + changelog[current_version] = [] + elif line.strip() and current_version and not line.startswith("#"): + changelog[current_version].append(line.strip(" *\n")) + return changelog + +def dist_pypi(): + os.system("python setup.py sdist upload") + sys.exit() + +def dist_github(): + """Creates a release on the maebert/jrnl repository on github""" + import requests + import keyring + import getpass + version = get_version() + version_tuple = version.split(".") + changes_since_last_version = ["* __{}__: {}".format(key, "\n".join(changes)) for key, changes in get_changelog().items() if key.startswith("{}.{}".format(*version_tuple))] + changes_since_last_version = "\n".join(sorted(changes_since_last_version, reverse=True)) + payload = { + "tag_name": version, + "target_commitish": "master", + "name": version, + "body": "Changes in Version {}.{}: \n\n{}".format(version_tuple[0], version_tuple[1], changes_since_last_version) + } + print "Preparing release {}...".format(version) + username = keyring.get_password("github", "__default_user") or raw_input("Github username: ") + password = keyring.get_password("github", username) or getpass.getpass() + otp = raw_input("One Time Token: ") + response = requests.post("https://api.github.com/repos/maebert/jrnl/releases", headers={"X-GitHub-OTP": otp}, json=payload, auth=(username, password)) + if response.status_code in (403, 404): + print "Authentication error." + else: + keyring.set_password("github", "__default_user", username) + keyring.set_password("github", username, password) + if response.status_code > 299: + if "message" in response.json(): + print "Error: {}".format(response.json()['message']) + for error_dict in response.json().get('errors', []): + print "*", error_dict + else: + print "Unkown error" + print response.text + else: + print "Release created." + sys.exit() + +if sys.argv[-1] == 'publish': + dist_pypi() + +if sys.argv[-1] == 'github_release': + dist_github() + conditional_dependencies = { "pyreadline>=2.0": not readline_available and "win32" in sys.platform, "readline>=6.2": not readline_available and "win32" not in sys.platform, @@ -92,24 +152,24 @@ setup( }, long_description=__doc__, entry_points={ - 'console_scripts': [ - 'jrnl = jrnl:run', - ], + "console_scripts": [ + "jrnl = jrnl:run", + ] }, classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Topic :: Office/Business :: News/Diary', - 'Topic :: Text Processing' + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Topic :: Office/Business :: News/Diary", + "Topic :: Text Processing" ], # metadata for upload to PyPI author = "Manuel Ebert", From 73d9882b641956a8e955e38236d3a0526621503c Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 12:38:57 +0100 Subject: [PATCH 16/34] Travis update --- .travis.yml | 4 ++-- setup.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35f34a63..93c1f82b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ python: - "3.3" - "3.4" install: - - "pip install -e . --use-mirrors" - - "pip install pycrypto>=2.6 --use-mirrors" + - "pip install -e ." + - "pip install pycrypto>=2.6" - "pip install -q behave" # command to run tests script: diff --git a/setup.py b/setup.py index 2abfa6cd..0208acdf 100644 --- a/setup.py +++ b/setup.py @@ -97,26 +97,26 @@ def dist_github(): "name": version, "body": "Changes in Version {}.{}: \n\n{}".format(version_tuple[0], version_tuple[1], changes_since_last_version) } - print "Preparing release {}...".format(version) + print("Preparing release {}...".format(version)) username = keyring.get_password("github", "__default_user") or raw_input("Github username: ") password = keyring.get_password("github", username) or getpass.getpass() otp = raw_input("One Time Token: ") response = requests.post("https://api.github.com/repos/maebert/jrnl/releases", headers={"X-GitHub-OTP": otp}, json=payload, auth=(username, password)) if response.status_code in (403, 404): - print "Authentication error." + print("Authentication error.") else: keyring.set_password("github", "__default_user", username) keyring.set_password("github", username, password) if response.status_code > 299: if "message" in response.json(): - print "Error: {}".format(response.json()['message']) + print("Error: {}".format(response.json()['message'])) for error_dict in response.json().get('errors', []): - print "*", error_dict + print("*", error_dict) else: - print "Unkown error" - print response.text + print("Unkown error") + print(response.text) else: - print "Release created." + print("Release created.") sys.exit() if sys.argv[-1] == 'publish': From a43cf163954da74d7fad9892669b460e9252aabc Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Thu, 6 Nov 2014 15:36:03 -0500 Subject: [PATCH 17/34] Added basic logging feature to understand how is configuration loaded --- jrnl/cli.py | 18 ++++++++++++++++++ jrnl/util.py | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/jrnl/cli.py b/jrnl/cli.py index b7b980b4..90feda09 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -17,16 +17,19 @@ import jrnl import os import argparse import sys +import logging xdg_config = os.environ.get('XDG_CONFIG_HOME') CONFIG_PATH = os.path.join(xdg_config, "jrnl") if xdg_config else os.path.expanduser('~/.jrnl_config') PYCRYPTO = install.module_exists("Crypto") +log = logging.getLogger(__name__) def parse_args(args=None): parser = argparse.ArgumentParser() 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") + parser.add_argument('-d', '--debug', dest='debug', action='store_true', help='execute in debug mode') 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="*") @@ -90,6 +93,7 @@ def decrypt(journal, filename=None): def touch_journal(filename): """If filename does not exist, touch the file""" if not os.path.exists(filename): + log.debug('Creating journal file %s', filename) util.prompt("[Journal created at {0}]".format(filename)) open(filename, 'a').close() @@ -114,8 +118,15 @@ def update_config(config, new_config, scope, force_local=False): config.update(new_config) +def configure_logger(debug=False): + logging.basicConfig(level=logging.DEBUG if debug else logging.INFO, + format='%(levelname)-8s %(name)-12s %(message)s') + logging.getLogger('parsedatetime').setLevel(logging.INFO) # disable parsedatetime debug logging + + def run(manual_args=None): args = parse_args(manual_args) + configure_logger(args.debug) args.text = [p.decode('utf-8') if util.PY2 and not isinstance(p, unicode) else p for p in args.text] if args.version: version_str = "{0} version {1}".format(jrnl.__title__, jrnl.__version__) @@ -123,8 +134,10 @@ def run(manual_args=None): sys.exit(0) if not os.path.exists(CONFIG_PATH): + log.debug('Configuration file not found, installing jrnl...') config = install.install_jrnl(CONFIG_PATH) else: + log.debug('Reading configuration from file %s', CONFIG_PATH) config = util.load_and_fix_json(CONFIG_PATH) install.upgrade_config(config, config_path=CONFIG_PATH) @@ -132,6 +145,7 @@ def run(manual_args=None): print(util.py2encode(list_journals(config))) sys.exit(0) + log.debug('Using configuration "%s"', config) original_config = config.copy() # check if the configuration is supported by available modules if config['encrypt'] and not PYCRYPTO: @@ -151,8 +165,10 @@ def run(manual_args=None): except: pass + log.debug('Using journal "%s"', journal_name) journal_conf = config['journals'].get(journal_name) if type(journal_conf) is dict: # We can override the default config on a by-journal basis + log.debug('Updating configuration with specific jourlnal overrides %s', journal_conf) config.update(journal_conf) else: # But also just give them a string to point to the journal file config['journal'] = journal_conf @@ -163,6 +179,7 @@ def run(manual_args=None): config['journal'] = os.path.expanduser(os.path.expandvars(config['journal'])) touch_journal(config['journal']) + log.debug('Using journal path %(journal)s', config) mode_compose, mode_export = guess_mode(args, config) # open journal file or folder @@ -204,6 +221,7 @@ def run(manual_args=None): raw = " ".join(args.text).strip() if util.PY2 and type(raw) is not unicode: raw = raw.decode(sys.getfilesystemencoding()) + log.debug('Appending raw line "%s" to journal "%s"', raw, journal_name) journal.new_entry(raw) util.prompt("[Entry added to {0} journal]".format(journal_name)) journal.write() diff --git a/jrnl/util.py b/jrnl/util.py index db8b0af7..47d34314 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -13,6 +13,7 @@ import tempfile import subprocess import codecs import unicodedata +import logging PY3 = sys.version_info[0] == 3 PY2 = sys.version_info[0] == 2 @@ -22,6 +23,8 @@ STDOUT = sys.stdout TEST = False __cached_tz = None +log = logging.getLogger(__name__) + def getpass(prompt="Password: "): if not TEST: @@ -96,18 +99,22 @@ def load_and_fix_json(json_path): """ with open(json_path) as f: json_str = f.read() + log.debug('Configuration file %s read correctly', json_path) config = None try: return json.loads(json_str) except ValueError as e: + log.debug('Could not parse configuration %s: %s', json_str, e) # Attempt to fix extra , json_str = re.sub(r",[ \n]*}", "}", json_str) # Attempt to fix missing , json_str = re.sub(r"([^{,]) *\n *(\")", r"\1,\n \2", json_str) try: + log.debug('Attempting to reload automatically fixed configuration file %s', json_str) config = json.loads(json_str) with open(json_path, 'w') as f: json.dump(config, f, indent=2) + log.debug('Fixed configuration saved in file %s', json_path) prompt("[Some errors in your jrnl config have been fixed for you.]") return config except ValueError as e: From 75eaf275b47c33f5047250b8381c760e2057cc0d Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Thu, 6 Nov 2014 19:15:11 -0500 Subject: [PATCH 18/34] Added exception info to the output when it cannot be loaded --- jrnl/util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jrnl/util.py b/jrnl/util.py index 47d34314..0a2727cf 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -104,13 +104,15 @@ def load_and_fix_json(json_path): try: return json.loads(json_str) except ValueError as e: - log.debug('Could not parse configuration %s: %s', json_str, e) + log.debug('Could not parse configuration %s: %s', json_str, e, + exc_info=True) # Attempt to fix extra , json_str = re.sub(r",[ \n]*}", "}", json_str) # Attempt to fix missing , json_str = re.sub(r"([^{,]) *\n *(\")", r"\1,\n \2", json_str) try: - log.debug('Attempting to reload automatically fixed configuration file %s', json_str) + log.debug('Attempting to reload automatically fixed configuration file %s', + json_str) config = json.loads(json_str) with open(json_path, 'w') as f: json.dump(config, f, indent=2) @@ -118,6 +120,7 @@ def load_and_fix_json(json_path): prompt("[Some errors in your jrnl config have been fixed for you.]") return config except ValueError as e: + log.debug('Could not load fixed configuration: %s', e, exc_info=True) prompt("[There seems to be something wrong with your jrnl config at {0}: {1}]".format(json_path, e.message)) prompt("[Entry was NOT added to your journal]") sys.exit(1) From 44c74d509c2fbed5dd41ba22a5144b32eda815df Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Mon, 17 Nov 2014 15:36:43 +0100 Subject: [PATCH 19/34] Fixes #308 --- CHANGELOG.md | 1 + jrnl/__init__.py | 2 +- jrnl/util.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0379f6..4dd5e1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changelog ### 1.9 (July 21, 2014) +* __1.9.8__ Fixes a problem with temporary files on windows * __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 diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 29a9cef8..44bf8eac 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.7' +__version__ = '1.9.8' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 - 2014 Manuel Ebert' diff --git a/jrnl/util.py b/jrnl/util.py index db8b0af7..513ba8d7 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -116,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.mkstemp(prefix="jrnl", text=True, suffix=".txt")) + _, tmpfile = tempfile.mkstemp(prefix="jrnl", text=True, suffix=".txt") with codecs.open(tmpfile, 'w', "utf-8") as f: if template: f.write(template) From 12d34667fd5183c3d82f3a984906ca767ad1c317 Mon Sep 17 00:00:00 2001 From: Stephan Gabler Date: Tue, 18 Nov 2014 14:28:55 +0100 Subject: [PATCH 20/34] fix small typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d4bbb29f..1c0e921e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ If you find a typo or a mistake in the docs, just fix it right away and send a p Bugs ---- -They unfortunately happen. Specifically, I don't have a Windows machine to test on, so expect a few rough spots. If you found a bug, please [open a new issue](https://www.github.com/maebert/jrnl/issues/new) and describe it as well as possible. If you're a programmer and have a little time time spare, go ahead, fork the code and fix bugs you spot, it'll be much appreciated! +They unfortunately happen. Specifically, I don't have a Windows machine to test on, so expect a few rough spots. If you found a bug, please [open a new issue](https://www.github.com/maebert/jrnl/issues/new) and describe it as well as possible. If you're a programmer and have a little time to spare, go ahead, fork the code and fix bugs you spot, it'll be much appreciated! Feature requests and ideas From 270157010c7f93f8137d10529653524759a6a00b Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 29 Nov 2014 18:36:42 +0700 Subject: [PATCH 21/34] Icon update! --- docs/_themes/jrnl/static/img/favicon-152.png | Bin 3261 -> 12511 bytes docs/_themes/jrnl/static/img/favicon.ico | Bin 5558 -> 5558 bytes docs/_themes/jrnl/static/img/logo.png | Bin 2783 -> 5390 bytes docs/_themes/jrnl/static/img/logo@2x.png | Bin 5598 -> 13865 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/_themes/jrnl/static/img/favicon-152.png b/docs/_themes/jrnl/static/img/favicon-152.png index ac658d9ce73f8bddb02c753987998b52bc7325a2..539d40cc834f1c65e59b4e52eacdea1206ee3a0e 100644 GIT binary patch literal 12511 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 KtqLvc$o~M=2{qmT diff --git a/docs/_themes/jrnl/static/img/favicon.ico b/docs/_themes/jrnl/static/img/favicon.ico index 7c9c2c1e2ed5cbf83ae4302ce72f729bef91ea48..d6197b35b8e02b11fc2980ba30a11bde265f6305 100644 GIT binary patch literal 5558 zcmd^DX;YL}7VepvACUP6ewxb9n2%HLiMZgtv_PYdBq}Z_L@^@tzAu7+3obEZW@3y{ zP*FoB6L+Htri>z}=m;8hoM=&;d7g9M`*sU4RjK6Lbk(VI-*cbyJm=i*>U;0&@%+y7 zd(W6L9{4eyH~;AI{K@0-yzz#4F8hPWGZC>j5d*~YXBcs>`vk_9h#zLW*Y%9~$9wa0 z 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 diff --git a/docs/_themes/jrnl/static/img/logo.png b/docs/_themes/jrnl/static/img/logo.png index 1ea79cf1583927ca8bdbde9f8779fecfc567b72a..900ebac66c05e4e5f2838ee82ca704c0501f19e2 100644 GIT binary patch literal 5390 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 literal 2783 zcmc&$c{tQx7ypiZ$&w~JBZX{*kSvkNOm;#)OD3`uV;g1&W1G-e#t_n&sVpUfD3UFb z2-$ZT!p~OrefNIf|KI=L`#k5K=bn3>^PF?;IiGW%T9_GeaR_q&0KkQS8(4wd9wZkw z7I5#5YH9=-6Ivf}hYeg|Y!BkWGrJ%BA2g^x^k0JLUnB>ELZLvzyMfle?t$3*0d4>m zi@o&F#|!Ox-_PxmZ-57RSz8zYI2;iMH|_*yFHu4Qt!>03>!lwWX9m8`m%TuD$mq4o z7?re8HzY_fx(GQYw?kTjn%$Z#yZmRBIyJ~`LSo-@FTM(X;4hRQ8BO3%cgt%t6OqVA zI8fL`ksAvhZ1*YI-~0`a?9f*$!=+-IbMNZ2oLxlv6~REQb%;zU=}x zff|dqo_~1Pgoq)|Cns~S5BbVO&@(}+csz3&hl-FYsAyHeEP%E7vkKNCNv4#}KLZ^6)5IdG4n3oF(+hdas-rlq{?1IYqwA zVF_hUy4BI-)$F1jmKuOQ`3mGf!rea)+X(0?BmRR)xH|LE(QFkgGkchC7tl)IPw%s$c&TynfL%!&a)Hzj!=Z41ajHNUr%-x##+b!u7Y>Nq?UVM@Y#^(CLXq9Hz z2WEfBc{s9fB=%3kxa95ROl2jNXCnM&jIWTtb~F~vtc+x?V8Y~pm%jVlk)Zs%`u$ZY zLH85n;lHP1^Q8cbz;337XJZP1Hzmt{WkX?d5D6@fJ0^!Ups*;GF*Lz4E71)wA*Uyu z0J;DsyrN4?+m{bW+Okbv)P?AECi*wB3^=#Gc*d>waIVdy*w()zdkql1tit$eZ9qxw zDkqTE6W#Wp96-hsSsE5RbE`jaM-Dfg1Mb`{1PcWP=OtNAM#uI=ECkq$qS!^xtQ%-PtHr+D=_Qf3j4$J-}$jX$zeny2Cfcm<()vv^h8x=l#UmvNa|m|c;l zvo2SqLe{se-}3RF1Q@nzm(yTiyZ_E3ewz0>kzsB39_((iF}SUCM! z$`U57H2FxhQ)*9tx-+jmok--t;g-?ml@+0*paN>UGb{=cbzCsTM;{StH>M-rvT}1U zzACBiN%K}Jx{~XsQj!|`o$8~tfO(PPqL-dB#a|cN zf-`UUOryU`#0q@QywS4{XAxn6_?33g7au#TLtzS#dD3{5-_B`(PYZn@PiX9XAHgJX z-H<4U8nY-rcSDfp<%79x81Ou>SSx64+Z<~>7-HO-MPhHzdLM3Lym1+8OEx{bxEppE zEKimON?Wk`oPjAKi4{3#r|Tm3iC4J9t^f1u0?^bLX2KB|tgFguTepvWE2DxQzu;KH z4Zhj+PqwU88)!i)6%InBZ~cqQIQ$V{Xc_GF-6$|HkT+8mPjXEys~8Rm{cZ&OtoB#g zAHG;9_voLK*|)&hT7x!248xf2*B0|m3ADa2SCDOn&c2G#Hq4`E>Muf!lDM{tpoT1- z{qsmTc=esb)BYzP(FZ)Wn~U~Eriz(9{n2IW6{TS#j|e>JuLcXMmZ()A$gTeDJ^MV7 zAN2FXYKbF=W;0#HFaJgIku)Ra>XkJ2x; zRrZ)vT~ueC2(yGIRHS#8B7MGua)BePAcQ4Lk#>0a| zF>1z-w3FSWxgb+Fovx!bQ#pkJ(A`M`OWCJ%>a>&Gen;YRvVHhI&kH1q_e&jAbtM7r zr18<`=UVOcd)`&0+>hDUa=JWjpO{p0XfgELTn#>YJQuS&L)p>UDOmOPT7r>r`!mJU zl8#LWL!b5V@t7)2ZK<0_n(}HpGM!jqB&nSQ}G&3mCM@Ih#m%}p~ diff --git a/docs/_themes/jrnl/static/img/logo@2x.png b/docs/_themes/jrnl/static/img/logo@2x.png index 9cc3d76b6447c42cf7c19f859d3a7ccb89c01387..2ef28d1a3d9b091eb342870399258796135615da 100644 GIT binary patch literal 13865 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ⅇtFjiCzo&hr@cM^=3a z*d(W+L_~1H_atni#9x;MMPp|;C5`T*+U8&7&&ikvi%b+|zLx>dViz&ND|7N)&F~~& z``D-AfoghJ+LEN$6UK0tUs35V<%5Z07u7@kjGEE$KQrPBL|F`72goa3>JGgI$R}LX zvcPYJ^jL|8b!-2t$)a>VZG#r&wKAI6BN6+$JspP0+>6FX^5wHWS0q-L#1k)U65pfA zDBDQS(l^`nd;X`_4=P;RTsAKl5Jj2>#so%P#xbG59(ip#RS|ndJqFEKRa$|yyF%T~ znWvCVsv+>%4$c2-;6`%koskfG&c!u5}HaUApG zReZ89%GYzT)4`5J@?u(BYKPpocqJu7S$!hx;$4GSkDiO36n+H$gHP$Zsr(YRr5`

kKVg&1=NMS$A)mbasg$Qb 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 literal 5598 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 Og4C2W70VSYg8m0xQL!Tc From 3323db3e2a6b31fc2f1d8c2334ea90455c0b5b49 Mon Sep 17 00:00:00 2001 From: seinfield Date: Sun, 7 Dec 2014 02:55:49 -0500 Subject: [PATCH 22/34] corrects issue #316 --- jrnl/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index 90feda09..014c8842 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -256,14 +256,14 @@ def run(manual_args=None): elif args.encrypt is not False: encrypt(journal, filename=args.encrypt) # Not encrypting to a separate file: update config! - if not args.encrypt: + if not args.encrypt or args.encrypt == config['journal]: update_config(original_config, {"encrypt": True}, journal_name, force_local=True) install.save_config(original_config, config_path=CONFIG_PATH) elif args.decrypt is not False: decrypt(journal, filename=args.decrypt) # Not decrypting to a separate file: update config! - if not args.decrypt: + if not args.decrypt or args.decrypt == config['journal']: update_config(original_config, {"encrypt": False}, journal_name, force_local=True) install.save_config(original_config, config_path=CONFIG_PATH) From 10235b77ce6391f0c561d4331f199478bb968b86 Mon Sep 17 00:00:00 2001 From: seinfield Date: Sun, 7 Dec 2014 03:00:41 -0500 Subject: [PATCH 23/34] corrected typo --- jrnl/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index 014c8842..35764734 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -256,7 +256,7 @@ def run(manual_args=None): elif args.encrypt is not False: encrypt(journal, filename=args.encrypt) # Not encrypting to a separate file: update config! - if not args.encrypt or args.encrypt == config['journal]: + if not args.encrypt or args.encrypt == config['journal']: update_config(original_config, {"encrypt": True}, journal_name, force_local=True) install.save_config(original_config, config_path=CONFIG_PATH) From ef0227cf8954e8289a9dbf4a74439d55993cb112 Mon Sep 17 00:00:00 2001 From: Nik V Date: Thu, 11 Dec 2014 21:21:18 +0800 Subject: [PATCH 24/34] Fix bug with dayone journals on linux --- jrnl/DayOneJournal.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index 6d91a9bd..dfcf01d6 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -58,7 +58,10 @@ class DayOne(Journal.Journal): if not hasattr(entry, "uuid"): entry.uuid = uuid.uuid1().hex utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple())) - filename = os.path.join(self.config['journal'], "entries", entry.uuid + ".doentry") + # make sure to upper() the uuid since uuid.uuid1 returns a lowercase string by default + # while dayone uses uppercase by default. On fully case preserving filesystems (e.g. + # linux) this results in duplicated entries when we save the file + filename = os.path.join(self.config['journal'], "entries", entry.uuid.upper() + ".doentry") entry_plist = { 'Creation Date': utc_time, 'Starred': entry.starred if hasattr(entry, 'starred') else False, From b99c82f9beb7e9f791c7a487cba808360fe2683e Mon Sep 17 00:00:00 2001 From: Nik V Date: Thu, 11 Dec 2014 21:28:04 +0800 Subject: [PATCH 25/34] Don't die if you specify a directory (DayOne journal) to install --- jrnl/install.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jrnl/install.py b/jrnl/install.py index dcb83601..138cb826 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -87,7 +87,8 @@ def install_jrnl(config_path='~/.jrnl_config'): except OSError: pass - open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there + if not os.path.isdir(path): # if it's a directory and exists (e.g. a DayOne journal, let it be) + open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there # Write config to ~/.jrnl_conf with open(config_path, 'w') as f: From 4a89f92f978ee18f8b205fe5c02dd919579c0448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Galv=C3=A3o?= Date: Wed, 8 Jul 2015 20:40:08 +0100 Subject: [PATCH 26/34] readme.md: homebrew instructions --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4ea95240..00f71138 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,6 @@ Or, if you want the option to encrypt your journal, pip install jrnl[encrypted] +Alternatively, on OS X with [Homebrew](http://brew.sh/) installed: + + brew install jrnl From 5dccceff78faeb2917d15cb896523c4559278a17 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Mon, 28 Dec 2015 21:58:08 -0800 Subject: [PATCH 27/34] Don't build 2.6 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 93c1f82b..175fe9f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: - - "2.6" - "2.7" - "3.3" - "3.4" From 54d36297a8bdb2e93c24d1a7763934df0f86ffb6 Mon Sep 17 00:00:00 2001 From: David Silva Date: Fri, 6 May 2016 19:02:53 +0100 Subject: [PATCH 28/34] Change README url to point to actual documentation (#423) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00f71138..aa7bcab9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ jrnl [![Build Status](http://img.shields.io/travis/maebert/jrnl.svg?style=flat)](https://travis-ci.org/maebert/jrnl) [![Downloads](http://img.shields.io/pypi/dm/jrnl.svg?style=flat)](https://pypi.python.org/pypi/jrnl/) [![Version](http://img.shields.io/pypi/v/jrnl.svg?style=flat)](https://pypi.python.org/pypi/jrnl/) ==== -_For news on updates or to get help, [read the docs](http://maebert.github.io/jrnl), follow [@maebert](https://twitter.com/maebert) or [submit an issue](https://github.com/maebert/jrnl/issues/new) on Github._ +_For news on updates or to get help, [read the docs](http://maebert.github.io/jrnl/overview.html), follow [@maebert](https://twitter.com/maebert) or [submit an issue](https://github.com/maebert/jrnl/issues/new) on Github._ *jrnl* is a simple journal application for your command line. Journals are stored as human readable plain text files - you can put them into a Dropbox folder for instant syncing and you can be assured that your journal will still be readable in 2050, when all your fancy iPad journal applications will long be forgotten. From 976a6faaa8ad635980e37aabd66d04a4dd8a8b9d Mon Sep 17 00:00:00 2001 From: Andrew Sauber Date: Tue, 17 May 2016 13:35:56 -0400 Subject: [PATCH 29/34] Minor Documentation Fixes (#425) * Update installation.rst * minor fixes to encryption.rst instruct user to set `HISTIGNORE` while maintaining their existing `HISTIGNORE` minor whitespace change --- docs/encryption.rst | 4 ++-- docs/installation.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/encryption.rst b/docs/encryption.rst index 8ca1faba..4c967231 100644 --- a/docs/encryption.rst +++ b/docs/encryption.rst @@ -30,7 +30,7 @@ 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`` :: - HISTIGNORE="jrnl *" + HISTIGNORE="$HISTIGNORE:jrnl *" If you are using zsh instead of bash, you can get the same behaviour adding this to your ``zshrc`` :: @@ -40,7 +40,7 @@ If you are using zsh instead of bash, you can get the same behaviour adding this Manual decryption ----------------- -Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. So, to decrypt a journal file in python, run:: +Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. So, to decrypt a journal file in python, run :: import hashlib, Crypto.Cipher key = hashlib.sha256(my_password).digest() diff --git a/docs/installation.rst b/docs/installation.rst index 9cadd548..7a57865c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -24,7 +24,7 @@ to install the dependencies for encrypting journals as well. Installing the encryption library, `pycrypto`, requires a `gcc` compiler. For this reason, jrnl will not install `pycrypto` unless explicitly told so like this. You can `install PyCrypto manually `_ first or install it with ``pip install pycrypto`` if you have a `gcc` compiler. - Also note that when using zsh, you the correct syntax is ``pip install "jrnl[encrypted]"`` (note the quotes). + Also note that when using zsh, the correct syntax is ``pip install "jrnl[encrypted]"`` (note the quotes). The first time you run ``jrnl`` you will be asked where your journal file should be created and whether you wish to encrypt it. From cf6bc9c051399f959ae93161a7b7e8e11bb9e514 Mon Sep 17 00:00:00 2001 From: doozr Date: Thu, 14 Jul 2016 22:24:48 +0100 Subject: [PATCH 30/34] Do not crash if subdirs found inside Day One dir (#430) --- jrnl/DayOneJournal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index dfcf01d6..cf246a90 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -30,6 +30,8 @@ class DayOne(Journal.Journal): 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: + if os.path.isdir(filename): + continue with open(filename, 'rb') as plist_entry: try: dict_entry = plistlib.readPlist(plist_entry) From b2ddd22e504eb03739672a012480454cf10f578c Mon Sep 17 00:00:00 2001 From: Andrew Sauber Date: Sat, 20 Aug 2016 13:55:09 -0400 Subject: [PATCH 31/34] add fully functional decryption script to docs (#431) --- docs/encryption.rst | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/encryption.rst b/docs/encryption.rst index 4c967231..694b26fe 100644 --- a/docs/encryption.rst +++ b/docs/encryption.rst @@ -40,13 +40,29 @@ If you are using zsh instead of bash, you can get the same behaviour adding this Manual decryption ----------------- -Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. So, to decrypt a journal file in python, run :: +Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. Here's a Python script that you can use to decrypt your journal:: + + #!/usr/bin/env python3 + + import argparse + from Crypto.Cipher import AES + import getpass + import hashlib + import sys + + parser = argparse.ArgumentParser() + parser.add_argument("filepath", help="journal file to decrypt") + args = parser.parse_args() + + pwd = getpass.getpass() + key = hashlib.sha256(pwd.encode('utf-8')).digest() + + with open(args.filepath, 'rb') as f: + ciphertext = f.read() + + crypto = AES.new(key, AES.MODE_CBC, ciphertext[:16]) + plain = crypto.decrypt(ciphertext[16:]) + plain = plain.strip(plain[-1:]) + plain = plain.decode("utf-8") + print(plain) - import hashlib, Crypto.Cipher - key = hashlib.sha256(my_password).digest() - with open("my_journal.txt") as f: - cipher = f.read() - crypto = AES.new(key, AES.MODE_CBC, iv = cipher[:16]) - plain = crypto.decrypt(cipher[16:]) - plain = plain.strip(plain[-1]) - plain = plain.decode("utf-8") From 4ba577db33efb73237286f5b9bb9bdb4717e1e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radom=C3=ADr=20Bos=C3=A1k?= Date: Wed, 4 Jan 2017 19:24:03 +0100 Subject: [PATCH 32/34] Fix failing behave tests (#447) The keyring package broke backward compatibility in version 8.0 by moving some keyring backends to another package, keyrings.alt, as documented in the changelog [1]. This change broke behave tests which were trying to use keyring.backends.file.PlaintextKeyring - no longer existing in keyring package. This commit adds the keyrings.alt package as dependency so that the PlaintextKeyring class can be used. [1] https://pythonhosted.org/keyring/history.html#id22 --- features/steps/core.py | 3 ++- setup.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/features/steps/core.py b/features/steps/core.py index d0ea8460..e3e0100b 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -5,7 +5,8 @@ import os import codecs import json import keyring -keyring.set_keyring(keyring.backends.file.PlaintextKeyring()) +import keyrings +keyring.set_keyring(keyrings.alt.file.PlaintextKeyring()) try: from io import StringIO except ImportError: diff --git a/setup.py b/setup.py index 0208acdf..851170bb 100644 --- a/setup.py +++ b/setup.py @@ -146,6 +146,7 @@ setup( "six>=1.6.1", "tzlocal>=1.1", "keyring>=3.3", + "keyrings.alt>=1.3", ] + [p for p, cond in conditional_dependencies.items() if cond], extras_require = { "encrypted": "pycrypto>=2.6" From a7b98f28e157d61bbd8efafed949af4e4ec6a5eb Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 4 Jan 2017 12:02:53 -0800 Subject: [PATCH 33/34] Update CONTRIBUTING.md --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c0e921e..4a342df2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,9 @@ Contributing If you use jrnl, you can totally make my day by just saying "thanks for the code" or by [tweeting about jrnl](https://twitter.com/intent/tweet?text=Write+your+memoirs+on+the+command+line.+Like+a+boss.+%23jrnl&url=http%3A%2F%2Fmaebert.github.io%2Fjrnl&via=maebert). It's your chance to make a programmer happy today! If you have a minute or two, let me know what you use jrnl for and how, it'll help me to make it even better. If you blog about jrnl, I'll send you a post card! +> # Important: +> ### Please develop new features against the `2.0-rc1` branch. PRs to the `master` branch will not get merged until version 2.0 is released. + Docs & Typos ------------ From ba34ba2e58980c796ec4dfad77610b7c57a90f94 Mon Sep 17 00:00:00 2001 From: Paul Liu Date: Mon, 13 Feb 2017 00:03:42 -0500 Subject: [PATCH 34/34] Update iA Writer bundle id (#464) --- docs/recipes.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index e1aef9a9..ddf6c9cd 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -108,10 +108,17 @@ On OS X, you can use the fabulous `iA Writer `_ to .. code-block:: javascript - "editor": "open -b jp.informationarchitects.WriterForMacOSX -Wn" + "editor": "open -b pro.writer.mac -Wn" What does this do? ``open -b ...`` opens a file using the application identified by the bundle identifier (a unique string for every app out there). ``-Wn`` tells the application to wait until it's closed before passing back control, and to use a new instance of the application. +If the ``pro.writer.mac`` bundle identifier is not found on your system, you can find the right string to use by inspecting iA Writer's ``Info.plist`` file in your shell: + +.. code-block:: sh + + $ grep -A 1 CFBundleIdentifier /Applications/iA\ Writer.app/Contents/Info.plist + CFBundleIdentifier + pro.writer.mac Notepad++ on Windows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~