From 20e4ca2e14c854cbab7c5890e1d9497c5c29143a Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Sep 2014 08:35:39 -0700 Subject: [PATCH 01/41] 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 00015abf952b35b6d09d166bd6e14b2b365139bb Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Mon, 22 Sep 2014 07:42:00 -0400 Subject: [PATCH 02/41] 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 3edb28790e8c44a0ddd0c79291c5973f7502b338 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Thu, 25 Sep 2014 10:04:17 -0400 Subject: [PATCH 03/41] 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 0af00be1bac903baf7e323c2b97a73cfe6504542 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 30 Sep 2014 10:16:50 -0700 Subject: [PATCH 04/41] 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 9310b60ed052d2c5deb85bd7ede09d961a2a2dac Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 30 Sep 2014 10:17:07 -0700 Subject: [PATCH 05/41] 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 c13029c6682b3d5ec54fcede4e7ebbcab5375a71 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 2 Oct 2014 10:28:39 -0700 Subject: [PATCH 06/41] 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 8fd0e2f62dd190ebe646918fcd76c223948f9f85 Mon Sep 17 00:00:00 2001 From: Will Barrett Date: Mon, 6 Oct 2014 16:45:25 -0400 Subject: [PATCH 07/41] 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 c64bf402fd840bedee72cb0c2dfb785d192a813c Mon Sep 17 00:00:00 2001 From: jfunction Date: Wed, 15 Oct 2014 14:23:03 +0200 Subject: [PATCH 08/41] 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 733009e28232a4b16304719352e1d53136b081df Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 21 Oct 2014 15:33:28 +0200 Subject: [PATCH 09/41] 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 4256052e90fdaf00917addf7ef30c506a4041165 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 21 Oct 2014 15:35:48 +0200 Subject: [PATCH 10/41] 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 5b5c7cb6e8e59754b44ba3bea698e3a2af23d6a1 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 21 Oct 2014 18:27:56 +0200 Subject: [PATCH 11/41] 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 0f754f97c1d95337d90a2b0bec99e27fdafaf1a4 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 11:30:06 +0100 Subject: [PATCH 12/41] 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 bbf7f73a3fe228dd6c0a2df4b1d8c31868fbf502 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 11:30:11 +0100 Subject: [PATCH 13/41] 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 33cb12d64ff8bc3f518bee883b635598446378fc Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 11:30:15 +0100 Subject: [PATCH 14/41] 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 1b98b3a3e55eefb202d937a6d0a42baf4af5eb80 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 12:35:31 +0100 Subject: [PATCH 15/41] 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 fbecc6d062ac80c52722c54634baacf01e3ec299 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 6 Nov 2014 12:38:57 +0100 Subject: [PATCH 16/41] 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 155b8e8f723252b2f248e96de3fba6f48d59e003 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Thu, 6 Nov 2014 15:36:03 -0500 Subject: [PATCH 17/41] 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 2511785dd45524d9cecdbd6822d07222cbb2acd6 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Date: Thu, 6 Nov 2014 19:15:11 -0500 Subject: [PATCH 18/41] 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 e3329716c1bac5085f951dc487da4a2aed6f1d5a Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Mon, 17 Nov 2014 15:36:43 +0100 Subject: [PATCH 19/41] 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 6c390dafb471ca4e519f1d1b615aa8c539985a97 Mon Sep 17 00:00:00 2001 From: Stephan Gabler Date: Tue, 18 Nov 2014 14:28:55 +0100 Subject: [PATCH 20/41] 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 511952e20308ad9943f141e6d9549677371df0ef Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sat, 29 Nov 2014 18:36:42 +0700 Subject: [PATCH 21/41] 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 zcmW+-cQl;O_a>r5+bGdlL`fpids`to5s3s*R*4>6bT;bhs|L|~d=kCa)mJA7(b-jl zu)ST#e-8!O?VF{K zo9DJ6_I|CYN>n|`u}?%KnyvlfxsgBdQHW`+1>%zh{EFy)ZchM_R`^{9`uh&A>$P-B zAH=2}rhXXPPS4ZMi0eL283l*F-u@4_CFoi9Izz{1?7`s6*W2|rV@&G0#bj|2kK#&Y z!x+g^HmGA5wC-#W-4_X}YVs>k4w>Ry>$(!F>~q@4`@7b)u-0LGJiEJc_P4`t6CQeW zFgA9N6hYdI{G30;{TPwsTQWNw*vaD9D(8bWGrIF@q%QZ%vm{Gk>-R1 zFIMNBH)32_$xye$K&9?o_mq3le0{JZ!xWAbMHbkA^)f*3iH+#W?U(Im&?~D}jF|Od z*76t0R3~cff$jy*-t@`0=Hfd7l14f?*Uk!1O`2b)#a>z{8Fx?W3+}*X-xF11f@oyknR6wZiIIvZ}8b{ z9;l*@R0qcnSaYR&q{QjZM0L|QA;^JBkS|ts4+n+IltmKrO#=`}H^CXvN%H@cl#l;r z3o7&n;(Jtd@ZXEIgr^?48Obt^Ab5_fK3+@jycK9OJU}Z!s3vOS{4E-TY9iXZd784q zMx_)^f6PTOIykRXc!J`MiEbxRGYVXdF#W=9m?gUs?h|vPV+IFsXTMV1ocnugVHJyl z$KO?Y@-t3;jAuimoQ_Lv==>0ErE)0QWI~Lcm%oc0tKl{4Fsgk$_nhN}sZ4Y0>vvY8 z_AvL~vgsJJ{;1ue5he=_o=Ck-74z*zRZ0yMzft^A%%0nR3Efh2yTn+jn(s*-Vo8KTV;i?N$)cW7y7JRD zAQ%#3H90?|-<-@mwy@^@-!hMIrgPQh13gEK3?n|tA6ViCQu9Z=>wHG9z)D=Pzs_W` zK$n`p6tTAF#6di#i8>_of4Hc%=TU!&iEZpqA!&AMy`mbcPA3CjOzRY>$Rk zp>ODQi5b_L0(zH^1J||?!sQ97;#eBRuU4?^@;Fy!F&gdvD)J64F!}J|$3c2w)T*cc z5lUlh%F)T@+V&(`0!by!UoO<9Q4y^7=gye1X&`x(?C{Jb4Wp3<`=`+8^zReob=BYf znr&)t{nvB`t>Mjig-H>XOGE)=Z5Sw-);V$ii!< zxTumg=HKtG^RtELC~_8=NO;qdn>D%AE|=#&gx-&#fi6$cIjvqdiLPco5U(Y4-8dcR z9G7;vAL-U869Sje!`@B{4K+*j2czOw7Y!Jzw5#oRe%n)h5X>J)gWeNh7NyY>A<>Cu zw;XB3=~l@h6f+a^O!I+=PUf12p*(J{pJaaFdryyoJ3Ni6{iyEz#DGhe9MrQ@_~NsW z0D|!Vx{}2>6(m}@Y}l5XMta52nT=T2e`nh>Y}hdSOWpz_(UO;XRK1t(yYR!z zR^j^EXRA)LY<6lnYqAq_?sKS(c=a!W1Xz0Hr<6~72ug1LxSX4$ux&Ehi+uz4AM&ja znSPVJMwVLG#6RUk|SMjByY8m6_ z_Q)a$N_PlNN;Mv5w;u;Nq07=&@WaWkLJrjS2P^!_UEJCg^ZW)E)8U=A#_h*JfKujy zrEKAwt$CltMB~!I!NK!Us+zJ6_imXk%y$c?pQ*{2PTnyl@9~f0DRSy!Q8g#EJ)kQG zp9$+lWmRid&jd7xdfmjig;p41b!JvJHIW@3EVG5yDN&Ou4s#zH!LTX(Rea+Q@c9i>tvp4klBf?@(Zq3r>A0g>~pY z9r0PO0#pRS&s25*Yi78KfE#RRtrlB(e992pe(dT1=NIwr*Su{rgDf%SFUh&mFms@` zBn8IF)*w7Stnp?NOz$&m?539^BAe)dG)U|@^2_G9hIoX?AD=e3nt`(12nQXrmj%j` z9!$*7yI+Q@cd|wYp+AUjOD!K9P9E#I#zG8Zw9> zj-Y?a-PWTJ|Anjhh~fG0!kEFa4q+D*qTqUyNHyC8ZWRbQY^l}}{3x${a{^h<@4onM zHY2@!1iHk2_xIxR`%|~yv5qke+=n0PF@uAw+jkl=%Y=^Zf_5THW~v~e~1NBHS^EE$FP$$UShg_tvYEt0zm-@Zo!E&tD}))x2u7Ht{HsmMI-8^bH)+kmJG2A+ zmmNTZb$r?}zegkkx}Lpd@z2vz&BFoG%nimBcGu0h@o3kQ>AfGA z3ivv{+xTGwD#o`l$yrl=%%-{V$62@Zc2kaG{|4J&E3|aGusEDKL0b?>;X1%*>!@#8IuSEG2sEw)eaE1t81`%Eaq##~MNs+K zQFQ1{3e*rk%n4iM9hsNzFp%xzGTVs88+Pp8;pr@r=~Iy$raWde;qJ#h9Gb+O@o6H! zsGshcrf63ei!OP57RZ?|#ovtEc!PSywd3-R02Muf4#lw&;nErNntorL5^IRy8j(U$ zG1dM`9`kS}+<)1QpC%1WDOic*T8WncOO9+Y=1Z9z_-uN*LeXPxE0hl}N4I$*TwKT> zGl!gaJ_QePIoA)C@pnA$`h3~}_lDns%Zps?pPJbN@YJNGmrJ9_dgZ=-lykU!b^UOm zVzs6-+p_teq#murjx5RvnKMB6fnM+@Gv(BnfY1lq3Hu3d7HDhudUVwGAY?XZIgr_` z%!5yno`6=eUG|n-)n+=s;kpCss+}&p7{rw-z?q4eo{ap50&gsv{QS^;s^y=a*>72P zCvG&Q-(U_!z*`0I#kL{#Uk!)WJd|6t25lDxpmnXfdfN^q3Q4&s)&4@-dVvd&3y#EJ zL)yR4lK<~k%?#@=(h?Fp&x=Y-1%+TyLCn@CaH5II9}C^!?|Y>>roE(S-$Bk;EL{a>)@j|g}mF$P(L+U zhd|N2+h+z{dP*MJ1c81KZk}ZO#puz`$PvR<>36nOHmls=N`fI4RRFM$AJ@hd2mY~o zFD56OWZ0-yT?s(K>cQ_zI&X%!S_E*>DA=f1u67t`X*=j}0aQvlnH*eAJgtLM^ZSD7 zIEg8G9H)pZh=h|VRF}==@r4RdG1$u;Eq1}8Vaj+%uLpF018haM*^?Nq_N+pA5byQr z36ePGV{!(L#o?YG@eQ9l5=hmu?~eNlcOU3z`X5@>hOIv;M*! zJv`}ugy1lP)$2K#y>l}gAdvpdU-TjCvd1d%}8 z63tC_OOI5mxdXtMj=dOsBBkY&H{u!lVw|P7L-hz}vraIDW-__FiGGgoL57r* z_-9Ihb%TdjnTM?Xh(MKu0Mx_oZ*=CY@D}JPbF1gYk2Ao(3!K_oFAzF3=zjUG5&su_ z&;I*xl%xTc?GFPfEQLDw;yLjP*KwkJL#m}dhXD3tBoh zgpZC{gro}+`;Rz#clCo~{Lyov>XARaGVYWqbQpI_3z)0GgYc28dK#y&7HOTt%(;lB zmrlCZq3+V(338~1f4>cyvO=T?qmYfEUk#@PM*y@tl6W$Tl@sI+iTQ}ixi}bFrhT^( zX{H**c7Q}jzUGIz2gWcW+=3lpi(2^IiJM|0aCgV%6`hF&<54HBF8c2C zSZJ9io*hqOZPv@N0K&Bp{u>UqMm+Cb68Y`TWf;?aWUtxZp*#qi0P4J1oly-DPcqis#srp-P|qmT?Q>mGR=Ie&ft z6nGfgRW0=xI3yO9uIKgeLcZ<3g^+bL($0=(>Rxi#49zlyUh~hqUj*Ck+>tJvNpYJ2xze-;IS~M%xpB-LFjiPAYz)5!U__ zc8Y{Da%-K~rE}{EflwH`_&1+{+g-#~0DQxJKJ570U2cljRddXh#Q##|9g5B&olNbD zvEl888ncvnEBn5ELE1ksO|X!LPaNjp)`24ElBFKsO;07KR*t{G=8lt87aAQrPXQlZ z`z90pkCus8&h}epCHPq&dCI_NWa(XU{ck=0hPl_spLFWJo#?*}f`CCk1W1(NiJ6c5 zKKbJe+E#=QDMUg}F~-sEYXCB6bfUxyy3-2{Nl$!MoZHDo9KD1X>tEJ8?ySFlp9Js< zu#0S;@+&SG7M4h;+Xk;O`eWF3!FsGFg^Oq=KRWQhy)%y}f3q{-3FtQ3H?hB&0@}yP zvYMT|9WG)LTQ<8|it_GLEhggY?i%@I`j`;hk;HRP!$L#b_I`>aEHyOc1Ivl{XFm(} zXf^?(B~nUWcxDEDngg85Q}s_34WvNQqT4sh0y*vxGviH!-un?0PgSh zkqq)ouCi)=id(+Yvwkc7YgOMNZl5b*+|lp>*R1Ms+(MbrysE?RO*s3L-E8i1kabM#W_-7S<1V|B#|_skRKLf5`xG z^1cuIXCFJ)%<8}@Cj4!6i*40ZRq0Q+XZywsNP|g{DCKdWw0j09>~O3A5?r*fMq|0#O%xhx9PHa zpd4vWU8TI5>@Ps>!2`^nI3W_5e=#I7)kzFqOPK!9L_h$8LqBMUtxeKJ@%mAsWrVvo zPglE#h|o&gy>S>``|tC=t| z9lnYal6$j#R6pnMDGuE?!K74YrMhCDFf+-AiQGV`1^O-LW;c|WYT8l2ZFnMZ?H2#6 z+wvigYj(j&6Ww?_{GJ#oQubHO)jHCL znRS-Q8}RD>4|A^z6{%F>9w6SB(vNz1HC|dh>G*>BNo7+=Wz3NZIodouo?PT2Tp*8wV%k_;gq$09v(~uqj8)1q2_;OTTaZ9ne`9N?Q*QM zLj~7thhE&7mHa3gsa&aQp(bXz*&xeCnqN$Z?)T{NvgOBceDK5B-eh8|k4#a3j(}oP zg8J_Od=b$0A+E;pW);r$CeqW-UfD_AkisDQ-pm3(i5dQ@fD&X2Q-UNO8k?r%sjwnJYu!_?;*kY{a7JUj9>RlDn#AdUi>2Z^=X zZsi*wy`M?&_fJp5`%CBFpVCqMN=J(V@i+$B&8I70OL9dit5_@5-Xggh-8#vuMw9x{ zrlg5&|Ia$s<8DRN@*q449l^9KO8DsGMMxVI30mxw8k$(|PE%jl^PgsFN_~Fvw7<`c zcSl+At$)m@)uKQ`ODKzdST6m>cUUlz#iC{f!oWV9#R;AqRC6DM!RQfftf3{ex0!^l z*{7f-EW)_Ihs(xULRch0aGpfR@@uQ@%}zg1xj)-HhJufMvDi#@(2ACXMu3t1Bdl*$ zD={L(EgJcsoY-FS`qlLoIKL0%wMy`U!RR*7|j<&7beYgNe zia;>;sKvO+7*hJukFB1`b?R0#qNN3l_VweS>gm#LqwkH$peU8;n^Q9BtCG$J9hd1XPKWQA~5%M3uqG2;30$Y zR*c{|7*PY8{xF}-ZuV+uFltbaGAvcwRD_dywg9%>)_6i4bRTH5Pn&b67-ftjfTmC8u}J=+Uc!QiE3Dx|7H$PR+C=8$b=AByxZ;xo)0EjRG7Ntdbxi& zbySmD3R5YFrwhLWEZ5cB({1)5d=Np10fJvF<`W_NDGY>-tsl6~pB#rvD~2449G7mD zL;NjVKI~vSq!#z{L6fdS;HYS~$nWj!0Olg&W~WJ4vTp2{Tljeb6?cV!{^f~|VdVaH zO|**DIW*;-{+72fYf_l-0Q#dE`P<#Tb% z*H`Xd{~nC{{O^F=RknGG6g0?skq?O?hh6#&`>g~++IQzQPp`-a7Y?ih%`D%)yb304 zHrkJT=|4OXaz0_D+p0qXP&;m)AwwF{i_SIR3+QyYMld#WI;LqJLK+$xRlz`kT*57IMP1bUr#vMfIvzt5Ei;uJqMXD5XZB@oL=A@H4SllO}pz+ z2phtA5Wve2y~!Jhs}v%A%FzhHxVY>MWRBPP;1fu)4@v!lcbt<1x6Ew^Iokhg!q?lT z%Apa0SdWbWhi8$I#qt90N9Uq7?*59(oG-=H-eOfgBZz?M*jaxOJHLljfPKmP{VdBQ z)X8Z5QaF_T>rhJMjNzS^AVQ31Jw`3433GO}1!eD2$QX&2AgT~XeI!2^nXnL8vL-%u za~+XCDH?0|vBV{=eCV^$kB270T1)Z=;N5KR!5*;40l4p}R`dtdUv*_YhtXRiXMtf6 z!cv*eo$VADoLY0dMB56c#{K%~cFe0xt2fIr&@!YmVPB}rrd&t+Gk=so(DWad^?f#H zXoF9E?$+^jZ9RO)=nSwti}dz?b6tbJy^~}y{g}QKb;{~IX_JzDb4bD+hfGje=MTtF z44tmzv33q_-n)0uvB`+HhJ3-e1m`Eqw4>a2z$oL<^+rrg!%(IdAD9)Aka1v)sz`({ z_GYo2Q2V`W@J}4{y7%7>O?VM=(Jz1Ux0oTLh^hLv>)Z5|tN`UA^%axxex3l~gct{J zBNs7S2MgnH$vXPZ5M>t1qz9&qVWKW3Q3|Ag-UH17bHDd}+Hhj_m~Dir_Z%=#NfC8^ zOmB`8yUy#qO-mH=(Jnj6CA>r9stDZf7Q_zMUy0CMt4eH-0iJC>q}O4;X{&)xfse16 z1|7I5hC+25I_X3WS>u*^9PiBe9rF1bzS`u+y}}tVz@f%QZ*f9d1W9~U%0EL4uZooC z8>4Xjr9N`24CGYiaIj{q|He3{gmtZL0MT^G_%$nugnM*caJgCb7MI($ zkFj&XCH=J~~q3|5a=%StHqu?J=^z`bG}i9~3^)=%wQt zC>-807Qd2=*1gFo}e-*t#RKuS_yN|2{J|}>DMS%ee z#WGadYjRB2i7%rxMb!&G!350YJywKE&@=8nI>Ab09f{?gbMiuiV>-5<$}pxkFoaN_ ztaOU+6zKd_&XMT?yzd}$Rd`%Gg8fBcF-YTc9mU%~F1eFQmPy{ZzPcb~ryIq7_{ zqtdpqGyEr+hr7wv_)Ux=6;9)+Pnd1tJNV6xB^d-0een#}@lP~6op#+LtMa;3kHDD; z{4`GIjRuP8v)^iA*1P?!SOC&^Xk8Lhl4LrC>9LCi;T%y*lyW<4tb>=wF}Ac7!BaW^ zOfnCQk-zeVLv=+BSp4)1%>6ic+kQ8a+`7ayKnx{-#AdurNoZur74rKwA8 zlN6S=`;J2|o{h?IxL-?PDpKC|&&mV(ci`=K%`ZJ$ zA*SkKik$Mr>P{*R-6xptx|ibP6&l~2nG;T?f@)2)C^~e^gr#*&d)O9L1JCglPJdQh z9Wa_7M=wzTWnHYCnE|W|7JL3(6I_VH=}MceCY97tWoYAOjg-fkMHXI`# zuUOZ{yhL8d_}`x1cOM$f^U~L*0kBIE(@0p?oPiv3XsY3 z7_Hlo&q(^&Q2OwdO(2+g&M3!>z|D79c1NNrId;8iE#r>>KTAkt1Qsh z!*^gJo~@`apl-)JE{%1J}Z&nFum){|DxAYExth-uRe?gYAKt5Rl+F*ZX+OQDas(;xNT6B?MZ+xUks#Es1`3+N$UKzxzeqHDj96O1-jLJP@u%I? z#X1;m7}ogQ9AKCAyG_OV+xa#>)+)!7Mi7*T^i+eDf?W@4{9> zwGvvsV{*huCd1vaiIdR1b@N|n)C1&>_sf?Xl*U9JE$jK9@DLBm$-4Yot1t#x^3b#~ zu?YCT_~qHKE0Ad1xixA!Z|b_gU9N$E6#zc3^sna$87{E@k~VVALZMGV(nYKYWtw>i zf;_p|b3fMAb@?&_3_gE^Oc`)*-{KLsXAuTUHMNa9@V)X~=nZ#k02BHIuGrP<4~`^ECQN z?>=mf;|(YLoShkR3F8nIAO89JvL?f0tx}Mo&xKAoQLXZAi{aIPu~cle0Ea)v1`Y5?)l`})y&vb zclpF{tW_4*>)9^^ZPf12uD4;(eG&1@S;5c)N#DX-&#TDc+M?4o7Xzzs_Z$@3}iq73Jl zLioa)5xy*%*Qc~M31 z%;^dTtt~fLEM(~tVWpEeo-)|MW?wjBcEKD2ZC0RqFaGqhu z6i>cM_3jT#GnG4qGRbV_fZ6g|BPPXVQXhT;tJXzq#CBzX;pc&YEBRA}gxmhH*t1bx z_fiCx|57NpS$gwF!g|go9fAR%l$jrZD?6V*2x73bhr7rA-Uiu37dY;c=17LdV>eF8 z_t$Z|5Mqfo+;YuL81GE?O>_Ic6nfZg7j4VAgN`KqV$H!EgTRjWLIX`9~$#O#GVJ1yglhC}^@JHLtS^Htej z&#V%py!JmpU)6>y>y$XqG%E?#tXPS1Zfpxz)Hya|C^mUkar)tMMZp zehtY@54$>iu(f^q9s=!>|faNxP2YI%p_ zh0dy@=Ce6&uH)>@H{d5GW|R72$?v@5)qsEL7|E1cwNG;MLQbf_)a-u?SsUZDFMlX4 zB4$IfTMV*$i4>oIws(8wj0%+I2(-~+*^5@}pZ=^bx8Xkkh;KYr?m%C=ytL{2!a6pU z$g=zAS`+7yXh!prUw|1$Jy3%^9pa4Nl{v9W(k9i>6VQ3*ceCL5KwFBL*Jc6vRWTe& zDe&jNZz}TDTWwYxl=nBGu8H28N3(X3KSh0s;0-yWJ@ei8AEOk<J#k9lkmNlN@UHXt8cp#SdA2_& zZ0B(;Lz9Y-^|aT(y?TTi^PgfwFfgChoW$HkwdXQ=_DYTCR2pJTaS|Bb&~XO-9l-Ll ze=**VcaE>#=Q7U~9@f!s)%S(U0ruuu5**s31Wh5K8PqDo<)kY6StRs}bY!0TUvf;0 zXlm{npzNazl8@}EuWYyh1DoVm4vUcC37NRg5{ql&7hzCKX3Ix=&FJe?AFD0+fbCE^ z>;yy!^bGHC!e#mo{F1t5Jvn|kBn~m}8F84dCFU6{~>_Qd<XvtpHR!jGW@7jnF zr0xbuyu6QB{c|`XB5GWJMS?>IhyKZ!8qxNO?`}&_TFH)bB%fD(A(N|QVDQv>Ee?Gg zpAcF1A+Bn;1-QzmmM_DXn{JsNs^528*$Min^hi&xlW}%z>1hJH@Ytho&I&Si2Z;GK zfjR_v8DG<`L8jysknn`nkWuD1aPhGcKNtPXrMN>XfWEfu`S{%<^j05u;~eL7r5d+K zAKNfVc{k+)<8Lmeki+*lgn25*KB=j;eqz)8=*XtU!6u3VmGY+1CfMfb2c@-p zbx$TW{yOiO$r)i?LYW_PrHc-8@U<; z&x|{PdZk-AegFp`{aS>MyO24PN zOxn|kt_O=qG1|IiC)feH_tjK^bfGxpaI6;DNupmmEE2!B#DdQ;c_TsOScw}t#}$oR-F*~iSK`_teFHt^sFC8Oh6y}WzG^g zB)NZeL~Q>$n|7=1-2Zj&a$G*>2sxzHJp#*nB%GaMG_08LI2)NyeJ`fr?S9n2n5)bx z%GM9mh;6v5_J!M1(X2BuGM1A{9<#^4k9?F+VSKyRvs@L8N142k)9+fYM_Qbnap|wm zM1S!$^Q%)nspBR7|2(|RF-5J+eam`Oc5oFo$SkCY-3-uMY3i2M3jd!Bor`#d+{p5Yz#)4ZoaAP~E*&MhMV zv;TA!CSc_55*7h~5v`^BfCXqFEcWrhoYhOm3=PPS{pk!^k}m@QB9HIwN500Mj=mTh z9|sTygSqPL;fl7i@p8E8>Era0rpgNfol()f_4k8-ywyo(uZ(R0{Q8>2#AlksoVtvr zaQxihvvAF{aCU>`Cq*#~B_npPUCf;xyCQ~{Uz$?&E4jA0gY{kWRaC6??n@?}g+3){ zYo3p{78YY+jXtYILj;SpVyox+nOi>u%Hd`|=}1ys%&ms` z-pTZJ8S%SdN!YWRs^uD9RA^4>vRd6tAY+6@!u!UU>{0z`H^9wl`1Ncmm3%y=`( z0~O|=7_0|Fy6g3umH8L0=Xc;^79b%V{U;}8p&Qh=?975l*TK)yVF8=(dXX=4 zd@Li{PiP|VAS1`f-q;v39Rof4{qhGLL1VV1hDbTOz{%?fBqa1>B)O)q%>ia(0|N!M z?n_Ozq$*Fy_gUouzR~qZG2sp=JG%#Zo||i!_Z5K)C&Ur_!VlHw?5WeqJseH43USoH zd8nr?W_-7k^o`Hk4?|zia%va~X-H-kl)f>$v3IyKv3JhXolNTc9iLNtWCc+2JY5z; zj1eY6-+UHxOE{;t#k;!Tb3as=8wa`*aZ`7{C)sywQWOZ{`XaL8DTp!nJO(%UNkehf zp?had;!@ZR|F~pe0|o=l5JP^1-S?!@_7}Ai<*NI|quLg0IO85Yl6&{+#&5h`k5~y2 zJUBksRf}|=v#^sMc=*N1=5GjSN&u^RvL)f~AvM-i(cSl(1<{zXc{u!XiM9l`1_S`P zML?XSUY|ycg@MwID1kH8wv$((*VG!5ccxiYUCiJx!J{D;w;S)B(K)d3mbi!MstRX^ z3M?Fd!k`|rFM75O(69&zVk$`V%ZC~pG z^;zN47?_om8KdUIJJ&otr=XhA3V9|}wq(kW06(Z;Dpz=VaMpQeXqzD!x5IMDh4(Y} zZ3X})fEg&wpHF;;iPk5Wnx%-Evg2=b!M~2HkgUiB7`GQxQbjf3Oqm!9F0WabTYuyiVTL(3reoKqjL>#`aniq%u!))T+~gAT z9Uk&tqI@#cTw3F8=jukZN^DCsdH&T#l%|fUIpZG|w$t@on>L{H#KR3N9E@U0qYqjo z#_&Hf^@u$;kh8x@p@0682f@#TC#HP!pltBfmhzDITl5TnH?ylR3VfJ418EAcR^U}= zi3Kd0HIE(34wPs?;3CSzalsElr;t%!-r|QTd!=-5jq_PDba^MFtF=)i$vwpOQxmq> zbvCJn4_PP^${saF4+55Tv#6_{3h{SqYKoR3()Vg9;6&uD@x+`WNbq9QY&OdN`_l?mzD0bf} z^?6_ zYL-W1Tk=>n@YePj7K7``TA^ucyuN&7W831S)mQ%v6klt82w@Kd#U~^}wttP9Bo$DG z(CU{13}P#)Vmn=f+qEF&Z-Q4)xi>^7fIE@57|Wwe2`zo!&J`{tYdFjCCR!!3C9;Vi zMKt!n+*xY^!hoaAw9}Qltzk@h4zEHcG9mSZ_`a-e$SBR6nu%SefT=Y8gB+gP(%3(m zGib7(vo+V4y%#=)tUzCawf%3^h8BiOC+_f^T2JoAN+7O5P8s=x3?<5jas6V_>ed8r zcE${vFXYR4<5@55iHQG(4vHjGKgupbI_qQYk(EU$WMtCSLrR2smsVG9MDg0S2DnF5 zW1EV;#QO}@@LybQxYv6xE545Ws1DA|Lhu0?<6Ld9PS*^a(r^GH;#e2#k)=4NtiLCw zv3rv+y2E8#YsR3ozpl%407gD%WAn+*=h=NsSl#e?y}<}tPmnEdsT@p_eeld==K@C| zHyEGx@jP*OX64f|9L^H0IKDQ!)O(4cYNEWn19ujLnJzGj)$JDKMzuYDg6KxRcB_!MTrvN8)C5l;(z z+o$BwyC4_aE()5g_Afg(7u zp}_dMEl$BOA2nI;`@Ri3HE6w=SaZ172_|uSJDc^Z6LS7|4Q5x(c-)TGQ_f#)ClGG0 z?>?vIWEPylC)hddR+Z;g$*{Q!!i(P8%YxjmSh@uUHac%cFOsf=d{45vE;mcj$FdDP z-Kc&9|CEacFY_2HKlXc?E4uwBsXEm~jqg5z!TV>2?UMNqb~$y+ykD1GvcqWC%nfW- zJ?>UE!9Z}Nux;sl&A)E%5eV8py@LZQ>wIc@_mY0cd)=j(5wX)!#B&!|@ts(Ol}RJP zCc6_~wKC@fd=Ktd0Id-8r&^-a=T5IJVsPK}se6W-3v$vj;VIt)TT~AE3bOtNGSghy zI{e^QNGU<3Oj3iml_RB8yfQA3Hra47)l9L(yY3e!EHP%Jju8%HQ*<=EgI{B zD6W6m*s!{&*Erp!DAeA)J2{{*XER)=2h>rnzmhW9#WfVMd+A2Dfx8K#_KOZlt&>y% zqRY`rqjC3-H0fjUh%tmdkeBk^)f`g;4Qdt~X1ug#w;|}Q!G7KRzl24UGwZhI1`3c( z8_MmIy!;wp@dMY~-n5S%qTw9lZ!h$)HDkK(QhpSxTg5U>wH<%-pl1qlwnB#Rpg)yr z)0M=(8Xe?CMPFo(OP1d_O2NtnsC94A&v-TbJY~Y1V(j-%X2h|`5S0_U;U`;IUp#c|-jWr{>b-8DWKI&_JBc{;B+2`{*&HPiQvV)o3pqoVd5p3XB!I65NcXnk 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 zcmV+p74hncP)Px}$Vo&&RCodHT?>>I#hLEv8DUTq5F*M!H@auTQP~ZO*#{;@jIU&(GsNibF&iT@ z3iD!Mc*qeK85b}C2M1vu1jCpZFxhAjq8N;>m_*Fkb28aIXN`Mw*T-shLQoeKvLMXu z+V88rb#GPQ>N|7s_60ptoO`S4umAZ~_21Qfdm7IhDnkeiAuxnM!3c;#C1k7gH}311 z=JkteDZHtk3?>LqoFP5$WUWg|=?RffHMqTS+{NpnY29I6QuzZ2r4JGH3F-BE<#OAs zF*VPNRuR}f22M8^<#AkEFc67mI97 z#W=BUX6wvDMrg6kK>^*}wCOs`=30O=k?{J$`z>a8JDO(-DgIu10fwi0jM4L90ccAFm_&O0O1L5GdSJ^y$Zea$?OUFJ0bm+ zx~`f>D4wtQ`37M~bk}cM8KmSR0YDBs@tiNpuAKCVKb?Z1VHp2){v)0!2Cm3;7>l+iK!}NQy&m0 z$D-xyI%}3;CqB9B#kXF0Ov+#(ruiUHZm#R7{oL%1 z+Gn~OHf@$cuo^NFGEMcJwV$c)sCmY1`a@9jhNOXI9j;(5K7!4n>WF-O$INHp>og(! zIgm*OsbCdjVN7-7YcKJAe=ZKoLyY;wy{|lW?)bBxB^Ja4hsD7l2$pm;_WUp(*s(x& zH}v4J^8OaC9T0)|Vq@1W_hHuMfBfOU?v^QeHD-E0(#97Xx@*^y4##f8%-;y$_u-hj zvZ1rKlk_mW;(kjkNrD(xG5`Z%#Rpc+Pw3<1>$_??d|zA&_V=-3Z`{z>vzRBn`p%kl zi0Nv?wjX+~CI*a+m|(4g?vs84Qre0*pPWE5C*zPU69ZyFdQ;~uzd+1aW8Mz~gC(5}Jzpg~T5^Eq?jn_v?OXwv zwZ)BHHP2J1tA5KEobw(;TEN7L#Z7bwW%HcPrvzT`LpWB3)8xETfQ*3h7W&Q)i~7cw z7GXV*Ji&v+V#dVSzPsD8=V4!`$aGifZu1fcE?0y#D%58kYZ1|23Q9@v}8@au64XkLt978j`1 zEahFAi8b(piXs(&2ftLrVCq=xAa?uz*;aV^(KfU(Yl+a~f2=(DdzFpVEx$ z>v}GOu}@(qVbNds!>d=o_i>)^Uz)Qaf!&36AW|#9c%c(l(tt8I6A*%>YPFI>(3!^v zD$U(BPebtEAwQ0>YJP={@4tXu$fWgk_g`kwg?K>*-f3OHQqx&mKx6Kzl|#~d++>nL z{|xpq#XC%Te~`LL^@D&HFC{G@fNqn7mLRk6I$86Qr0JTbnDTrfB|c7GBlB?yWwwwNFDg~{=O%Ez) ztnowKATsR?woR}GgOdV+L90*tBnTsem8U<%B9srTy>UpVA2NqdImnD;txIG@b0%_& zi$*Te2Re*%wH1Lv;PR-UPoZyVAJe>w#`Xa6QPpv_LL0fG+PRKbDaqyP#0)dA{8C;^No#wL%MM5<^Gya9C%Z8ywx{Dt<4{A8pADJ{wP( z3cLoA*Lrjv#~(}Af<|rSXu6^gHG@YrP3O(AIJ(J`L_42d4X$~}tJvU3W~@M`;_SKT z+J00Mz7dSDh6ge)=Sh~W>PBj+L z)aqK%E?UT|{5TH3)P)3s30>3vt8>s%c&p{3qK>tfW=Y#O1T3KA1RWEkRN$rxYJ27k z-HA2h{LuzOV1TkdIte)4M2&kF;RF=mcAUMq_%rskL#T6ev@xigYGJ4^O$lpmZjM!n zTNGJ;xP=3z_|pEfrR8*;<&SS^`6x&J_-$+=E84xiQ!}JjN5tAd`7**@lI;nBPGe9vfgY`mIIUCcUHcSJ^>)~R=ZvjokEHExkwE z;gd$0J+ebd7)QvW+hJ}_GY=piRat2K8JmV8J4YJ>In5YEU0azP*&-8MftV)3mCJ3M zL52T%4Gko3NM)M^#INmVg`+DP9x(q!i^|nIpB+w@Wc zt@E_WpRav1@oW29ubn#Ri3>N7@qC$<$<~UV?Ut&Qh0bj;1T@VBxA8D0&+=&CIc_4L zp7U|U<5tJjS5W-gzNyXznj>^1QWPnawnpOU7E=NpS0GU{It=nkU(8YCfyc+Z(lKfd zMalaW7iSzjW5UpC0j(GufExTc&B42yBbpdNpX-=5%0yX9>-QTvX>94&x=z4`w7r;y zUGZ!CrXA~KsFJuNMG$+}Nl&eo0w=~aeza}+;yU4IeM}n4E#F_%x}_oWpyqP4A-8BC zd96p+)Ky6Q8T(oRY8z+}TmewfRoqk;XYpzIZR2Dqyd}#wH?~DBFMnuG8}jv%{PMO` z;qm9*8?k|gb=LwV%}rN-L}7bXf?k|IS<~Xq6BGy+zYfHa6DD(3LhQ7Se4&MbC_}SGq!f z3OV#YtY?MB@AA(AI&{blDB#ZVH*_F}oH&7=xE@?d%5WI@;N#Ebj9eMg(YUj#D~Pz6 zcJUUI#z0Cr$*w%ic=E*`_0P`HuBpg_P=V}uG!-~>%naLp#<>wLRy#9Tse=r$_oHna z>0a(O#>rM>B3DaXkVQ77)WzG2k3Z_41$3x2*HsERb0P+!3Uj54Pbsy{LgNopjqRnK zI)F)Bj&{*qwBo~>t1*6s zWZWh(Ok|rboI?k)JRj#)A@M8T$N^^o&AjK@#wkIErI6&Fm?S|De+5s{f~~vI_|^GC zJ+_+FW94X)T`M{~>1Bn?mIHrtIGHw9ODXLZ9)IqIj0H3v{yD$^7JmJ~lT3v0!z^BN zlS>a|aER-<_4-chZ|Y3hB-++v1W6b$bTYQ^m=)IBz=#oEF~J{gbLeVfq}*?)v43H~ zo7~i2z>34~o^CRs{Vt6~=}0qptqS8b$I@^a0fY9V`ROvoVf&WbEuJFzx0>|bSvKR20+^w1ai-v znhD^Ov>{Djrs=J%)A=Tgb?R}eK(2y;$TnSb7PsbV<|oObVv)gWWeYJfR&6g81j|TA zn%JOetQB1m{58bV9J7Gtz_?RJ^JRL!gV1LitJloa%p*rsd>4Sf1Ny~H)%VWT^pj3F zaUG2A!NgqDRK2!A)03`~g&D8Ln9<${Ecq6hJ>sOY`*b|S1k-#m7S}r`pE&$`CLOE% zV)Wk}H<}pQB?su%@6J4gmj||_7tAUc{2PjV>!F5y0Q4p%aJ+nQwWhCnc=mgNFa8dJ z<3%;W+NSFEOKlUy?=d$fnLC=lvSvDVTJ6{)rx@G%EzR#Uige7Ykh6J6-9$`CZ>YL& z8hl#CHZE0Kh#}i%$pM;!U0Yio!HnIFliV#Ei?!>|~C!39h3F3z2#V>|NG z`s#MRI8}6RYgq;7et`CvR4VmUV-*j*q;hudC9W&DLTL20q59q?)IUQUV{k39rEA-K z=4hc}?+?P5J#Xh}7hZUW;=i_SE)MUVn1GR3eE-zBZT`|Yn|V)Oq4D+=Py}%K&wCE9 zDZXp`1yi2g`GB4q4J6^;2lLp$S3Yp9P?7Jbttz+8 z$uaq({g0Zw?|Xc6*qYN%9lOqT;v*u_qS1-cWBaXm=Vo8;nrUFD#_oD5&cG8evlDQ5 zsW*(_@BygV=RZKjhEHiCQM!Md~}~o zUx&&N0z(K4Auxo%5CTI83?VRtzz_mM2n-=GguoC2LkQ$Upy1mO`IN;I8|*3?-i92)y)!}7`Y?@OK1vwN|6sBqTQ))T!mMy>{bBV~NgpM&a` zY#+;VTmro@4Kjh**t_uvDt=-MK+gPH6Uw$W-&%Q674rK;{A}_Uv4SNWIBrr$abs@9 z|Ih0EGj$>dm;6arS1cb>pcTMxF<_kVPId)eYTCyaXvv?Tj-T?e1seGoS>yOAB?0>S z^IQ3J51#YjF3f)T?^r>h{h(j;FKIuL23lP~jQ*36_}`erf?t#WN4Jk2LJxxc${a96VZHTv4i{DNbJb0e|Qh{ez$X s7RUQhq2~=a!60b25Zi_D7VP%_1I%$g{U1!;q5uE@07*qoM6N<$f{JsdhX4Qo 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 z(mTStk#>FyD-#S7v@SNZf(SX09~2d7RQ~I?n6?3VHl4t8Kn*d@-X;9E48@GR(g*;m z>jQg}0_C45^M9GRR;1!4QgBC)WExP~_xof~1M;E4{B-!}-MYBLL#5h~2IU)26$&Q^ zm3%Le>)p;#UgH}bSTqv)OtO$>S2dKuXYrDBtw@wh`#X~Fv?*3rLWZU&31#&J?rG+#&S<3mkxc zdLoSdK)R%luRm9>Ndo5LnQXUKa@Jz$#ApG9bu(e&Q<@Q`M1F{{r6oA z(e4B)r98_L-9Vza`3y(x8oOBuqUOSxYZk2y!xA0OuvUQN^WV_Bz$qL`+8&>>o`|!x z!fe^k@qBMe=uA(Am&%ra?|~P|`N3FDXq>pEF|t%??g(#VC6uRYcPUYD-zdHuGLLTYt(Q>zpZrNh?%+~>d44MQAzb4dFkuu$`N7;n!V z4o+a6o>e#m0VO9sKtTGyzey7h_<|f0D_Q&wejU#u^;TcX*D3p>#0RCH#iStm9`{bN z#1O>t*{gT{8T@v!PwCsmQ+kde(HE~pcBKBKes%k%xEW=GzzkWlY$oIZ4UXC#?b^b@zoQgnyg}9i* zlL|#D=GWQpl=Pg_t|w_J@fHGb4xSK3Jytr>Sq=>pXiP`-ZlglSH>KKNn%nAF$RyPv0i`6{vpEp^(3@E;FW z|9+ULNrm|lO*BN=dX{;`_t33BMLS1E~AKdGNDA3{FH|SnNRT z=dUQFQ!#a-4>ZwItR)@PUw9(@M~^Fi_}}l+nLmHIzJ+4^^k%Jz$~|xSC+u_4X!xoihcTed%sbZFII??Bo z+473hqL1rpXwumnPP>6)k%|dVla2ov>roXt*bc(J>lf-Hkt3f|QJZq$loHYjj65G! zk6hiVHvdoSans|cpEz9}AkI5{4y1|i1iIQiae67cuB8?i{0G zWe%kc{aIXgk|IsXzDyvB50Fte5zF)how9|vIG>gqy@T~F-Vx4_GY(4B-c~`1~I;bkx#d7uASyJUD0*$G=O3;anFKqgpr;OPtO@`&L#i!&6+BAX25N^&P zGp`(VT$H<-TTo3$d-TON)E5vOXO#lePwddHlPP=Hu{iy@IAVi>aSo!Za%!3xb;Hd2 zY2VWi_fqi{9qSR191~HaSG=gYc-C3I%MOPtB>MW1;YQr37y?irEDEvh^b51q!1`=e z1*|0lq+r0u+^m=az4B zYix+Q(i+07OXQi!T9p7aIhLp|XOATiYU*zH9jZ}{CG>94OW#g)}xgNHKtF?y&z zT#n=ut@S9k5*pda9#;Z6_T|mjHu?oVF^r=F9nRM~^FQqStL8&L#r-@Dd*A5@Bi=i_ zqP`miHLuuTgKP99{`js2Elo9Na^BA!BtmCCUAAhnecE~&9<4LaSgge@N^QrLjc{%8 zGss&#y%BnyPrswJzh*>oLbL8C&wP${;=2q1aBqFx4L^|~bkd}+rgeo)X!8AmaBb=Z zWve)92G90ti>>w;SvRNNf(p3{z z^O4uA7@b0H?2eMv;B5R!HGklqBS1ED17_U*N+}7#533%**&@a_vBuwS0ydWg=#SAc zNz_`>3;Lbf)Y6Fp|s7XTnBx3xX?AM z1cbb9aqg6Hb&CGgM|UmV5zbGZDtP=Z^>jbL(cug@4f8)7>m@$iSvuag=^}qnuL?%s z$&Lx*TdUf6{3uZS4Q8RGX=h&LQ#!uYx9!yP+Y_Us8PXZ=m;MsXPt4A$kBg%GX}qZ_ zpW+inJ{?j+rf;9RV1AR`EzN<`@deQCTzP2trLNHqq>Kyy=7c4ts`7jF-E3nW{x*G0 z#m^Qur>59Uu1%+kmy?zXP!FqNWl}cp-47E-NK1E1OWy^;>3VD>d58Ycc#je1chF7s zA6rKbFL~O(9X=Mqm~A4`GFCfbwH3@MaiFgeTz+#RN5ClNFY8SSVJ5Cl{EGCzfP=rq zpG{xrSD+U{m)aK>303vh1BI}3M_gh`GGXA>`^9DVK^*xyt9}w4qq3q6{~IWoo^i!jU?FKm!oJp z;wf7Rlt|pac3-=jvwZg$IB|^Z^#xx18$^nlgi>G|R`e4Tl}#fT>6ao`^51R%ri4Ma zDuJ!{3wCa(ah<`4qsiLtXs1ict3FyTQbxTe_KOZC;EaE&WqFDX?FgsDnp)g z-&#@Y#JLL0^OdH8M4*^>G{j`L`)Os}Az_fLqQY#Rg$?Dkwn;h)fz6)Mnicp21B#zou3Sl57C!AS9(QY+a8&eeJ+VWewd`9yY%98qyD15T%2L* z-}tebWN$oDG2G3){a#Z!_{k-*DE5~SsTNlm4-Uo25{T$Ib_ipk!wvEFOiyA6$>eaadtitFCNZ7612yW%ks zm7*x%r2;pak+*Gn19kj2JLhzbT|rVWUoeg3F2~MRkUW+IOU>Y+((&XM(ATgU(Ca1a z{@brd9BJk~Srs&)%XB9PZp7WU3%{&cf6#Tqv2f^j_krj7uS7+^k-#QdNQ36aWe_b~%%a9930_MCc5_Tb5wUuXsMU7Y^VWEx=uy8iCf2qJ7jccXU3zVpjR#H)K67nKrft!2WO& zy;4>&;%+XHG}CK=s!3FzO{Jna-0%&?4EV__y25Eb`6f@ggYC;q9fjs8E>-y44|Jj- zB3Rv8>xr&1{Z^<{uvu>u3N7!qk0O9TB%S_BAZFg4|H{d!e^sdrJ04@W8eDwz#l8GD z+itsJcMsw-NMmp4;juTqGCUx}y6}T)qiZ{_NaTG~H_L(7g&NGb*7L0s6-~hIIie6> za#ry8tNL2d+}uDWShLU0+y}L99Is%$l_*BfYR|6)a3XTARHVi|s6YvW4ou;(rSm!H z7biQAa@~VB?3eOC_Taj8v5>RvsL!6Ew&Sg7d(}Do8`ZmXn1+M8Yuj%#P+i|9D06L7 zl*D0dY0&vHvOeM6fjC+{frz8{z%m-?f=?lAoW1L1hTC^|&)WY@$#z^$HZ!KJu>WS1 zm5tduJ3AMo09Bt+f%59*||dc4Jz*cSQRLgwtLG2de=MvH5n>kGU zL5xRp18*?s%kK)aM%wbQ)6m)5nyoOrXBdQ${L)X6J1e&Z3&HKLt5AutWm2rfp~DTn zi#T2rBonM9%_rFKB9CZQti==yz)C^oD7TopZS!uxb$I{y;S`9*w*~H^ZRT$AnhZZ~a+Z#?>eHGtZIsx6r?~`jK5eL`d?;qa5kz&p3Q`hcWgK z)shm44za*R1JbqPZUnz&tGC)Fw|e1pNw9xLC`UKo2z_r-o|_L+N|$^gWC{`*m2=X3&e5Z7b5{e1Y1kWqWv zQOuMO?LEJeqCrl5IeY%oA0N^t1Af09#Fb0)_{bLNAJ`r&E^En}_GDJ~$@XH^A`?Q= z5VHQ^ce)Q(&`FP=l_a7VK4UFFcrC#ka?mHakvH1V+~yC|Pt`MLuSmwlCM3OAc8L`= zvNqb;ox7YC`1Po-+67oJ&Liz+{^Pz2aNkm6^{?rl0=1%Q@u7#1-pxaj+!Mubi+2^o zgWl_v>k>;A5Sbwg{`5T)qNC}j(n<$QgarQQxo5mSe3D+}rL|yb5he6rwu5O0TtllmAALRviXecuk z#3rSIs5Dr#D=BAS6$8*iExlgP<7Qs=*M?PgAAp@!*feKT5v~J&hpB+;fe&{ZeL#Ei zlc<|^!2F@XyhLt^|; z6Lv+ZT7#qU*q)So8I`j3bzJO>ME@i-C^A?wd(VQ)FsbG2I%v-Aq5ppE4`7Mq+^pe+ zfY$Hy5a}=A*MamX$obv>-#Gmk1OZt)RHQa^U{x(D3Lbbx11+C&@Voz=8hA{wW!)0J zT*bzyYDDV73lkj$zRSKN31ydE35+A_47zH1xSb7Wf41QT2mMV}zgQEH{BapQrQnyg zGO4!Ih_q{Big+EQ@w7ANrZS>uz*P6@=lAksNvGQW3c8f7EO6O|c&*s|pd#+!;A%RL$BY~a%25k zY}Sx=xjY{Tl$-d@ZK>_1b(x35M7zKIfhX@(`%F1y?Ad$m?^^70Y&>O{RKI-aO4=3k zof`gHqYS#P!{~Yq7R8Gsge*8VfIq;y)mU=yD@X+kZ0>QZd~iVME_;dL7B|(5hsJs! z+q-Bv@s2!Af0ixnL>^n*$2RH)BL`go8~3_ge5dsUEgv zj|4+Hm|>RsSJ`dYZ0s-JUMQCRsk16^t0aa1@?m9m{ zU~?L3UC?OV9svmr!dRq+a=%`n)i_JszmKX3VpsBPWIt@y>+K;vwpqIr_LL#W^iHj8 zXOwM3{qe}}2e@&0@X2I*$06Y!q$b(3Z}i#gPuS13@)=^nwAX&Jef(uF#I1%WEW?lX z!5cjfZ3)=d(px>93V9Sz;@qy2Ut2wdD|fU0W{#Jbk64bum z1HSr3>tSfv@}PfNJ<#Y7X(y0tQ*z1&m#Ia63~X-wU!yty7msr;BvzzfO2zf?T}V+LhR3z|MT2>G@993O?j97@uG zbs5v?fZKHweC%NqBWXvMxpY8K8#Q;|nt3{4aP{ts>aPWDNViCtAE_`6plx?3RP)|f z%C5eKa3=Np+i`yP&YBgF)>+gvM#u*w;MEyb^vsdN%lmUUjw~6S(=s&Gv%X)QFH{s( z!tBv&p;QH?i<1?3#j#tPy*J@_<4)E`f<*7?T~%mE2Z)kE8NLlg~z8aLP}Zry)=Y>PsyS*u?Dy!~^8-teA9v;x+f7;e6}kA!5MIK3r&D(HKf zMjni`i-kE)&t84-26NHg&;4jB2^{vE%^TcYU9_# literal 5598 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 Og4C2W70VSYg8m0xQL!Tc From 53c9663bb9c8c83b86058ee8cc8f6c9201cb3737 Mon Sep 17 00:00:00 2001 From: seinfield Date: Sun, 7 Dec 2014 02:55:49 -0500 Subject: [PATCH 22/41] 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 5d9cd1c324c5d87cc8f9510449a54ef7c43646c0 Mon Sep 17 00:00:00 2001 From: seinfield Date: Sun, 7 Dec 2014 03:00:41 -0500 Subject: [PATCH 23/41] 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 28c994fce1d714528b4a79176cd304a0b0a3ac4d Mon Sep 17 00:00:00 2001 From: Nik V Date: Thu, 11 Dec 2014 21:21:18 +0800 Subject: [PATCH 24/41] 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 d0e49d2c72e583eb133bcfb891e03b4382d8f355 Mon Sep 17 00:00:00 2001 From: Nik V Date: Thu, 11 Dec 2014 21:28:04 +0800 Subject: [PATCH 25/41] 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 051be7b5acd85c9fa277a0a48ffd3fccb2adcc6a 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/41] 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 bd8ea77e79c79798df68c676bbf372ccfb9a3c5c Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Mon, 28 Dec 2015 21:58:08 -0800 Subject: [PATCH 27/41] 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 c25e6f37df834cedc2eaf026676ffaf5c841d862 Mon Sep 17 00:00:00 2001 From: David Silva Date: Fri, 6 May 2016 19:02:53 +0100 Subject: [PATCH 28/41] 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 43658c1343778ff0c3b5904773a5f0bc2e36850f Mon Sep 17 00:00:00 2001 From: Andrew Sauber Date: Tue, 17 May 2016 13:35:56 -0400 Subject: [PATCH 29/41] 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 4aa458d31a6a45e2a37c26958b162611721a6498 Mon Sep 17 00:00:00 2001 From: doozr Date: Thu, 14 Jul 2016 22:24:48 +0100 Subject: [PATCH 30/41] 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 a6f6d7eec563005e2ccf7e0fdf19003d65eb1582 Mon Sep 17 00:00:00 2001 From: Andrew Sauber Date: Sat, 20 Aug 2016 13:55:09 -0400 Subject: [PATCH 31/41] 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 3f556334518013d99e0302eaffb64cee78d5b0a3 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/41] 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 4659b9381bf57a5a2180034a424d4b8dd593c9a9 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 4 Jan 2017 12:02:53 -0800 Subject: [PATCH 33/41] 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 9fe8728d21cd94fee6c9cdfbb9cbf658117befff Mon Sep 17 00:00:00 2001 From: Paul Liu Date: Mon, 13 Feb 2017 00:03:42 -0500 Subject: [PATCH 34/41] 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From de91e1ea0d6e06ee02cd7ed8d98aa91c4266ed24 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 9 Mar 2017 12:09:05 -0800 Subject: [PATCH 35/41] Updated docs from master --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 56eb4598..49982715 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +.PHONY: clean docs + # A Makefile for commands I run frequently: clean: From 5787bd0bd905aab96965662791dbade11be2c60e Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 9 Mar 2017 12:40:38 -0800 Subject: [PATCH 36/41] Explicit code blocks in docs --- docs/_themes/jrnl/static/less/docs.less | 2 +- docs/advanced.rst | 7 ++-- docs/encryption.rst | 20 ++++++++--- docs/export.rst | 24 +++++++++---- docs/installation.rst | 16 ++++++--- docs/recipes.rst | 20 ++++++++--- docs/usage.rst | 48 ++++++++++++++++++------- 7 files changed, 99 insertions(+), 38 deletions(-) diff --git a/docs/_themes/jrnl/static/less/docs.less b/docs/_themes/jrnl/static/less/docs.less index eb12bb48..7433941e 100644 --- a/docs/_themes/jrnl/static/less/docs.less +++ b/docs/_themes/jrnl/static/less/docs.less @@ -114,7 +114,7 @@ a:hover, a:active background: desaturate(lighten(@terminal,10), 10); pre {color: white;} } -.highlight-python +.highlight-python, .highlight-sh { .terminal; pre diff --git a/docs/advanced.rst b/docs/advanced.rst index e28975fe..ac883915 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -84,12 +84,11 @@ You can configure _jrnl_ to use with multiple journals (eg. ``private`` and ``wo } } -The ``default`` journal gets created the first time you start _jrnl_. Now you can access the ``work`` journal by using ``jrnl work`` instead of ``jrnl``, eg. :: +The ``default`` journal gets created the first time you start _jrnl_. Now you can access the ``work`` journal by using ``jrnl work`` instead of ``jrnl``, eg. + +.. code-block:: sh jrnl work at 10am: Meeting with @Steve - -:: - jrnl work -n 3 will both use ``~/work.txt``, while ``jrnl -n 3`` will display the last three entries from ``~/journal.txt`` (and so does ``jrnl default -n 3``). diff --git a/docs/encryption.rst b/docs/encryption.rst index 694b26fe..76f89b8f 100644 --- a/docs/encryption.rst +++ b/docs/encryption.rst @@ -7,11 +7,15 @@ Encrypting and decrypting ------------------------- -If you don't choose to encrypt your file when you run `jrnl` for the first time, you can encrypt your existing journal file or change its password using :: +If you don't choose to encrypt your file when you run `jrnl` for the first time, you can encrypt your existing journal file or change its password using + +.. code-block:: sh jrnl --encrypt -If it is already encrypted, you will first be asked for the current password. You can then enter a new password and your plain journal will replaced by the encrypted file. Conversely, :: +If it is already encrypted, you will first be asked for the current password. You can then enter a new password and your plain journal will replaced by the encrypted file. Conversely, + +.. code-block:: sh jrnl --decrypt @@ -28,11 +32,15 @@ If you don't initially store the password in the keychain but decide to do so at 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`` :: +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`` + +.. code-block:: sh HISTIGNORE="$HISTIGNORE:jrnl *" -If you are using zsh instead of bash, you can get the same behaviour adding this to your ``zshrc`` :: +If you are using zsh instead of bash, you can get the same behaviour adding this to your ``zshrc`` + +.. code-block:: sh setopt HIST_IGNORE_SPACE alias jrnl=" jrnl" @@ -40,7 +48,9 @@ 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. Here's a Python script that you can use to decrypt your journal:: +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 + +.. code-block:: python #!/usr/bin/env python3 diff --git a/docs/export.rst b/docs/export.rst index e017951d..f3b82b5a 100644 --- a/docs/export.rst +++ b/docs/export.rst @@ -6,7 +6,9 @@ Import and Export Tag export ---------- -With:: +With + +.. code-block:: sh jrnl --tags @@ -15,7 +17,7 @@ you'll get a list of all tags you used in your journal, sorted by most frequent. List of all entries ------------------- -:: +.. code-block:: sh jrnl --short @@ -24,7 +26,9 @@ Will only display the date and title of each entry. JSON export ----------- -Can do:: +Can do + +.. code-block:: sh jrnl --export json @@ -33,7 +37,9 @@ Why not create a `beautiful timeline `_ of your jour Markdown export --------------- -Use:: +Use + +.. code-block:: sh jrnl --export markdown @@ -42,7 +48,7 @@ Markdown is a simple markup language that is human readable and can be used to b Text export ----------- -:: +.. code-block:: sh jrnl --export text @@ -51,11 +57,15 @@ Pretty-prints your entire journal. Export to files --------------- -You can specify the output file of your exported journal using the `-o` argument:: +You can specify the output file of your exported journal using the `-o` argument + +.. code-block:: sh jrnl --export md -o journal.md -The above command will generate a file named `journal.md`. If the `-o` argument is a directory, jrnl will export each entry into an individual file:: +The above command will generate a file named `journal.md`. If the `-o` argument is a directory, jrnl will export each entry into an individual file + +.. code-block:: sh jrnl --export json -o my_entries/ diff --git a/docs/installation.rst b/docs/installation.rst index 7a57865c..be015055 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,15 +6,21 @@ Getting started Installation ------------ -On OS X, the easiest way to install *jrnl* is using `Homebrew `_ :: +On OS X, the easiest way to install *jrnl* is using `Homebrew `_ + +.. code-block:: sh brew install jrnl -On other platforms, install *jrnl* using pip :: +On other platforms, install *jrnl* using pip + +.. code-block:: sh pip install jrnl -Or, if you want the option to encrypt your journal, :: +Or, if you want the option to encrypt your journal, + +.. code-block:: sh pip install jrnl[encrypted] @@ -32,7 +38,9 @@ The first time you run ``jrnl`` you will be asked where your journal file should Quickstart ---------- -to make a new entry, just type:: +to make a new entry, just type + +.. code-block:: sh jrnl yesterday: Called in sick. Used the time to clean the house and spent 4h on writing my book. diff --git a/docs/recipes.rst b/docs/recipes.rst index ddf6c9cd..85c6b4a6 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -9,7 +9,9 @@ Recipes Co-occurrence of tags ~~~~~~~~~~~~~~~~~~~~~ -If I want to find out how often I mentioned my flatmates Alberto and Melo in the same entry, I run :: +If I want to find out how often I mentioned my flatmates Alberto and Melo in the same entry, I run + +.. code-block:: sh jrnl @alberto --tags | grep @melo @@ -18,7 +20,9 @@ And will get something like ``@melo: 9``, meaning there are 9 entries where both Combining filters ~~~~~~~~~~~~~~~~~ -You can do things like :: +You can do things like + +.. code-block:: sh jrnl @fixed -starred -n 10 -until "jan 2013" --short @@ -27,11 +31,15 @@ To get a short summary of the 10 most recent, favourited entries before January Statistics ~~~~~~~~~~ -How much did I write last year? :: +How much did I write last year? + +.. code-block:: sh jrnl -from "jan 1 2013" -until "dec 31 2013" | wc -w -Will give you the number of words you wrote in 2013. How long is my average entry? :: +Will give you the number of words you wrote in 2013. How long is my average entry? + +.. code-block:: sh expr $(jrnl --export text | wc -w) / $(jrnl --short | wc -l) @@ -40,7 +48,9 @@ This will first get the total number of words in the journal and divide it by th Importing older files ~~~~~~~~~~~~~~~~~~~~~ -If you want to import a file as an entry to jrnl, you can just do ``jrnl < entry.ext``. But what if you want the modification date of the file to be the date of the entry in jrnl? Try this :: +If you want to import a file as an entry to jrnl, you can just do ``jrnl < entry.ext``. But what if you want the modification date of the file to be the date of the entry in jrnl? Try this + +.. code-block:: sh echo `stat -f %Sm -t '%d %b %Y at %H:%M: ' entry.txt` `cat entry.txt` | jrnl diff --git a/docs/usage.rst b/docs/usage.rst index f9cdb60c..f0ff87bd 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -10,7 +10,9 @@ We intentionally break a convention on command line arguments: all arguments sta Listing Journals ---------------- -You can list the journals accessible by jrnl:: +You can list the journals accessible by jrnl + +.. code-block:: sh jrnl -ls @@ -19,7 +21,9 @@ The journals displayed correspond to those specified in the jrnl configuration f Composing Entries ----------------- -Composing mode is entered by either starting ``jrnl`` without any arguments -- which will prompt you to write an entry or launch your editor -- or by just writing an entry on the prompt, such as:: +Composing mode is entered by either starting ``jrnl`` without any arguments -- which will prompt you to write an entry or launch your editor -- or by just writing an entry on the prompt, such as + +.. code-block:: sh jrnl today at 3am: I just met Steve Buscemi in a bar! He looked funny. @@ -28,7 +32,9 @@ Composing mode is entered by either starting ``jrnl`` without any arguments -- w Most shell contains a certain number of reserved characters, such as ``#`` and ``*``. Unbalanced quotes, parenthesis, and so on will also get into the way of your editing. For writing longer entries, just enter ``jrnl`` and hit ``return``. Only then enter the text of your journal entry. Alternatively, :doc:`use an external editor `). -You can also import an entry directly from a file:: +You can also import an entry directly from a file + +.. code-block:: sh jrnl < my_entry.txt @@ -48,7 +54,9 @@ Timestamps that work: Starring entries ~~~~~~~~~~~~~~~~ -To mark an entry as a favourite, simply "star" it:: +To mark an entry as a favourite, simply "star" it + +.. code-block:: sh jrnl last sunday *: Best day of my life. @@ -65,30 +73,42 @@ If you don't want to add a date (ie. your entry will be dated as now), The follo Viewing ------- -:: + + +.. code-block:: sh jrnl -n 10 -will list you the ten latest entries (if you're lazy, ``jrnl -10`` will do the same), :: +will list you the ten latest entries (if you're lazy, ``jrnl -10`` will do the same), + +.. code-block:: sh jrnl -from "last year" -until march -everything that happened from the start of last year to the start of last march. To only see your favourite entries, use :: +everything that happened from the start of last year to the start of last march. To only see your favourite entries, use + +.. code-block:: sh jrnl -starred Using Tags ---------- -Keep track of people, projects or locations, by tagging them with an ``@`` in your entries :: +Keep track of people, projects or locations, by tagging them with an ``@`` in your entries + +.. code-block:: sh jrnl Had a wonderful day on the @beach with @Tom and @Anna. -You can filter your journal entries just like this: :: +You can filter your journal entries just like this: + +.. code-block:: sh jrnl @pinkie @WorldDomination -Will print all entries in which either ``@pinkie`` or ``@WorldDomination`` occurred. :: +Will print all entries in which either ``@pinkie`` or ``@WorldDomination`` occurred. + +.. code-block:: sh jrnl -n 5 -and @pineapple @lubricant @@ -101,7 +121,9 @@ the last five entries containing both ``@pineapple`` **and** ``@lubricant``. You Editing older entries --------------------- -You can edit selected entries after you wrote them. This is particularly useful when your journal file is encrypted or if you're using a DayOne journal. To use this feature, you need to have an editor configured in your journal configuration file (see :doc:`advanced usage `):: +You can edit selected entries after you wrote them. This is particularly useful when your journal file is encrypted or if you're using a DayOne journal. To use this feature, you need to have an editor configured in your journal configuration file (see :doc:`advanced usage `) + +.. code-block:: sh jrnl -until 1950 @texas -and @history --edit @@ -109,7 +131,9 @@ Will open your editor with all entries tagged with ``@texas`` and ``@history`` b Of course, if you are using multiple journals, you can also edit e.g. the latest entry of your work journal with ``jrnl work -n 1 --edit``. In any case, this will bring up your editor and save (and, if applicable, encrypt) your edited journal after you save and exit the editor. -You can also use this feature for deleting entries from your journal:: +You can also use this feature for deleting entries from your journal + +.. code-block:: sh jrnl @girlfriend -until 'june 2012' --edit From 247310fea2c0a40830083bd4db411a6f40fbcf7a Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 9 Mar 2017 12:42:08 -0800 Subject: [PATCH 37/41] Docs CSS update --- docs/_themes/jrnl/static/less/docs.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_themes/jrnl/static/less/docs.less b/docs/_themes/jrnl/static/less/docs.less index 7433941e..da817702 100644 --- a/docs/_themes/jrnl/static/less/docs.less +++ b/docs/_themes/jrnl/static/less/docs.less @@ -223,12 +223,12 @@ div.footer { body:not(.landing){ padding-top: 130px; - .highlight-output,.highlight-python, .highlight-javascript + .highlight-output, .highlight-python, .highlight-javascript, .highlight-sh { width: auto; max-width: 500px; } - .highlight-python + .highlight-python, .highlight-sh { pre { margin: -10px 0 10px 0;} &:before From 9ffca1a7a66a9fb9ddcd5e22149b7ec0e7e9e689 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 9 Mar 2017 21:47:52 +0100 Subject: [PATCH 38/41] Add most recent Python versions in Travis CI (#474) Add more recent Python versions including development branches and nightly build. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 175fe9f5..a8df432e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ python: - "2.7" - "3.3" - "3.4" + - "3.5" + - "3.6" + - "3.6-dev" + - "3.7-dev" + - "nightly" install: - "pip install -e ." - "pip install pycrypto>=2.6" From 1080298930b3b718d89f0285fbd22d30fdece8a6 Mon Sep 17 00:00:00 2001 From: timetoplatypus Date: Sat, 26 Aug 2017 20:15:04 -0400 Subject: [PATCH 39/41] Adds import feature for JSON formatted journals. --- jrnl/cli.py | 9 +++++++-- jrnl/importers.py | 21 +++++++++++++++++++++ jrnl/util.py | 28 +++++++++++++--------------- 3 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 jrnl/importers.py diff --git a/jrnl/cli.py b/jrnl/cli.py index 35764734..fb067aa3 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -11,6 +11,7 @@ from __future__ import absolute_import, unicode_literals from . import Journal from . import DayOneJournal from . import util +from . import importers from . import exporters from . import install import jrnl @@ -45,6 +46,7 @@ def parse_args(args=None): exporting = parser.add_argument_group('Export / Import', 'Options for transmogrifying your journal') exporting.add_argument('--short', dest='short', action="store_true", help='Show only titles or line containing the search tags') exporting.add_argument('--tags', dest='tags', action="store_true", help='Returns a list of all tags and number of occurences') + exporting.add_argument('--import-json', metavar='FILEPATH', dest='import_json', help='Import a journal from a JSON file.', default=False, const=None) exporting.add_argument('--export', metavar='TYPE', dest='export', choices=['text', 'txt', 'markdown', 'md', 'json'], help='Export your journal. TYPE can be json, markdown, or text.', default=False, const=None) exporting.add_argument('-o', metavar='OUTPUT', dest='output', help='Optionally specifies output file when using --export. If OUTPUT is a directory, exports each entry into an individual file instead.', default=False, const=None) exporting.add_argument('--encrypt', metavar='FILENAME', dest='encrypt', help='Encrypts your existing journal with a new password', nargs='?', default=False, const=None) @@ -58,7 +60,7 @@ def guess_mode(args, config): """Guesses the mode (compose, read or export) from the given arguments""" compose = True export = False - if args.decrypt is not False or args.encrypt is not False or args.export is not False or any((args.short, args.tags, args.edit)): + if args.decrypt is not False or args.encrypt is not False or args.import_json is not False or args.export is not False or any((args.short, args.tags, args.edit)): compose = False export = True elif any((args.start_date, args.end_date, args.on_date, args.limit, args.strict, args.starred)): @@ -138,7 +140,7 @@ def run(manual_args=None): config = install.install_jrnl(CONFIG_PATH) else: log.debug('Reading configuration from file %s', CONFIG_PATH) - config = util.load_and_fix_json(CONFIG_PATH) + config = util.load_and_fix_json(CONFIG_PATH, "configuration") install.upgrade_config(config, config_path=CONFIG_PATH) if args.ls: @@ -247,6 +249,9 @@ def run(manual_args=None): elif args.tags: print(util.py2encode(exporters.to_tag_list(journal))) + elif args.import_json is not False: + importers.import_json(args.import_json, config) + elif args.export is not False: print(util.py2encode(exporters.export(journal, args.export, args.output))) diff --git a/jrnl/importers.py b/jrnl/importers.py new file mode 100644 index 00000000..e522d0f5 --- /dev/null +++ b/jrnl/importers.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +import os +import json +import sys +from . import util + +def import_json(path, jrnl_config): + json_data = util.load_and_fix_json(path, "JSON") + + new_jrnl = open(jrnl_config["journal"], "w") + for element in json_data["entries"]: + new_jrnl.write( + element["date"] + + " " + + element["time"] + + " " + + element["title"] + + "\n" + + element["body"] + ) + new_jrnl.close() diff --git a/jrnl/util.py b/jrnl/util.py index e9df0fb1..6653d9a2 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -93,36 +93,34 @@ def yesno(prompt, default=True): raw = py23_input(prompt) return {'y': True, 'n': False}.get(raw.lower(), default) -def load_and_fix_json(json_path): +def load_and_fix_json(json_path, target_file): """Tries to load a json object from a file. If that fails, tries to fix common errors (no or extra , at end of the line). """ with open(json_path) as f: json_str = f.read() - log.debug('Configuration file %s read correctly', json_path) - config = None + log.debug('%s file %s read correctly', target_file, json_path) + data = None try: return json.loads(json_str) except ValueError as e: - log.debug('Could not parse configuration %s: %s', json_str, e, - exc_info=True) + log.debug('Could not parse %s %s: %s', target_file, 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) - config = json.loads(json_str) + log.debug('Attempting to reload automatically fixed %s file %s', target_file, json_str) + data = 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 + json.dump(data, f, indent=2) + log.debug('Fixed %s saved in file %s', target_file, json_path) + prompt("[Some errors in your {0} file have been fixed for you.]".format(target_file)) + return data 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]") + log.debug('Could not load fixed %s: %s', target_file, e, exc_info=True) + prompt("[There seems to be something wrong with your {0} file at {1}: {2}]".format(target_file, json_path, e.message)) + prompt("[Journal was NOT modified]") sys.exit(1) def get_text_from_editor(config, template=""): From e0c74011ca1f2f31010462ffd174fff0e2624a21 Mon Sep 17 00:00:00 2001 From: timetoplatypus Date: Tue, 29 Aug 2017 21:28:59 -0400 Subject: [PATCH 40/41] Adds feature to export a journal to a static HTML page (with inline CSS) --- jrnl/Entry.py | 28 ++++++++++++++++++++++++++++ jrnl/cli.py | 2 +- jrnl/exporters.py | 38 +++++++++++++++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/jrnl/Entry.py b/jrnl/Entry.py index 66b32f6c..2da9c216 100755 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -109,3 +109,31 @@ class Entry: body=body, space=space ) + + def to_html(self): + html = "\n" + html += "\n" + html += "\t\n" + html += "\t\t\n" + html += "\t\n\n" + html += "\t\n" + html += "\t\t\n" + html += "\t\t\t

