From 34f8f858f14707646c0897e5577361eb80781a6a Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Thu, 31 Oct 2019 21:12:55 +0100 Subject: [PATCH 1/9] remove py2 remnants and use mocks in tests --- features/encryption.feature | 8 +-- features/environment.py | 15 +---- features/multiple_journals.feature | 2 +- features/steps/core.py | 68 +++++++++++-------- features/upgrade.feature | 4 +- jrnl/DayOneJournal.py | 3 +- jrnl/EncryptedJournal.py | 8 +-- jrnl/Entry.py | 5 +- jrnl/Journal.py | 24 +++---- jrnl/__main__.py | 1 - jrnl/cli.py | 49 +++++++------- jrnl/export.py | 17 +++-- jrnl/install.py | 14 ++-- jrnl/plugins/__init__.py | 2 - jrnl/plugins/jrnl_importer.py | 9 ++- jrnl/plugins/json_exporter.py | 1 - jrnl/plugins/markdown_exporter.py | 1 - jrnl/plugins/tag_exporter.py | 1 - jrnl/plugins/template.py | 2 +- jrnl/plugins/template_exporter.py | 6 +- jrnl/plugins/text_exporter.py | 15 ++--- jrnl/plugins/xml_exporter.py | 10 ++- jrnl/plugins/yaml_exporter.py | 3 +- jrnl/upgrade.py | 32 ++++----- jrnl/util.py | 101 ++++------------------------- 25 files changed, 155 insertions(+), 246 deletions(-) diff --git a/features/encryption.feature b/features/encryption.feature index 82d971eb3..d30c48b31 100644 --- a/features/encryption.feature +++ b/features/encryption.feature @@ -2,7 +2,7 @@ Scenario: Loading an encrypted journal Given we use the config "encrypted.yaml" When we run "jrnl -n 1" and enter "bad doggie no biscuit" - Then we should see the message "Password" + Then the output should contain "Password" and the output should contain "2013-06-10 15:40 Life is good" Scenario: Decrypting a journal @@ -14,16 +14,16 @@ Scenario: Encrypting a journal Given we use the config "basic.yaml" - When we run "jrnl --encrypt" and enter "swordfish" + When we run "jrnl --encrypt" and enter "swordfish" and "n" Then we should see the message "Journal encrypted" and the config for journal "default" should have "encrypt" set to "bool:True" When we run "jrnl -n 1" and enter "swordfish" - Then we should see the message "Password" + Then the output should contain "Password" and the output should contain "2013-06-10 15:40 Life is good" Scenario: Storing a password in Keychain Given we use the config "multiple.yaml" - When we run "jrnl simple --encrypt" and enter "sabertooth" + When we run "jrnl simple --encrypt" and enter "sabertooth" and "y" When we set the keychain password of "simple" to "sabertooth" Then the config for journal "simple" should have "encrypt" set to "bool:True" When we run "jrnl simple -n 1" diff --git a/features/environment.py b/features/environment.py index 6f9ac5dfb..7a918feb7 100644 --- a/features/environment.py +++ b/features/environment.py @@ -1,25 +1,15 @@ -from behave import * import shutil import os -import jrnl -try: - from io import StringIO -except ImportError: - from cStringIO import StringIO + def before_scenario(context, scenario): """Before each scenario, backup all config and journal test data.""" - context.messages = StringIO() - jrnl.util.STDERR = context.messages - jrnl.util.TEST = True - # Clean up in case something went wrong for folder in ("configs", "journals"): working_dir = os.path.join("features", folder) if os.path.exists(working_dir): shutil.rmtree(working_dir) - for folder in ("configs", "journals"): original = os.path.join("features", "data", folder) working_dir = os.path.join("features", folder) @@ -32,10 +22,9 @@ def before_scenario(context, scenario): else: shutil.copy2(source, working_dir) + def after_scenario(context, scenario): """After each scenario, restore all test data and remove working_dirs.""" - context.messages.close() - context.messages = None for folder in ("configs", "journals"): working_dir = os.path.join("features", folder) if os.path.exists(working_dir): diff --git a/features/multiple_journals.feature b/features/multiple_journals.feature index 1d4943ee4..28587f960 100644 --- a/features/multiple_journals.feature +++ b/features/multiple_journals.feature @@ -42,5 +42,5 @@ Feature: Multiple journals Scenario: Don't crash if no file exists for a configured encrypted journal Given we use the config "multiple.yaml" - When we run "jrnl new_encrypted Adding first entry" and enter "these three eyes" + When we run "jrnl new_encrypted Adding first entry" and enter "these three eyes" and "y" Then we should see the message "Journal 'new_encrypted' created" diff --git a/features/steps/core.py b/features/steps/core.py index 83981d138..9217f77f3 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -1,5 +1,4 @@ -from __future__ import unicode_literals -from __future__ import absolute_import +from unittest.mock import patch from behave import given, when, then from jrnl import cli, install, Journal, util, plugins @@ -10,10 +9,13 @@ import json import yaml import keyring +import tzlocal +import shlex +import sys class TestKeyring(keyring.backend.KeyringBackend): - """A test keyring that just stores its valies in a hash""" + """A test keyring that just stores its values in a hash""" priority = 1 keys = defaultdict(dict) @@ -31,15 +33,6 @@ def delete_password(self, servicename, username, password): keyring.set_keyring(TestKeyring()) -try: - from io import StringIO -except ImportError: - from cStringIO import StringIO -import tzlocal -import shlex -import sys - - def ushlex(command): if sys.version_info[0] == 3: return shlex.split(command) @@ -73,18 +66,41 @@ def set_config(context, config_file): cf.write("version: {}".format(__version__)) +def _mock_getpass(inputs): + def prompt_return(prompt="Password: "): + print(prompt) + return next(inputs) + return prompt_return + + +def _mock_input(inputs): + def prompt_return(prompt=""): + val = next(inputs) + print(prompt, val) + return val + return prompt_return + + @when('we run "{command}" and enter') -@when('we run "{command}" and enter "{inputs}"') -def run_with_input(context, command, inputs=None): - text = inputs or context.text +@when('we run "{command}" and enter "{inputs1}"') +@when('we run "{command}" and enter "{inputs1}" and "{inputs2}"') +def run_with_input(context, command, inputs1="", inputs2=""): + # create an iterator through all inputs. These inputs will be fed one by one + # to the mocked calls for 'input()', 'util.getpass()' and 'sys.stdin.read()' + text = iter((inputs1, inputs2)) if inputs1 else iter(context.text.split("\n")) args = ushlex(command)[1:] - buffer = StringIO(text.strip()) - util.STDIN = buffer - try: - cli.run(args or []) - context.exit_status = 0 - except SystemExit as e: - context.exit_status = e.code + with patch("builtins.input", side_effect=_mock_input(text)) as mock_input: + with patch("jrnl.util.getpass", side_effect=_mock_getpass(text)) as mock_getpass: + with patch("sys.stdin.read", side_effect=text) as mock_read: + try: + cli.run(args or []) + context.exit_status = 0 + except SystemExit as e: + context.exit_status = e.code + + # assert at least one of the mocked input methods got called + assert mock_input.called or mock_getpass.called or mock_read.called + @when('we run "{command}"') @@ -190,28 +206,24 @@ def check_output_time_inline(context, text): def check_output_inline(context, text=None): text = text or context.text out = context.stdout_capture.getvalue() - if isinstance(out, bytes): - out = out.decode('utf-8') assert text in out, text @then('the output should not contain "{text}"') def check_output_not_inline(context, text): out = context.stdout_capture.getvalue() - if isinstance(out, bytes): - out = out.decode('utf-8') assert text not in out @then('we should see the message "{text}"') def check_message(context, text): - out = context.messages.getvalue() + out = context.stderr_capture.getvalue() assert text in out, [text, out] @then('we should not see the message "{text}"') def check_not_message(context, text): - out = context.messages.getvalue() + out = context.stderr_capture.getvalue() assert text not in out, [text, out] diff --git a/features/upgrade.feature b/features/upgrade.feature index bce026b8c..ddcce4943 100644 --- a/features/upgrade.feature +++ b/features/upgrade.feature @@ -13,11 +13,11 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x Scenario: Upgrading a journal encrypted with jrnl 1.x Given we use the config "encrypted_old.json" - When we run "jrnl -n 1" and enter + When we run "jrnl -n 1" and enter """ Y bad doggie no biscuit bad doggie no biscuit """ - Then we should see the message "Password" + Then the output should contain "Password" and the output should contain "2013-06-10 15:40 Life is good" diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index 9e988f786..ccec528a8 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals from . import Entry from . import Journal from . import time as jrnl_time @@ -83,7 +82,7 @@ def write(self): def editable_str(self): """Turns the journal into a string of entries that can be edited manually and later be parsed with eslf.parse_editable_str.""" - return "\n".join(["# {0}\n{1}".format(e.uuid, e.__unicode__()) for e in self.entries]) + return "\n".join(["# {0}\n{1}".format(e.uuid, str(e)) for e in self.entries]) def parse_editable_str(self, edited): """Parses the output of self.editable_str and updates its entries.""" diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index a83651e45..67345f452 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -8,14 +8,13 @@ import sys import os import base64 -import getpass import logging log = logging.getLogger() def make_key(password): - password = util.bytes(password) + password = password.encode("utf-8") kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, @@ -48,9 +47,9 @@ def open(self, filename=None): self.config['password'] = password text = "" self._store(filename, text) - util.prompt("[Journal '{0}' created at {1}]".format(self.name, filename)) + print("[Journal '{0}' created at {1}]".format(self.name, filename), file=sys.stderr) else: - util.prompt("No password supplied for encrypted journal") + print("No password supplied for encrypted journal", file=sys.stderr) sys.exit(1) else: text = self._load(filename) @@ -59,7 +58,6 @@ def open(self, filename=None): log.debug("opened %s with %d entries", self.__class__.__name__, len(self)) return self - def _load(self, filename, password=None): """Loads an encrypted journal from a file and tries to decrypt it. If password is not provided, will look for password in the keychain diff --git a/jrnl/Entry.py b/jrnl/Entry.py index 1306cef5a..cf012e800 100755 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import unicode_literals import re import textwrap from datetime import datetime @@ -52,13 +51,13 @@ def tags(self): @staticmethod def tag_regex(tagsymbols): pattern = r'(?u)(?:^|\s)([{tags}][-+*#/\w]+)'.format(tags=tagsymbols) - return re.compile(pattern, re.UNICODE) + return re.compile(pattern) def _parse_tags(self): tagsymbols = self.journal.config['tagsymbols'] return set(tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text)) - def __unicode__(self): + def __str__(self): """Returns a string representation of the entry to be written into a journal file.""" date_str = self.date.strftime(self.journal.config['timeformat']) title = "[{}] {}".format(date_str, self.title.rstrip("\n ")) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 72fe94b18..35ae801e6 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals from . import Entry from . import util from . import time @@ -15,7 +14,7 @@ log = logging.getLogger(__name__) -class Tag(object): +class Tag: def __init__(self, name, count=0): self.name = name self.count = count @@ -27,7 +26,7 @@ def __repr__(self): return "".format(self.name) -class Journal(object): +class Journal: def __init__(self, name='default', **kwargs): self.config = { 'journal': "journal.txt", @@ -72,7 +71,7 @@ def open(self, filename=None): filename = filename or self.config['journal'] if not os.path.exists(filename): - util.prompt("[Journal '{0}' created at {1}]".format(self.name, filename)) + print("[Journal '{0}' created at {1}]".format(self.name, filename), file=sys.stderr) self._create(filename) text = self._load(filename) @@ -96,7 +95,7 @@ def validate_parsing(self): return True def _to_text(self): - return "\n".join([e.__unicode__() for e in self.entries]) + return "\n".join([str(e) for e in self.entries]) def _load(self, filename): raise NotImplementedError @@ -140,9 +139,6 @@ def _parse(self, journal_txt): entry._parse_text() return entries - def __unicode__(self): - return self.pprint() - def pprint(self, short=False): """Prettyprints the journal's entries""" sep = "\n" @@ -153,7 +149,7 @@ def pprint(self, short=False): tagre = re.compile(re.escape(tag), re.IGNORECASE) pp = re.sub(tagre, lambda match: util.colorize(match.group(0)), - pp, re.UNICODE) + pp) else: pp = re.sub( Entry.Entry.tag_regex(self.config['tagsymbols']), @@ -162,6 +158,9 @@ def pprint(self, short=False): ) return pp + def __str__(self): + return self.pprint() + def __repr__(self): return "".format(len(self.entries)) @@ -254,7 +253,7 @@ def new_entry(self, raw, date=None, sort=True): def editable_str(self): """Turns the journal into a string of entries that can be edited manually and later be parsed with eslf.parse_editable_str.""" - return "\n".join([e.__unicode__() for e in self.entries]) + return "\n".join([str(e) for e in self.entries]) def parse_editable_str(self, edited): """Parses the output of self.editable_str and updates it's entries.""" @@ -347,8 +346,9 @@ def open_journal(name, config, legacy=False): from . import DayOneJournal return DayOneJournal.DayOne(**config).open() else: - util.prompt( - u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']) + print( + u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']), + file=sys.stderr ) sys.exit(1) diff --git a/jrnl/__main__.py b/jrnl/__main__.py index 73e08b337..b01d9ff47 100644 --- a/jrnl/__main__.py +++ b/jrnl/__main__.py @@ -1,6 +1,5 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals from . import cli diff --git a/jrnl/cli.py b/jrnl/cli.py index 65a535169..1bcdbae6a 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -7,8 +7,6 @@ license: MIT, see LICENSE for more details. """ -from __future__ import unicode_literals -from __future__ import absolute_import from . import Journal from . import util from . import install @@ -91,7 +89,7 @@ def encrypt(journal, filename=None): if util.yesno("Do you want to store the password in your keychain?", default=True): util.set_keychain(journal.name, journal.config['password']) - util.prompt("Journal encrypted to {0}.".format(filename or new_journal.config['journal'])) + print("Journal encrypted to {0}.".format(filename or new_journal.config['journal']), file=sys.stderr) def decrypt(journal, filename=None): @@ -102,7 +100,7 @@ def decrypt(journal, filename=None): new_journal = Journal.PlainJournal(filename, **journal.config) new_journal.entries = journal.entries new_journal.write(filename) - util.prompt("Journal decrypted to {0}.".format(filename or new_journal.config['journal'])) + print("Journal decrypted to {0}.".format(filename or new_journal.config['journal']), file=sys.stderr) def list_journals(config): @@ -138,20 +136,19 @@ def configure_logger(debug=False): 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__) - print(util.py2encode(version_str)) + print(version_str) sys.exit(0) try: config = install.load_or_install_jrnl() except UserAbort as err: - util.prompt("\n{}".format(err)) + print("\n{}".format(err), file=sys.stderr) sys.exit(1) if args.ls: - util.prnt(list_journals(config)) + print(list_journals(config)) sys.exit(0) log.debug('Using configuration "%s"', config) @@ -164,8 +161,8 @@ def run(manual_args=None): if journal_name is not 'default': args.text = args.text[1:] elif "default" not in config['journals']: - util.prompt("No default journal configured.") - util.prompt(list_journals(config)) + print("No default journal configured.", file=sys.stderr) + print(list_journals(config), file=sys.stderr) sys.exit(1) config = util.scope_config(config, journal_name) @@ -175,12 +172,12 @@ def run(manual_args=None): try: args.limit = int(args.text[0].lstrip("-")) args.text = args.text[1:] - except: + except ValueError: pass log.debug('Using journal "%s"', journal_name) mode_compose, mode_export, mode_import = guess_mode(args, config) - + # How to quit writing? if "win32" in sys.platform: _exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter" @@ -190,21 +187,22 @@ def run(manual_args=None): if mode_compose and not args.text: if not sys.stdin.isatty(): # Piping data into jrnl - raw = util.py23_read() + raw = sys.stdin.read() elif config['editor']: template = "" if config['template']: try: template = open(config['template']).read() - except: - util.prompt("[Could not read template at '']".format(config['template'])) + except IOError: + print("[Could not read template at '']".format(config['template']), file=sys.stderr) sys.exit(1) raw = util.get_text_from_editor(config, template) else: try: - raw = util.py23_read("[Compose Entry; " + _exit_multiline_code + " to finish writing]\n") + print("[Compose Entry; " + _exit_multiline_code + " to finish writing]\n", file=sys.stderr) + raw = sys.stdin.read() except KeyboardInterrupt: - util.prompt("[Entry NOT saved to journal.]") + print("[Entry NOT saved to journal.]", file=sys.stderr) sys.exit(0) if raw: args.text = [raw] @@ -215,7 +213,7 @@ def run(manual_args=None): try: journal = Journal.open_journal(journal_name, config) except KeyboardInterrupt: - util.prompt("[Interrupted while opening journal]".format(journal_name)) + print("[Interrupted while opening journal]".format(journal_name), file=sys.stderr) sys.exit(1) # Import mode @@ -225,11 +223,9 @@ def run(manual_args=None): # Writing mode elif mode_compose: 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)) + print("[Entry added to {0} journal]".format(journal_name), file=sys.stderr) journal.write() if not mode_compose: @@ -246,14 +242,14 @@ def run(manual_args=None): # Reading mode if not mode_compose and not mode_export and not mode_import: - print(util.py2encode(journal.pprint())) + print(journal.pprint()) # Various export modes elif args.short: - print(util.py2encode(journal.pprint(short=True))) + print(journal.pprint(short=True)) elif args.tags: - print(util.py2encode(plugins.get_exporter("tags").export(journal))) + print(plugins.get_exporter("tags").export(journal)) elif args.export is not False: exporter = plugins.get_exporter(args.export) @@ -275,7 +271,8 @@ def run(manual_args=None): elif args.edit: if not config['editor']: - util.prompt("[{1}ERROR{2}: You need to specify an editor in {0} to use the --edit function.]".format(install.CONFIG_FILE_PATH, ERROR_COLOR, RESET_COLOR)) + print("[{1}ERROR{2}: You need to specify an editor in {0} to use the --edit function.]" + .format(install.CONFIG_FILE_PATH, ERROR_COLOR, RESET_COLOR), file=sys.stderr) sys.exit(1) other_entries = [e for e in old_entries if e not in journal.entries] # Edit @@ -290,7 +287,7 @@ def run(manual_args=None): if num_edited: prompts.append("{0} {1} modified".format(num_edited, "entry" if num_deleted == 1 else "entries")) if prompts: - util.prompt("[{0}]".format(", ".join(prompts).capitalize())) + print("[{0}]".format(", ".join(prompts).capitalize()), file=sys.stderr) journal.entries += other_entries journal.sort() journal.write() diff --git a/jrnl/export.py b/jrnl/export.py index d48733148..a69ee00e1 100644 --- a/jrnl/export.py +++ b/jrnl/export.py @@ -1,15 +1,14 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals from .util import ERROR_COLOR, RESET_COLOR -from .util import slugify, u -from .template import Template +from .util import slugify +from .plugins.template import Template import os import codecs -class Exporter(object): +class Exporter: """This Exporter can convert entries and journals into text files.""" def __init__(self, format): with open("jrnl/templates/" + format + ".template") as f: @@ -17,8 +16,8 @@ def __init__(self, format): self.template = Template(body) def export_entry(self, entry): - """Returns a unicode representation of a single entry.""" - return entry.__unicode__() + """Returns a string representation of a single entry.""" + return str(entry) def _get_vars(self, journal): return { @@ -28,7 +27,7 @@ def _get_vars(self, journal): } def export_journal(self, journal): - """Returns a unicode representation of an entire journal.""" + """Returns a string representation of an entire journal.""" return self.template.render_block("journal", **self._get_vars(journal)) def write_file(self, journal, path): @@ -41,7 +40,7 @@ def write_file(self, journal, path): return "[{2}ERROR{3}: {0} {1}]".format(e.filename, e.strerror, ERROR_COLOR, RESET_COLOR) def make_filename(self, entry): - return entry.date.strftime("%Y-%m-%d_{0}.{1}".format(slugify(u(entry.title)), self.extension)) + return entry.date.strftime("%Y-%m-%d_{0}.{1}".format(slugify(entry.title), self.extension)) def write_files(self, journal, path): """Exports a journal into individual files for each entry.""" @@ -57,7 +56,7 @@ def write_files(self, journal, path): def export(self, journal, format="text", output=None): """Exports to individual files if output is an existing path, or into a single file if output is a file name, or returns the exporter's - representation as unicode if output is None.""" + representation as string if output is None.""" if output and os.path.isdir(output): # multiple files return self.write_files(journal, output) elif output: # single file diff --git a/jrnl/install.py b/jrnl/install.py index 5a80562fb..08bb44ddb 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -69,7 +69,7 @@ def upgrade_config(config): for key in missing_keys: config[key] = default_config[key] save_config(config) - print("[Configuration updated to newest version at {}]".format(CONFIG_FILE_PATH)) + print("[Configuration updated to newest version at {}]".format(CONFIG_FILE_PATH), file=sys.stderr) def save_config(config): @@ -91,10 +91,10 @@ def load_or_install_jrnl(): try: upgrade.upgrade_jrnl_if_necessary(config_path) except upgrade.UpgradeValidationException: - util.prompt("Aborting upgrade.") - util.prompt("Please tell us about this problem at the following URL:") - util.prompt("https://github.com/jrnl-org/jrnl/issues/new?title=UpgradeValidationException") - util.prompt("Exiting.") + print("Aborting upgrade.", file=sys.stderr) + print("Please tell us about this problem at the following URL:", file=sys.stderr) + print("https://github.com/jrnl-org/jrnl/issues/new?title=UpgradeValidationException", file=sys.stderr) + print("Exiting.", file=sys.stderr) sys.exit(1) upgrade_config(config) @@ -121,7 +121,7 @@ def autocomplete(text, state): # Where to create the journal? path_query = 'Path to your journal file (leave blank for {}): '.format(JOURNAL_FILE_PATH) - journal_path = util.py23_input(path_query).strip() or JOURNAL_FILE_PATH + journal_path = input(path_query).strip() or JOURNAL_FILE_PATH default_config['journals']['default'] = os.path.expanduser(os.path.expandvars(journal_path)) path = os.path.split(default_config['journals']['default'])[0] # If the folder doesn't exist, create it @@ -139,7 +139,7 @@ def autocomplete(text, state): else: util.set_keychain("default", None) EncryptedJournal._create(default_config['journals']['default'], password) - print("Journal will be encrypted.") + print("Journal will be encrypted.", file=sys.stderr) else: PlainJournal._create(default_config['journals']['default']) diff --git a/jrnl/plugins/__init__.py b/jrnl/plugins/__init__.py index 64d7b3ba2..bb2ea1769 100644 --- a/jrnl/plugins/__init__.py +++ b/jrnl/plugins/__init__.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals - from .text_exporter import TextExporter from .jrnl_importer import JRNLImporter from .json_exporter import JSONExporter diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index 85615e75b..1015c2620 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -1,12 +1,11 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals import codecs import sys from .. import util -class JRNLImporter(object): +class JRNLImporter: """This plugin imports entries from other jrnl files.""" names = ["jrnl"] @@ -21,11 +20,11 @@ def import_(journal, input=None): other_journal_txt = f.read() else: try: - other_journal_txt = util.py23_read() + other_journal_txt = sys.stdin.read() except KeyboardInterrupt: - util.prompt("[Entries NOT imported into journal.]") + print("[Entries NOT imported into journal.]", file=sys.stderr) sys.exit(0) journal.import_(other_journal_txt) new_cnt = len(journal.entries) - util.prompt("[{0} imported to {1} journal]".format(new_cnt - old_cnt, journal.name)) + print("[{0} imported to {1} journal]".format(new_cnt - old_cnt, journal.name), file=sys.stderr) journal.write() diff --git a/jrnl/plugins/json_exporter.py b/jrnl/plugins/json_exporter.py index 5abaf9161..e6591302a 100644 --- a/jrnl/plugins/json_exporter.py +++ b/jrnl/plugins/json_exporter.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals from .text_exporter import TextExporter import json from .util import get_tags_count diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index 19b5404d6..0452a5f84 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals, print_function from .text_exporter import TextExporter import os import re diff --git a/jrnl/plugins/tag_exporter.py b/jrnl/plugins/tag_exporter.py index 439bac7c8..269a762de 100644 --- a/jrnl/plugins/tag_exporter.py +++ b/jrnl/plugins/tag_exporter.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals from .text_exporter import TextExporter from .util import get_tags_count diff --git a/jrnl/plugins/template.py b/jrnl/plugins/template.py index 21fb28963..7f72e2f80 100644 --- a/jrnl/plugins/template.py +++ b/jrnl/plugins/template.py @@ -13,7 +13,7 @@ INCLUDE_RE = r"{% *include +(.+?) *%}" -class Template(object): +class Template: def __init__(self, template): self.template = template self.clean_template = None diff --git a/jrnl/plugins/template_exporter.py b/jrnl/plugins/template_exporter.py index 85aa2236d..ecb9ac87d 100644 --- a/jrnl/plugins/template_exporter.py +++ b/jrnl/plugins/template_exporter.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals - from .text_exporter import TextExporter from .template import Template import os @@ -14,7 +12,7 @@ class GenericTemplateExporter(TextExporter): @classmethod def export_entry(cls, entry): - """Returns a unicode representation of a single entry.""" + """Returns a string representation of a single entry.""" vars = { 'entry': entry, 'tags': entry.tags @@ -23,7 +21,7 @@ def export_entry(cls, entry): @classmethod def export_journal(cls, journal): - """Returns a unicode representation of an entire journal.""" + """Returns a string representation of an entire journal.""" vars = { 'journal': journal, 'entries': journal.entries, diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index dbb54d04e..f8ade5197 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -1,26 +1,25 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals import codecs -from ..util import u, slugify +from ..util import slugify import os from ..util import ERROR_COLOR, RESET_COLOR -class TextExporter(object): +class TextExporter: """This Exporter can convert entries and journals into text files.""" names = ["text", "txt"] extension = "txt" @classmethod def export_entry(cls, entry): - """Returns a unicode representation of a single entry.""" - return entry.__unicode__() + """Returns a string representation of a single entry.""" + return str(entry) @classmethod def export_journal(cls, journal): - """Returns a unicode representation of an entire journal.""" + """Returns a string representation of an entire journal.""" return "\n".join(cls.export_entry(entry) for entry in journal) @classmethod @@ -35,7 +34,7 @@ def write_file(cls, journal, path): @classmethod def make_filename(cls, entry): - return entry.date.strftime("%Y-%m-%d_{0}.{1}".format(slugify(u(entry.title)), cls.extension)) + return entry.date.strftime("%Y-%m-%d_{0}.{1}".format(slugify(str(entry.title)), cls.extension)) @classmethod def write_files(cls, journal, path): @@ -53,7 +52,7 @@ def write_files(cls, journal, path): def export(cls, journal, output=None): """Exports to individual files if output is an existing path, or into a single file if output is a file name, or returns the exporter's - representation as unicode if output is None.""" + representation as string if output is None.""" if output and os.path.isdir(output): # multiple files return cls.write_files(journal, output) elif output: # single file diff --git a/jrnl/plugins/xml_exporter.py b/jrnl/plugins/xml_exporter.py index 0af2ed477..2783663b5 100644 --- a/jrnl/plugins/xml_exporter.py +++ b/jrnl/plugins/xml_exporter.py @@ -1,10 +1,8 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals from .json_exporter import JSONExporter from .util import get_tags_count -from ..util import u from xml.dom import minidom @@ -20,7 +18,7 @@ def export_entry(cls, entry, doc=None): entry_el = doc_el.createElement('entry') for key, value in cls.entry_to_dict(entry).items(): elem = doc_el.createElement(key) - elem.appendChild(doc_el.createTextNode(u(value))) + elem.appendChild(doc_el.createTextNode(value)) entry_el.appendChild(elem) if not doc: doc_el.appendChild(entry_el) @@ -33,8 +31,8 @@ def entry_to_xml(cls, entry, doc): entry_el = doc.createElement('entry') entry_el.setAttribute('date', entry.date.isoformat()) if hasattr(entry, "uuid"): - entry_el.setAttribute('uuid', u(entry.uuid)) - entry_el.setAttribute('starred', u(entry.starred)) + entry_el.setAttribute('uuid', entry.uuid) + entry_el.setAttribute('starred', entry.starred) entry_el.appendChild(doc.createTextNode(entry.fulltext)) return entry_el @@ -49,7 +47,7 @@ def export_journal(cls, journal): for count, tag in tags: tag_el = doc.createElement('tag') tag_el.setAttribute('name', tag) - count_node = doc.createTextNode(u(count)) + count_node = doc.createTextNode(str(count)) tag_el.appendChild(count_node) tags_el.appendChild(tag_el) for entry in journal.entries: diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index c07358110..eec9b0b3d 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import absolute_import, unicode_literals, print_function from .text_exporter import TextExporter import os import re @@ -27,7 +26,7 @@ def export_entry(cls, entry, to_multifile=True): tagsymbols = entry.journal.config['tagsymbols'] # see also Entry.Entry.rag_regex - multi_tag_regex = re.compile(r'(?u)^\s*([{tags}][-+*#/\w]+\s*)+$'.format(tags=tagsymbols), re.UNICODE) + multi_tag_regex = re.compile(r'(?u)^\s*([{tags}][-+*#/\w]+\s*)+$'.format(tags=tagsymbols)) '''Increase heading levels in body text''' newbody = '' diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index f2b80af39..2a5c1192f 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import, unicode_literals +import sys from . import __version__ from . import Journal @@ -10,7 +10,7 @@ def backup(filename, binary=False): - util.prompt(" Created a backup at {}.backup".format(filename)) + print(" Created a backup at {}.backup".format(filename), file=sys.stderr) filename = os.path.expanduser(os.path.expandvars(filename)) with open(filename, 'rb' if binary else 'r') as original: contents = original.read() @@ -26,7 +26,7 @@ def upgrade_jrnl_if_necessary(config_path): config = util.load_config(config_path) - util.prompt("""Welcome to jrnl {}. + print("""Welcome to jrnl {}. It looks like you've been using an older version of jrnl until now. That's okay - jrnl will now upgrade your configuration and journal files. Afterwards @@ -63,19 +63,19 @@ def upgrade_jrnl_if_necessary(config_path): longest_journal_name = max([len(journal) for journal in config['journals']]) if encrypted_journals: - util.prompt("\nFollowing encrypted journals will be upgraded to jrnl {}:".format(__version__)) + print("\nFollowing encrypted journals will be upgraded to jrnl {}:".format(__version__), file=sys.stderr) for journal, path in encrypted_journals.items(): - util.prompt(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name)) + print(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), file=sys.stderr) if plain_journals: - util.prompt("\nFollowing plain text journals will upgraded to jrnl {}:".format(__version__)) + print("\nFollowing plain text journals will upgraded to jrnl {}:".format(__version__), file=sys.stderr) for journal, path in plain_journals.items(): - util.prompt(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name)) + print(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), file=sys.stderr) if other_journals: - util.prompt("\nFollowing journals will be not be touched:") + print("\nFollowing journals will be not be touched:", file=sys.stderr) for journal, path in other_journals.items(): - util.prompt(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name)) + print(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), file=sys.stderr) try: cont = util.yesno("\nContinue upgrading jrnl?", default=False) @@ -85,13 +85,13 @@ def upgrade_jrnl_if_necessary(config_path): raise UserAbort("jrnl NOT upgraded, exiting.") for journal_name, path in encrypted_journals.items(): - util.prompt("\nUpgrading encrypted '{}' journal stored in {}...".format(journal_name, path)) + print("\nUpgrading encrypted '{}' journal stored in {}...".format(journal_name, path), file=sys.stderr) backup(path, binary=True) old_journal = Journal.open_journal(journal_name, util.scope_config(config, journal_name), legacy=True) all_journals.append(EncryptedJournal.from_journal(old_journal)) for journal_name, path in plain_journals.items(): - util.prompt("\nUpgrading plain text '{}' journal stored in {}...".format(journal_name, path)) + print("\nUpgrading plain text '{}' journal stored in {}...".format(journal_name, path), file=sys.stderr) backup(path) old_journal = Journal.open_journal(journal_name, util.scope_config(config, journal_name), legacy=True) all_journals.append(Journal.PlainJournal.from_journal(old_journal)) @@ -100,8 +100,9 @@ def upgrade_jrnl_if_necessary(config_path): failed_journals = [j for j in all_journals if not j.validate_parsing()] if len(failed_journals) > 0: - util.prompt("\nThe following journal{} failed to upgrade:\n{}".format( - 's' if len(failed_journals) > 1 else '', "\n".join(j.name for j in failed_journals)) + print("\nThe following journal{} failed to upgrade:\n{}".format( + 's' if len(failed_journals) > 1 else '', "\n".join(j.name for j in failed_journals)), + file=sys.stderr ) raise UpgradeValidationException @@ -110,10 +111,11 @@ def upgrade_jrnl_if_necessary(config_path): for j in all_journals: j.write() - util.prompt("\nUpgrading config...") + print("\nUpgrading config...", file=sys.stderr) backup(config_path) - util.prompt("\nWe're all done here and you can start enjoying jrnl 2.".format(config_path)) + print("\nWe're all done here and you can start enjoying jrnl 2.".format(config_path), file=sys.stderr) + class UpgradeValidationException(Exception): """Raised when the contents of an upgraded journal do not match the old journal""" diff --git a/jrnl/util.py b/jrnl/util.py index bc36ba9b4..7b5e2b5c1 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -1,9 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -from __future__ import unicode_literals -from __future__ import absolute_import - import sys import os import getpass as gp @@ -21,15 +18,6 @@ log = logging.getLogger(__name__) - -PY3 = sys.version_info[0] == 3 -PY2 = sys.version_info[0] == 2 -STDIN = sys.stdin -STDERR = sys.stderr -STDOUT = sys.stdout -TEST = False -__cached_tz = None - WARNING_COLOR = "\033[33m" ERROR_COLOR = "\033[31m" RESET_COLOR = "\033[0m" @@ -44,18 +32,14 @@ \s+ # a sequence of required spaces. | # Otherwise, \n # a sentence also terminates newlines. -)""", re.UNICODE | re.VERBOSE) +)""", re.VERBOSE) class UserAbort(Exception): pass -def getpass(prompt="Password: "): - if not TEST: - return gp.getpass(bytes(prompt)) - else: - return py23_input(prompt) +getpass = gp.getpass def get_password(validator, keychain=None, max_attempts=3): @@ -67,14 +51,14 @@ def get_password(validator, keychain=None, max_attempts=3): set_keychain(keychain, None) attempt = 1 while result is None and attempt < max_attempts: - prompt("Wrong password, try again.") - password = getpass() + print("Wrong password, try again.", file=sys.stderr) + password = gp.getpass() result = validator(password) attempt += 1 if result is not None: return result else: - prompt("Extremely wrong password.") + print("Extremely wrong password.", file=sys.stderr) sys.exit(1) @@ -88,57 +72,16 @@ def set_keychain(journal_name, password): if password is None: try: keyring.delete_password('jrnl', journal_name) - except: + except RuntimeError: pass - elif not TEST: + else: keyring.set_password('jrnl', journal_name, password) -def u(s): - """Mock unicode function for python 2 and 3 compatibility.""" - if not isinstance(s, str): - s = str(s) - return s if PY3 or type(s) is unicode else s.decode("utf-8") - - -def py2encode(s): - """Encodes to UTF-8 in Python 2 but not in Python 3.""" - return s.encode("utf-8") if PY2 and type(s) is unicode else s - - -def bytes(s): - """Returns bytes, no matter what.""" - if PY3: - return s.encode("utf-8") if type(s) is not bytes else s - return s.encode("utf-8") if type(s) is unicode else s - - -def prnt(s): - """Encode and print a string""" - STDOUT.write(u(s + "\n")) - - -def prompt(msg): - """Prints a message to the std err stream defined in util.""" - if not msg.endswith("\n"): - msg += "\n" - STDERR.write(u(msg)) - - -def py23_input(msg=""): - prompt(msg) - return STDIN.readline().strip() - - -def py23_read(msg=""): - print(msg) - return STDIN.read() - - def yesno(prompt, default=True): - prompt = prompt.strip() + (" [Y/n]" if default else " [y/N]") - raw = py23_input(prompt) - return {'y': True, 'n': False}.get(raw.lower(), default) + prompt = f"{prompt.strip()} {'[Y/n]' if default else '[y/N]'}" + response = input(prompt) + return {"y": True, "n": False}.get(response.lower(), default) def load_config(config_path): @@ -176,7 +119,7 @@ def get_text_from_editor(config, template=""): os.close(filehandle) os.remove(tmpfile) if not raw: - prompt('[Nothing saved to file]') + print('[Nothing saved to file]', file=sys.stderr) return raw @@ -188,27 +131,11 @@ def colorize(string): def slugify(string): """Slugifies a string. Based on public domain code from https://github.com/zacharyvoase/slugify - and ported to deal with all kinds of python 2 and 3 strings """ - string = u(string) - ascii_string = str(unicodedata.normalize('NFKD', string).encode('ascii', 'ignore')) - if PY3: - ascii_string = ascii_string[1:] # removed the leading 'b' - no_punctuation = re.sub(r'[^\w\s-]', '', ascii_string).strip().lower() + normalized_string = str(unicodedata.normalize('NFKD', string)) + no_punctuation = re.sub(r'[^\w\s-]', '', normalized_string).strip().lower() slug = re.sub(r'[-\s]+', '-', no_punctuation) - return u(slug) - - -def int2byte(i): - """Converts an integer to a byte. - This is equivalent to chr() in Python 2 and bytes((i,)) in Python 3.""" - return chr(i) if PY2 else bytes((i,)) - - -def byte2int(b): - """Converts a byte to an integer. - This is equivalent to ord(bs[0]) on Python 2 and bs[0] on Python 3.""" - return ord(b)if PY2 else b + return slug def split_title(text): From 1c403904e57a9b2f9c83d51740003e183a598183 Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Fri, 1 Nov 2019 10:37:12 +0100 Subject: [PATCH 2/9] fstring wip --- jrnl/DayOneJournal.py | 2 +- jrnl/Journal.py | 2 +- jrnl/cli.py | 2 +- jrnl/plugins/markdown_exporter.py | 12 ++++-------- jrnl/plugins/template_exporter.py | 2 +- jrnl/plugins/text_exporter.py | 4 ++-- jrnl/util.py | 2 +- 7 files changed, 11 insertions(+), 15 deletions(-) diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index ccec528a8..30846f71a 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -82,7 +82,7 @@ def write(self): def editable_str(self): """Turns the journal into a string of entries that can be edited manually and later be parsed with eslf.parse_editable_str.""" - return "\n".join(["# {0}\n{1}".format(e.uuid, str(e)) for e in self.entries]) + return "\n".join([f"# {e.uuid}\n{str(e)}" for e in self.entries]) def parse_editable_str(self, edited): """Parses the output of self.editable_str and updates its entries.""" diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 35ae801e6..aeab73857 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -23,7 +23,7 @@ def __str__(self): return self.name def __repr__(self): - return "".format(self.name) + return f"" class Journal: diff --git a/jrnl/cli.py b/jrnl/cli.py index 1bcdbae6a..464c8c39e 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -144,7 +144,7 @@ def run(manual_args=None): try: config = install.load_or_install_jrnl() except UserAbort as err: - print("\n{}".format(err), file=sys.stderr) + print(f"\n{err}", file=sys.stderr) sys.exit(1) if args.ls: diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index 0452a5f84..da2b57482 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -50,15 +50,11 @@ def export_entry(cls, entry, to_multifile=True): newbody = newbody + previous_line # add very last line if warn_on_heading_level is True: - print("{}WARNING{}: Headings increased past H6 on export - {} {}".format(WARNING_COLOR, RESET_COLOR, date_str, entry.title), file=sys.stderr) + print(f"{WARNING_COLOR}WARNING{RESET_COLOR}: " + f"Headings increased past H6 on export - {date_str} {entry.title}", + file=sys.stderr) - return "{md} {date} {title}\n{body} {space}".format( - md=heading, - date=date_str, - title=entry.title, - body=newbody, - space="" - ) + return f"{heading} {date_str} {entry.title}\n{newbody} " @classmethod def export_journal(cls, journal): diff --git a/jrnl/plugins/template_exporter.py b/jrnl/plugins/template_exporter.py index ecb9ac87d..4ae618bad 100644 --- a/jrnl/plugins/template_exporter.py +++ b/jrnl/plugins/template_exporter.py @@ -34,7 +34,7 @@ def __exporter_from_file(template_file): """Create a template class from a file""" name = os.path.basename(template_file).replace(".template", "") template = Template.from_file(template_file) - return type(str("{}Exporter".format(name.title())), (GenericTemplateExporter, ), { + return type(str(f"{name.title(}Exporter")), (GenericTemplateExporter, ), { "names": [name], "extension": template.extension, "template": template diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index f8ade5197..c72e8d939 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -28,9 +28,9 @@ def write_file(cls, journal, path): try: with codecs.open(path, "w", "utf-8") as f: f.write(cls.export_journal(journal)) - return "[Journal exported to {0}]".format(path) + return f"[Journal exported to {path}]" except IOError as e: - return "[{2}ERROR{3}: {0} {1}]".format(e.filename, e.strerror, ERROR_COLOR, RESET_COLOR) + return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]" @classmethod def make_filename(cls, entry): diff --git a/jrnl/util.py b/jrnl/util.py index 7b5e2b5c1..52f7e4c4f 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -125,7 +125,7 @@ def get_text_from_editor(config, template=""): def colorize(string): """Returns the string wrapped in cyan ANSI escape""" - return u"\033[36m{}\033[39m".format(string) + return f"\033[36m{string}\033[39m" def slugify(string): From 30caf9cae2d2dfeee21c088705897b138ed8390e Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Fri, 1 Nov 2019 11:16:12 +0100 Subject: [PATCH 3/9] Run pyupgrade --- jrnl/DayOneJournal.py | 5 ++--- jrnl/EncryptedJournal.py | 6 +++--- jrnl/Entry.py | 7 +++---- jrnl/Journal.py | 19 +++++++++---------- jrnl/__init__.py | 1 - jrnl/__main__.py | 1 - jrnl/cli.py | 25 ++++++++++++------------- jrnl/export.py | 15 +++++++-------- jrnl/install.py | 6 ++---- jrnl/time.py | 2 +- jrnl/upgrade.py | 12 ++++++------ jrnl/util.py | 1 - 12 files changed, 45 insertions(+), 55 deletions(-) diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index 30846f71a..59314c4b9 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 from . import Entry from . import Journal @@ -25,7 +24,7 @@ class DayOne(Journal.Journal): def __init__(self, **kwargs): self.entries = [] self._deleted_entries = [] - super(DayOne, self).__init__(**kwargs) + super().__init__(**kwargs) def open(self): filenames = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))] @@ -106,7 +105,7 @@ def parse_editable_str(self, edited): current_entry.modified = False current_entry.uuid = m.group(1).lower() else: - date_blob_re = re.compile("^\[[^\\]]+\] ") + date_blob_re = re.compile("^\\[[^\\]]+\\] ") date_blob = date_blob_re.findall(line) if date_blob: date_blob = date_blob[0] diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index 67345f452..1cce66b81 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -29,7 +29,7 @@ def make_key(password): class EncryptedJournal(Journal.Journal): def __init__(self, name='default', **kwargs): - super(EncryptedJournal, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.config['encrypt'] = True def open(self, filename=None): @@ -47,7 +47,7 @@ def open(self, filename=None): self.config['password'] = password text = "" self._store(filename, text) - print("[Journal '{0}' created at {1}]".format(self.name, filename), file=sys.stderr) + print(f"[Journal '{self.name}' created at {filename}]", file=sys.stderr) else: print("No password supplied for encrypted journal", file=sys.stderr) sys.exit(1) @@ -97,7 +97,7 @@ class LegacyEncryptedJournal(Journal.LegacyJournal): """Legacy class to support opening journals encrypted with the jrnl 1.x standard. You'll not be able to save these journals anymore.""" def __init__(self, name='default', **kwargs): - super(LegacyEncryptedJournal, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.config['encrypt'] = True def _load(self, filename, password=None): diff --git a/jrnl/Entry.py b/jrnl/Entry.py index cf012e800..ca80b231e 100755 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 import re import textwrap @@ -50,12 +49,12 @@ def tags(self): @staticmethod def tag_regex(tagsymbols): - pattern = r'(?u)(?:^|\s)([{tags}][-+*#/\w]+)'.format(tags=tagsymbols) + pattern = fr'(?u)(?:^|\s)([{tagsymbols}][-+*#/\w]+)' return re.compile(pattern) def _parse_tags(self): tagsymbols = self.journal.config['tagsymbols'] - return set(tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text)) + return {tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text)} def __str__(self): """Returns a string representation of the entry to be written into a journal file.""" @@ -105,7 +104,7 @@ def pprint(self, short=False): ) def __repr__(self): - return "".format(self.title.strip(), self.date.strftime("%Y-%m-%d %H:%M")) + return "".format(self.title.strip(), self.date.strftime("%Y-%m-%d %H:%M")) def __hash__(self): return hash(self.__repr__()) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index aeab73857..e5f87e037 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 from . import Entry from . import util @@ -71,7 +70,7 @@ def open(self, filename=None): filename = filename or self.config['journal'] if not os.path.exists(filename): - print("[Journal '{0}' created at {1}]".format(self.name, filename), file=sys.stderr) + print(f"[Journal '{self.name}' created at {filename}]", file=sys.stderr) self._create(filename) text = self._load(filename) @@ -117,7 +116,7 @@ def _parse(self, journal_txt): # Initialise our current entry entries = [] - date_blob_re = re.compile("(?:^|\n)\[([^\\]]+)\] ") + date_blob_re = re.compile("(?:^|\n)\\[([^\\]]+)\\] ") last_entry_pos = 0 for match in date_blob_re.finditer(journal_txt): date_blob = match.groups()[0] @@ -162,7 +161,7 @@ def __str__(self): return self.pprint() def __repr__(self): - return "".format(len(self.entries)) + return f"" def sort(self): """Sorts the Journal's entries by date""" @@ -182,7 +181,7 @@ def tags(self): for entry in self.entries for tag in set(entry.tags)] # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] - tag_counts = set([(tags.count(tag), tag) for tag in tags]) + tag_counts = {(tags.count(tag), tag) for tag in tags} return [Tag(tag, count=count) for count, tag in sorted(tag_counts)] def filter(self, tags=[], start_date=None, end_date=None, starred=False, strict=False, short=False, exclude=[]): @@ -199,8 +198,8 @@ def filter(self, tags=[], start_date=None, end_date=None, starred=False, strict= exclude is a list of the tags which should not appear in the results. entry is kept if any tag is present, unless they appear in exclude.""" - self.search_tags = set([tag.lower() for tag in tags]) - excluded_tags = set([tag.lower() for tag in exclude]) + self.search_tags = {tag.lower() for tag in tags} + excluded_tags = {tag.lower() for tag in exclude} end_date = time.parse(end_date, inclusive=True) start_date = time.parse(start_date) @@ -225,7 +224,7 @@ def new_entry(self, raw, date=None, sort=True): raw = raw.replace('\\n ', '\n').replace('\\n', '\n') starred = False # Split raw text into title and body - sep = re.search("\n|[\?!.]+ +\n?", raw) + sep = re.search("\n|[\\?!.]+ +\n?", raw) first_line = raw[:sep.end()].strip() if sep else raw starred = False @@ -322,7 +321,7 @@ def _parse(self, journal_txt): # escaping for the new format). line = new_date_format_regex.sub(r' \1', line) if current_entry: - current_entry.text += line + u"\n" + current_entry.text += line + "\n" # Append last entry if current_entry: @@ -347,7 +346,7 @@ def open_journal(name, config, legacy=False): return DayOneJournal.DayOne(**config).open() else: print( - u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']), + f"[Error: {config['journal']} is a directory, but doesn't seem to be a DayOne journal either.", file=sys.stderr ) diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 57664dbb5..1905b1959 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 import pkg_resources diff --git a/jrnl/__main__.py b/jrnl/__main__.py index b01d9ff47..3ce867303 100644 --- a/jrnl/__main__.py +++ b/jrnl/__main__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 from . import cli diff --git a/jrnl/cli.py b/jrnl/cli.py index 464c8c39e..3cf670309 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 """ jrnl @@ -89,7 +88,7 @@ def encrypt(journal, filename=None): if util.yesno("Do you want to store the password in your keychain?", default=True): util.set_keychain(journal.name, journal.config['password']) - print("Journal encrypted to {0}.".format(filename or new_journal.config['journal']), file=sys.stderr) + print("Journal encrypted to {}.".format(filename or new_journal.config['journal']), file=sys.stderr) def decrypt(journal, filename=None): @@ -100,12 +99,12 @@ def decrypt(journal, filename=None): new_journal = Journal.PlainJournal(filename, **journal.config) new_journal.entries = journal.entries new_journal.write(filename) - print("Journal decrypted to {0}.".format(filename or new_journal.config['journal']), file=sys.stderr) + print("Journal decrypted to {}.".format(filename or new_journal.config['journal']), file=sys.stderr) def list_journals(config): """List the journals specified in the configuration file""" - result = "Journals defined in {}\n".format(install.CONFIG_FILE_PATH) + result = f"Journals defined in {install.CONFIG_FILE_PATH}\n" ml = min(max(len(k) for k in config['journals']), 20) for journal, cfg in config['journals'].items(): result += " * {:{}} -> {}\n".format(journal, ml, cfg['journal'] if isinstance(cfg, dict) else cfg) @@ -137,7 +136,7 @@ def run(manual_args=None): args = parse_args(manual_args) configure_logger(args.debug) if args.version: - version_str = "{0} version {1}".format(jrnl.__title__, jrnl.__version__) + version_str = f"{jrnl.__title__} version {jrnl.__version__}" print(version_str) sys.exit(0) @@ -158,7 +157,7 @@ def run(manual_args=None): # use this! journal_name = args.text[0] if (args.text and args.text[0] in config['journals']) else 'default' - if journal_name is not 'default': + if journal_name != 'default': args.text = args.text[1:] elif "default" not in config['journals']: print("No default journal configured.", file=sys.stderr) @@ -193,8 +192,8 @@ def run(manual_args=None): if config['template']: try: template = open(config['template']).read() - except IOError: - print("[Could not read template at '']".format(config['template']), file=sys.stderr) + except OSError: + print(f"[Could not read template at '{config['template']}']", file=sys.stderr) sys.exit(1) raw = util.get_text_from_editor(config, template) else: @@ -213,7 +212,7 @@ def run(manual_args=None): try: journal = Journal.open_journal(journal_name, config) except KeyboardInterrupt: - print("[Interrupted while opening journal]".format(journal_name), file=sys.stderr) + print(f"[Interrupted while opening journal]", file=sys.stderr) sys.exit(1) # Import mode @@ -225,7 +224,7 @@ def run(manual_args=None): raw = " ".join(args.text).strip() log.debug('Appending raw line "%s" to journal "%s"', raw, journal_name) journal.new_entry(raw) - print("[Entry added to {0} journal]".format(journal_name), file=sys.stderr) + print(f"[Entry added to {journal_name} journal]", file=sys.stderr) journal.write() if not mode_compose: @@ -283,11 +282,11 @@ def run(manual_args=None): num_edited = len([e for e in journal.entries if e.modified]) prompts = [] if num_deleted: - prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries")) + prompts.append("{} {} 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("{} {} modified".format(num_edited, "entry" if num_deleted == 1 else "entries")) if prompts: - print("[{0}]".format(", ".join(prompts).capitalize()), file=sys.stderr) + print("[{}]".format(", ".join(prompts).capitalize()), file=sys.stderr) journal.entries += other_entries journal.sort() journal.write() diff --git a/jrnl/export.py b/jrnl/export.py index a69ee00e1..399f2db01 100644 --- a/jrnl/export.py +++ b/jrnl/export.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 from .util import ERROR_COLOR, RESET_COLOR from .util import slugify @@ -35,12 +34,12 @@ def write_file(self, journal, path): try: with codecs.open(path, "w", "utf-8") as f: f.write(self.export_journal(journal)) - return "[Journal exported to {0}]".format(path) - except IOError as e: - return "[{2}ERROR{3}: {0} {1}]".format(e.filename, e.strerror, ERROR_COLOR, RESET_COLOR) + return f"[Journal exported to {path}]" + except OSError as e: + return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]" def make_filename(self, entry): - return entry.date.strftime("%Y-%m-%d_{0}.{1}".format(slugify(entry.title), self.extension)) + return entry.date.strftime("%Y-%m-%d_{}.{}".format(slugify(entry.title), self.extension)) def write_files(self, journal, path): """Exports a journal into individual files for each entry.""" @@ -49,9 +48,9 @@ def write_files(self, journal, path): full_path = os.path.join(path, self.make_filename(entry)) with codecs.open(full_path, "w", "utf-8") as f: f.write(self.export_entry(entry)) - except IOError as e: - return "[{2}ERROR{3}: {0} {1}]".format(e.filename, e.strerror, ERROR_COLOR, RESET_COLOR) - return "[Journal exported to {0}]".format(path) + except OSError as e: + return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]" + return f"[Journal exported to {path}]" def export(self, journal, format="text", output=None): """Exports to individual files if output is an existing path, or into diff --git a/jrnl/install.py b/jrnl/install.py index 08bb44ddb..c104b46b2 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -# encoding: utf-8 -from __future__ import absolute_import import readline import glob import getpass @@ -69,7 +67,7 @@ def upgrade_config(config): for key in missing_keys: config[key] = default_config[key] save_config(config) - print("[Configuration updated to newest version at {}]".format(CONFIG_FILE_PATH), file=sys.stderr) + print(f"[Configuration updated to newest version at {CONFIG_FILE_PATH}]", file=sys.stderr) def save_config(config): @@ -120,7 +118,7 @@ def autocomplete(text, state): readline.set_completer(autocomplete) # Where to create the journal? - path_query = 'Path to your journal file (leave blank for {}): '.format(JOURNAL_FILE_PATH) + path_query = f'Path to your journal file (leave blank for {JOURNAL_FILE_PATH}): ' journal_path = input(path_query).strip() or JOURNAL_FILE_PATH default_config['journals']['default'] = os.path.expanduser(os.path.expandvars(journal_path)) diff --git a/jrnl/time.py b/jrnl/time.py index 9ff125aa9..eee3fc207 100644 --- a/jrnl/time.py +++ b/jrnl/time.py @@ -51,7 +51,7 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None): except TypeError: return None - if flag is 1: # Date found, but no time. Use the default time. + if flag == 1: # Date found, but no time. Use the default time. date = datetime(*date[:3], hour=default_hour or 0, minute=default_minute or 0) else: date = datetime(*date[:6]) diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 2a5c1192f..d1b838093 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -10,7 +10,7 @@ def backup(filename, binary=False): - print(" Created a backup at {}.backup".format(filename), file=sys.stderr) + print(f" Created a backup at {filename}.backup", file=sys.stderr) filename = os.path.expanduser(os.path.expandvars(filename)) with open(filename, 'rb' if binary else 'r') as original: contents = original.read() @@ -63,12 +63,12 @@ def upgrade_jrnl_if_necessary(config_path): longest_journal_name = max([len(journal) for journal in config['journals']]) if encrypted_journals: - print("\nFollowing encrypted journals will be upgraded to jrnl {}:".format(__version__), file=sys.stderr) + print(f"\nFollowing encrypted journals will be upgraded to jrnl {__version__}:", file=sys.stderr) for journal, path in encrypted_journals.items(): print(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), file=sys.stderr) if plain_journals: - print("\nFollowing plain text journals will upgraded to jrnl {}:".format(__version__), file=sys.stderr) + print(f"\nFollowing plain text journals will upgraded to jrnl {__version__}:", file=sys.stderr) for journal, path in plain_journals.items(): print(" {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), file=sys.stderr) @@ -85,13 +85,13 @@ def upgrade_jrnl_if_necessary(config_path): raise UserAbort("jrnl NOT upgraded, exiting.") for journal_name, path in encrypted_journals.items(): - print("\nUpgrading encrypted '{}' journal stored in {}...".format(journal_name, path), file=sys.stderr) + print(f"\nUpgrading encrypted '{journal_name}' journal stored in {path}...", file=sys.stderr) backup(path, binary=True) old_journal = Journal.open_journal(journal_name, util.scope_config(config, journal_name), legacy=True) all_journals.append(EncryptedJournal.from_journal(old_journal)) for journal_name, path in plain_journals.items(): - print("\nUpgrading plain text '{}' journal stored in {}...".format(journal_name, path), file=sys.stderr) + print(f"\nUpgrading plain text '{journal_name}' journal stored in {path}...", file=sys.stderr) backup(path) old_journal = Journal.open_journal(journal_name, util.scope_config(config, journal_name), legacy=True) all_journals.append(Journal.PlainJournal.from_journal(old_journal)) @@ -114,7 +114,7 @@ def upgrade_jrnl_if_necessary(config_path): print("\nUpgrading config...", file=sys.stderr) backup(config_path) - print("\nWe're all done here and you can start enjoying jrnl 2.".format(config_path), file=sys.stderr) + print("\nWe're all done here and you can start enjoying jrnl 2.", file=sys.stderr) class UpgradeValidationException(Exception): diff --git a/jrnl/util.py b/jrnl/util.py index 52f7e4c4f..d2bdf9c47 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding: utf-8 import sys import os From dace253513dc0c750c6f5a223302262ca4f9b26e Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Fri, 1 Nov 2019 13:16:10 +0100 Subject: [PATCH 4/9] fix broken pyupgrade fstring --- jrnl/plugins/template_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/plugins/template_exporter.py b/jrnl/plugins/template_exporter.py index 4ae618bad..f15328f23 100644 --- a/jrnl/plugins/template_exporter.py +++ b/jrnl/plugins/template_exporter.py @@ -34,7 +34,7 @@ def __exporter_from_file(template_file): """Create a template class from a file""" name = os.path.basename(template_file).replace(".template", "") template = Template.from_file(template_file) - return type(str(f"{name.title(}Exporter")), (GenericTemplateExporter, ), { + return type(str(f"{name.title()}Exporter"), (GenericTemplateExporter, ), { "names": [name], "extension": template.extension, "template": template From 5cee4fb783fbc47a48a7c5df33595c1200ee4900 Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Fri, 1 Nov 2019 19:57:24 +0100 Subject: [PATCH 5/9] run pyupgrade on plugin dir --- jrnl/plugins/__init__.py | 4 ++-- jrnl/plugins/jrnl_importer.py | 2 +- jrnl/plugins/json_exporter.py | 2 +- jrnl/plugins/tag_exporter.py | 2 +- jrnl/plugins/text_exporter.py | 4 ++-- jrnl/plugins/util.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/jrnl/plugins/__init__.py b/jrnl/plugins/__init__.py index bb2ea1769..8d59b5562 100644 --- a/jrnl/plugins/__init__.py +++ b/jrnl/plugins/__init__.py @@ -13,8 +13,8 @@ __exporters =[JSONExporter, MarkdownExporter, TagExporter, TextExporter, XMLExporter, YAMLExporter] + template_exporters __importers =[JRNLImporter] -__exporter_types = dict([(name, plugin) for plugin in __exporters for name in plugin.names]) -__importer_types = dict([(name, plugin) for plugin in __importers for name in plugin.names]) +__exporter_types = {name: plugin for plugin in __exporters for name in plugin.names} +__importer_types = {name: plugin for plugin in __importers for name in plugin.names} EXPORT_FORMATS = sorted(__exporter_types.keys()) IMPORT_FORMATS = sorted(__importer_types.keys()) diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index 1015c2620..e2145232c 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -26,5 +26,5 @@ def import_(journal, input=None): sys.exit(0) journal.import_(other_journal_txt) new_cnt = len(journal.entries) - print("[{0} imported to {1} journal]".format(new_cnt - old_cnt, journal.name), file=sys.stderr) + print("[{} imported to {} journal]".format(new_cnt - old_cnt, journal.name), file=sys.stderr) journal.write() diff --git a/jrnl/plugins/json_exporter.py b/jrnl/plugins/json_exporter.py index e6591302a..e368a300b 100644 --- a/jrnl/plugins/json_exporter.py +++ b/jrnl/plugins/json_exporter.py @@ -34,7 +34,7 @@ def export_journal(cls, journal): """Returns a json representation of an entire journal.""" tags = get_tags_count(journal) result = { - "tags": dict((tag, count) for count, tag in tags), + "tags": {tag: count for count, tag in tags}, "entries": [cls.entry_to_dict(e) for e in journal.entries] } return json.dumps(result, indent=2) diff --git a/jrnl/plugins/tag_exporter.py b/jrnl/plugins/tag_exporter.py index 269a762de..f5453ced8 100644 --- a/jrnl/plugins/tag_exporter.py +++ b/jrnl/plugins/tag_exporter.py @@ -25,5 +25,5 @@ def export_journal(cls, journal): elif min(tag_counts)[0] == 0: tag_counts = filter(lambda x: x[0] > 1, tag_counts) result += '[Removed tags that appear only once.]\n' - result += "\n".join("{0:20} : {1}".format(tag, n) for n, tag in sorted(tag_counts, reverse=True)) + result += "\n".join("{:20} : {}".format(tag, n) for n, tag in sorted(tag_counts, reverse=True)) return result diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index c72e8d939..687de750b 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -34,7 +34,7 @@ def write_file(cls, journal, path): @classmethod def make_filename(cls, entry): - return entry.date.strftime("%Y-%m-%d_{0}.{1}".format(slugify(str(entry.title)), cls.extension)) + return entry.date.strftime("%Y-%m-%d_{}.{}".format(slugify(str(entry.title)), cls.extension)) @classmethod def write_files(cls, journal, path): @@ -46,7 +46,7 @@ def write_files(cls, journal, path): f.write(cls.export_entry(entry)) except IOError as e: return "[{2}ERROR{3}: {0} {1}]".format(e.filename, e.strerror, ERROR_COLOR, RESET_COLOR) - return "[Journal exported to {0}]".format(path) + return "[Journal exported to {}]".format(path) @classmethod def export(cls, journal, output=None): diff --git a/jrnl/plugins/util.py b/jrnl/plugins/util.py index 0a642cb2e..a056b19ab 100644 --- a/jrnl/plugins/util.py +++ b/jrnl/plugins/util.py @@ -10,7 +10,7 @@ def get_tags_count(journal): for entry in journal.entries for tag in set(entry.tags)] # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] - tag_counts = set([(tags.count(tag), tag) for tag in tags]) + tag_counts = {(tags.count(tag), tag) for tag in tags} return tag_counts From d47e1ed4799139ccc44c8dc6d1ffd95964ee9fab Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Fri, 1 Nov 2019 19:58:32 +0100 Subject: [PATCH 6/9] fixup! remove py2 remnants and use mocks in tests --- jrnl/Journal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index e5f87e037..9ecc2515d 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -224,7 +224,7 @@ def new_entry(self, raw, date=None, sort=True): raw = raw.replace('\\n ', '\n').replace('\\n', '\n') starred = False # Split raw text into title and body - sep = re.search("\n|[\\?!.]+ +\n?", raw) + sep = re.search(r"\n|[?!.]+ +\n?", raw) first_line = raw[:sep.end()].strip() if sep else raw starred = False From 65adb92ed4b316081ecfb9d348e0aecb3813f2c8 Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Fri, 1 Nov 2019 20:05:06 +0100 Subject: [PATCH 7/9] small print bugfix The file=sys.stderr was part of the format(), so an error got printed to stdout --- jrnl/plugins/yaml_exporter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index eec9b0b3d..4a75667f7 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -17,7 +17,8 @@ class YAMLExporter(TextExporter): def export_entry(cls, entry, to_multifile=True): """Returns a markdown representation of a single entry, with YAML front matter.""" if to_multifile is False: - print("{}ERROR{}: YAML export must be to individual files. Please specify a directory to export to.".format("\033[31m", "\033[0m", file=sys.stderr)) + print("{}ERROR{}: YAML export must be to individual files. " + "Please specify a directory to export to.".format("\033[31m", "\033[0m"), file=sys.stderr) return date_str = entry.date.strftime(entry.journal.config['timeformat']) From 827a598dd870854820fac0f81ed92bda98e07cce Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Fri, 1 Nov 2019 21:48:04 +0100 Subject: [PATCH 8/9] Drop use of codecs package Use builtins.open() instead --- jrnl/Journal.py | 9 ++++----- jrnl/export.py | 5 ++--- jrnl/plugins/jrnl_importer.py | 3 +-- jrnl/plugins/text_exporter.py | 5 ++--- jrnl/upgrade.py | 3 +-- jrnl/util.py | 5 ++--- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 9ecc2515d..f130823d6 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -5,7 +5,6 @@ from . import time import os import sys -import codecs import re from datetime import datetime import logging @@ -268,15 +267,15 @@ def parse_editable_str(self, edited): class PlainJournal(Journal): @classmethod def _create(cls, filename): - with codecs.open(filename, "a", "utf-8"): + with open(filename, "a", encoding="utf-8"): pass def _load(self, filename): - with codecs.open(filename, "r", "utf-8") as f: + with open(filename, "r", encoding="utf-8") as f: return f.read() def _store(self, filename, text): - with codecs.open(filename, 'w', "utf-8") as f: + with open(filename, 'w', encoding="utf-8") as f: f.write(text) @@ -285,7 +284,7 @@ class LegacyJournal(Journal): standard. Main difference here is that in 1.x, timestamps were not cuddled by square brackets. You'll not be able to save these journals anymore.""" def _load(self, filename): - with codecs.open(filename, "r", "utf-8") as f: + with open(filename, "r", encoding="utf-8") as f: return f.read() def _parse(self, journal_txt): diff --git a/jrnl/export.py b/jrnl/export.py index 399f2db01..1ee4e6ffa 100644 --- a/jrnl/export.py +++ b/jrnl/export.py @@ -4,7 +4,6 @@ from .util import slugify from .plugins.template import Template import os -import codecs class Exporter: @@ -32,7 +31,7 @@ def export_journal(self, journal): def write_file(self, journal, path): """Exports a journal into a single file.""" try: - with codecs.open(path, "w", "utf-8") as f: + with open(path, "w", encoding="utf-8") as f: f.write(self.export_journal(journal)) return f"[Journal exported to {path}]" except OSError as e: @@ -46,7 +45,7 @@ def write_files(self, journal, path): for entry in journal.entries: try: full_path = os.path.join(path, self.make_filename(entry)) - with codecs.open(full_path, "w", "utf-8") as f: + with open(full_path, "w", encoding="utf-8") as f: f.write(self.export_entry(entry)) except OSError as e: return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]" diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index e2145232c..83341cd94 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -import codecs import sys from .. import util @@ -16,7 +15,7 @@ def import_(journal, input=None): old_cnt = len(journal.entries) old_entries = journal.entries if input: - with codecs.open(input, "r", "utf-8") as f: + with open(input, "r", encoding="utf-8") as f: other_journal_txt = f.read() else: try: diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index 687de750b..ce2e71de0 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 -import codecs from ..util import slugify import os from ..util import ERROR_COLOR, RESET_COLOR @@ -26,7 +25,7 @@ def export_journal(cls, journal): def write_file(cls, journal, path): """Exports a journal into a single file.""" try: - with codecs.open(path, "w", "utf-8") as f: + with open(path, "w", encoding="utf-8") as f: f.write(cls.export_journal(journal)) return f"[Journal exported to {path}]" except IOError as e: @@ -42,7 +41,7 @@ def write_files(cls, journal, path): for entry in journal.entries: try: full_path = os.path.join(path, cls.make_filename(entry)) - with codecs.open(full_path, "w", "utf-8") as f: + with open(full_path, "w", encoding="utf-8") as f: f.write(cls.export_entry(entry)) except IOError as e: return "[{2}ERROR{3}: {0} {1}]".format(e.filename, e.strerror, ERROR_COLOR, RESET_COLOR) diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index d1b838093..216987a0e 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -6,7 +6,6 @@ from .EncryptedJournal import EncryptedJournal from .util import UserAbort import os -import codecs def backup(filename, binary=False): @@ -19,7 +18,7 @@ def backup(filename, binary=False): def upgrade_jrnl_if_necessary(config_path): - with codecs.open(config_path, "r", "utf-8") as f: + with open(config_path, "r", encoding="utf-8") as f: config_file = f.read() if not config_file.strip().startswith("{"): return diff --git a/jrnl/util.py b/jrnl/util.py index d2bdf9c47..1290adc8d 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -10,7 +10,6 @@ import re import tempfile import subprocess -import codecs import unicodedata import shlex import logging @@ -106,14 +105,14 @@ def scope_config(config, journal_name): def get_text_from_editor(config, template=""): filehandle, tmpfile = tempfile.mkstemp(prefix="jrnl", text=True, suffix=".txt") - with codecs.open(tmpfile, 'w', "utf-8") as f: + with open(tmpfile, 'w', encoding="utf-8") as f: if template: f.write(template) try: subprocess.call(shlex.split(config['editor'], posix="win" not in sys.platform) + [tmpfile]) except AttributeError: subprocess.call(config['editor'] + [tmpfile]) - with codecs.open(tmpfile, "r", "utf-8") as f: + with open(tmpfile, "r", encoding="utf-8") as f: raw = f.read() os.close(filehandle) os.remove(tmpfile) From 7d9795ace6e105092a482db9f34d050f7e55337e Mon Sep 17 00:00:00 2001 From: Peter Schmidbauer Date: Mon, 4 Nov 2019 16:36:16 +0100 Subject: [PATCH 9/9] fixup! remove py2 remnants and use mocks in tests --- jrnl/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/util.py b/jrnl/util.py index 1290adc8d..959d63a17 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -77,7 +77,7 @@ def set_keychain(journal_name, password): def yesno(prompt, default=True): - prompt = f"{prompt.strip()} {'[Y/n]' if default else '[y/N]'}" + prompt = f"{prompt.strip()} {'[Y/n]' if default else '[y/N]'} " response = input(prompt) return {"y": True, "n": False}.get(response.lower(), default)