Journal

\n" + html += "\t\t\t
\n" + # date time title body + html +="\t\t\t\t

" + self.date.strftime(self.journal.config['timeformat']) + "\t" + str(self.title) + "

\n" + html +="\t\t\t\t

" + str(self.body) + "

\n\t\t\t
\n\t\t\t
\n" + html +="\t\t
\n" + html +="\t\n" + html +="" + return html diff --git a/jrnl/cli.py b/jrnl/cli.py index fb067aa3..a974df1c 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -47,7 +47,7 @@ def parse_args(args=None): exporting.add_argument('--short', dest='short', action="store_true", help='Show only titles or line containing the search tags') exporting.add_argument('--tags', dest='tags', action="store_true", help='Returns a list of all tags and number of occurences') exporting.add_argument('--import-json', metavar='FILEPATH', dest='import_json', help='Import a journal from a JSON file.', default=False, const=None) - exporting.add_argument('--export', metavar='TYPE', dest='export', choices=['text', 'txt', 'markdown', 'md', 'json'], help='Export your journal. TYPE can be json, markdown, or text.', default=False, const=None) + exporting.add_argument('--export', metavar='TYPE', dest='export', choices=['text', 'txt', 'markdown', 'md', 'json', 'html'], help='Export your journal. TYPE can be json, markdown, html, or text.', default=False, const=None) exporting.add_argument('-o', metavar='OUTPUT', dest='output', help='Optionally specifies output file when using --export. If OUTPUT is a directory, exports each entry into an individual file instead.', default=False, const=None) exporting.add_argument('--encrypt', metavar='FILENAME', dest='encrypt', help='Encrypts your existing journal with a new password', nargs='?', default=False, const=None) exporting.add_argument('--decrypt', metavar='FILENAME', dest='decrypt', help='Decrypts your journal and stores it in plain text', nargs='?', default=False, const=None) diff --git a/jrnl/exporters.py b/jrnl/exporters.py index ad2c3186..881f3781 100644 --- a/jrnl/exporters.py +++ b/jrnl/exporters.py @@ -66,9 +66,38 @@ def to_txt(journal): return journal.pprint() +def to_html(journal): + html = "\n" + html += "\n" + html += "\t\n" + html += "\t\t\n" + html += "\t\n\n" + html += "\t\n" + html += "\t\t\n" + html += "\t\t\t

Journal

\n" + html += "\t\t\t
\n" + for element in journal.entries: + # date time title body + html += "\t\t\t\t

" + str(element.date) + "\t" + element.title + "

\n" + html += "\t\t\t\t

" + element.body + "

\n\t\t\t
\n\t\t\t
\n" + html += "\t\t
\n" + html += "\t\n" + html += "" + return html + def export(journal, format, output=None): """Exports the journal to various formats. - format should be one of json, txt, text, md, markdown. + format should be one of json, txt, text, md, markdown, html. If output is None, returns a unicode representation of the output. If output is a directory, exports entries into individual files. Otherwise, exports to the given output file. @@ -78,10 +107,11 @@ def export(journal, format, output=None): "txt": to_txt, "text": to_txt, "md": to_md, - "markdown": to_md + "markdown": to_md, + "html": to_html } if format not in maps: - return "[ERROR: can't export to '{0}'. Valid options are 'md', 'txt', and 'json']".format(format) + return "[ERROR: can't export to '{0}'. Valid options are 'md', 'txt', 'html', and 'json']".format(format) if output and os.path.isdir(output): # multiple files return write_files(journal, output, format) else: @@ -109,6 +139,8 @@ def write_files(journal, path, format): content = e.to_md() elif format in ('txt', 'text'): content = e.__unicode__() + elif format == 'html': + content = e.to_html() with codecs.open(full_path, "w", "utf-8") as f: f.write(content) return "[Journal exported individual files in {0}]".format(path) From 9876decfec3a5708fec966af83c98d3900e8211a Mon Sep 17 00:00:00 2001 From: timetoplatypus Date: Wed, 30 Aug 2017 13:01:41 -0400 Subject: [PATCH 41/41] Updates README.md Adds instructions for installing from source using `setup.py` --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index aa7bcab9..9eb52515 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ If you just call `jrnl`, you will be prompted to compose your entry - but you ca Installation ------------ +Install _jrnl_ from source: + + sudo python setup.py install Install _jrnl_ using pip: