From 6cd8c35598f02022028163348472c1b99d68b7e7 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 22 Oct 2022 22:40:26 +0000 Subject: [PATCH 001/373] Update changelog [ci skip] --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e0fb42f..c86cf268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [Unreleased](https://github.com/jrnl-org/jrnl/) +## [v3.3-beta2](https://pypi.org/project/jrnl/v3.3-beta2/) (2022-10-22) -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta...HEAD) +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta...v3.3-beta2) **Implemented enhancements:** From c6479c70c139a6475317f58cb2dca7859867365e Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Sat, 29 Oct 2022 11:51:41 -0700 Subject: [PATCH 002/373] Add contextual link to external editors page from overview --- docs/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/overview.md b/docs/overview.md index df09d273..5ab25c1a 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -39,7 +39,7 @@ read them or edit them. `jrnl` plays nicely with your favorite text editor. You may prefer to write journal entries in an editor. Or you may want to make changes that require a more comprehensive application. `jrnl` can filter specific entries and pass them -to the external editor of your choice. +to the [external editor](./external-editors.md) of your choice. ## Encryption From 6f9a3eb5369fe95499f27d0c08e28bb57b93fdd9 Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Sat, 29 Oct 2022 12:14:28 -0700 Subject: [PATCH 003/373] Document that editors must be blocking processes (#1624) * Describe blocking processes in External Editors page * Add documentation about how the editor must be a blocking process * Add contextual link to external editor site * Point to micro editor as an example since it's likely less intimidating than vim --- docs/external-editors.md | 6 ++++-- docs/reference-config-file.md | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/external-editors.md b/docs/external-editors.md index be321bab..4e8f70b7 100644 --- a/docs/external-editors.md +++ b/docs/external-editors.md @@ -8,11 +8,13 @@ License: https://www.gnu.org/licenses/gpl-3.0.html Configure your preferred external editor by updating the `editor` option in your [configuration file](./reference-config-file.md#editor) +If your editor is not in your operating system's `PATH` environment variable, +then you will have to enter in the full path of your editor. + !!! note To save and log any entry edits, save and close the file. -If your editor is not in your operating system's `PATH` environment variable, -then you will have to enter in the full path of your editor. +All editors must be [blocking processes](https://en.wikipedia.org/wiki/Blocking_(computing)) to work with jrnl. Some editors, such as [micro](https://micro-editor.github.io/), are blocking by default, though others can be made to block with additional arguments, such as many of those documented below. If jrnl opens your editor but finishes running immediately, then your editor is not a blocking process, and you may be able to correct that with one of the suggestions below. ## Sublime Text diff --git a/docs/reference-config-file.md b/docs/reference-config-file.md index 3a057cdb..1c58a3a2 100644 --- a/docs/reference-config-file.md +++ b/docs/reference-config-file.md @@ -47,10 +47,11 @@ key will be used instead. If set, executes this command to launch an external editor for writing and editing your entries. The path to a temporary file is passed after it, and `jrnl` processes the file once -the editor is closed. +the editor returns control to `jrnl`. -Some editors require special options to work properly. See -[External Editors](external-editors.md) for details. +Some editors require special options to work properly, since they must be +blocking processes to work with `jrnl`. See [External Editors](external-editors.md) +for details. ### encrypt If `true`, encrypts your journal using AES. Do not change this From 81771eff4fa9e78ec955ee39324a3e0837198708 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 29 Oct 2022 19:16:11 +0000 Subject: [PATCH 004/373] Update changelog [ci skip] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c86cf268..ae8dc0c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [Unreleased](https://github.com/jrnl-org/jrnl/) + +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...HEAD) + +**Documentation:** + +- Add documentation about how the editor must be a blocking process [\#1456](https://github.com/jrnl-org/jrnl/issues/1456) + ## [v3.3-beta2](https://pypi.org/project/jrnl/v3.3-beta2/) (2022-10-22) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta...v3.3-beta2) From 415fc336ec2648a3462b4ccb29425c59f00ae7a5 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 29 Oct 2022 19:19:53 +0000 Subject: [PATCH 005/373] Increment version to v3.3 --- jrnl/__version__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jrnl/__version__.py b/jrnl/__version__.py index 09700b5f..3d0bbe9b 100644 --- a/jrnl/__version__.py +++ b/jrnl/__version__.py @@ -1 +1 @@ -__version__ = "v3.3-beta2" +__version__ = "v3.3" diff --git a/pyproject.toml b/pyproject.toml index 6bc38d94..3ed885c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jrnl" -version = "v3.3-beta2" +version = "v3.3" description = "Collect your thoughts and notes without leaving the command line." authors = [ "jrnl contributors ", From 60590e0c49788ea91d54144b67efee49d91f31c2 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 29 Oct 2022 19:27:02 +0000 Subject: [PATCH 006/373] Update changelog [ci skip] --- CHANGELOG.md | 40 ++++++---------------------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae8dc0c3..02a19981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,50 +1,21 @@ # Changelog -## [Unreleased](https://github.com/jrnl-org/jrnl/) +## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...HEAD) - -**Documentation:** - -- Add documentation about how the editor must be a blocking process [\#1456](https://github.com/jrnl-org/jrnl/issues/1456) - -## [v3.3-beta2](https://pypi.org/project/jrnl/v3.3-beta2/) (2022-10-22) - -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta...v3.3-beta2) +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...v3.3) **Implemented enhancements:** -- Add machine-readable format for --list [\#1445](https://github.com/jrnl-org/jrnl/issues/1445) - -**Fixed bugs:** - -- Fix bug for new `--list --format` options when no default journal is specified [\#1621](https://github.com/jrnl-org/jrnl/pull/1621) ([wren](https://github.com/wren)) - -**Documentation:** - -- Documentation of "display\_format" incorrectly states that it can be used to change date display format [\#1617](https://github.com/jrnl-org/jrnl/issues/1617) -- Remove wrong option in configuration file reference [\#1618](https://github.com/jrnl-org/jrnl/pull/1618) ([DSiekmeier](https://github.com/DSiekmeier)) - -## [v3.3-beta](https://pypi.org/project/jrnl/v3.3-beta/) (2022-10-08) - -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.2...v3.3-beta) - -**Implemented enhancements:** - -- Add dependency security checks in CI [\#1488](https://github.com/jrnl-org/jrnl/issues/1488) -- Add machine-readable format for --list [\#1445](https://github.com/jrnl-org/jrnl/issues/1445) - Change default config to use journal key [\#1594](https://github.com/jrnl-org/jrnl/pull/1594) ([micahellison](https://github.com/micahellison)) - Add machine readable --list output [\#1592](https://github.com/jrnl-org/jrnl/pull/1592) ([apainintheneck](https://github.com/apainintheneck)) **Fixed bugs:** -- Bug Report - Sometimes jrnl crashes and truncates journal file [\#1599](https://github.com/jrnl-org/jrnl/issues/1599) -- Zero-length file created when attempting to export YAML to a directory that does not exist [\#1593](https://github.com/jrnl-org/jrnl/issues/1593) +- Fix bug for new `--list --format` options when no default journal is specified [\#1621](https://github.com/jrnl-org/jrnl/pull/1621) ([wren](https://github.com/wren)) - Don't create empty file when attempting a YAML export to a non-existing folder [\#1600](https://github.com/jrnl-org/jrnl/pull/1600) ([outa](https://github.com/outa)) **Build:** -- Replace Dependabot [\#1560](https://github.com/jrnl-org/jrnl/issues/1560) - Update `.gitignore` [\#1604](https://github.com/jrnl-org/jrnl/pull/1604) ([wren](https://github.com/wren)) - Fix Docs Accessibility Testing [\#1588](https://github.com/jrnl-org/jrnl/pull/1588) ([wren](https://github.com/wren)) - Update to use renamed flag for `brew bump-formula-pr` [\#1587](https://github.com/jrnl-org/jrnl/pull/1587) ([wren](https://github.com/wren)) @@ -56,8 +27,9 @@ **Documentation:** -- \[Documentation\] Edit on Github link broken [\#1601](https://github.com/jrnl-org/jrnl/issues/1601) -- Update `--format yaml` example in docs [\#1525](https://github.com/jrnl-org/jrnl/issues/1525) +- Add documentation about how the editor must be a blocking process [\#1456](https://github.com/jrnl-org/jrnl/issues/1456) +- Document that editors must be blocking processes [\#1624](https://github.com/jrnl-org/jrnl/pull/1624) ([micahellison](https://github.com/micahellison)) +- Remove wrong option in configuration file reference [\#1618](https://github.com/jrnl-org/jrnl/pull/1618) ([DSiekmeier](https://github.com/DSiekmeier)) - Update YAML export description in docs [\#1591](https://github.com/jrnl-org/jrnl/pull/1591) ([apainintheneck](https://github.com/apainintheneck)) - Update dependency jinja2 to v3.1.2 [\#1579](https://github.com/jrnl-org/jrnl/pull/1579) ([renovate[bot]](https://github.com/apps/renovate)) - Update dependency typed.js to v2.0.12 [\#1578](https://github.com/jrnl-org/jrnl/pull/1578) ([renovate[bot]](https://github.com/apps/renovate)) From 847dadac5dca7b40f0e6d428ff749edf23d6b1b5 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 29 Oct 2022 12:38:53 -0700 Subject: [PATCH 007/373] Manual change to changelog [ci skip] - Document that v3.0 had breaking changes with emoji for emphasis --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a19981..9ec50c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,8 @@ [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.0-beta2...v3.0) +🚨 **BREAKING CHANGES** 🚨 + **Implemented enhancements:** - Show name of journal when creating a password/encrypting [\#1478](https://github.com/jrnl-org/jrnl/pull/1478) ([jonakeys](https://github.com/jonakeys)) From a77a3d5a56f2ac969a199763c928ec2558a22329 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 29 Oct 2022 14:21:11 -0700 Subject: [PATCH 008/373] Replace `flake8` with `flakeheaven` in linting steps (#1625) * replace flake8 with flakeheaven * update pyproject.toml for new flake tool * update lock file * change flake8 to flakeheaven in tests * undo fix for poetry * remove unused plugin marker for flakeheaven * remove unused import in markdown file * Attempt to exclude .venv from flakeheaven runs in CI * Exclude more dirs from flakeheaven Co-authored-by: Micah Jerome Ellison --- docs/encryption.md | 1 - poetry.lock | 53 +++++++++++++++++++++++++++++++--------------- pyproject.toml | 29 +++++++++++++++++++------ 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/docs/encryption.md b/docs/encryption.md index 31f64502..6dae9a48 100644 --- a/docs/encryption.md +++ b/docs/encryption.md @@ -140,7 +140,6 @@ 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") diff --git a/poetry.lock b/poetry.lock index 9e724658..eda94884 100644 --- a/poetry.lock +++ b/poetry.lock @@ -178,6 +178,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "entrypoints" +version = "0.4" +description = "Discover and load entry points from installed packages." +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "execnet" version = "1.9.0" @@ -222,6 +230,25 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" +[[package]] +name = "flakeheaven" +version = "3.2.0" +description = "FlakeHeaven is a [Flake8](https://gitlab.com/pycqa/flake8) wrapper to make it cool." +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +colorama = "*" +entrypoints = "*" +flake8 = ">=4.0.1,<5.0.0" +pygments = "*" +toml = "*" +urllib3 = "*" + +[package.extras] +docs = ["alabaster", "myst-parser (>=0.18.0,<0.19.0)", "pygments-github-lexers", "sphinx"] + [[package]] name = "ghp-import" version = "2.1.0" @@ -713,18 +740,6 @@ python-versions = ">=3.6.8" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] -[[package]] -name = "pyproject-flake8" -version = "0.0.1a5" -description = "pyproject-flake8 (`pflake8`), a monkey patching wrapper to connect flake8 with pyproject.toml configuration" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -flake8 = "<5.0.0" -tomli = {version = "*", markers = "python_version < \"3.11\""} - [[package]] name = "pytest" version = "7.1.2" @@ -1128,7 +1143,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = ">=3.9.0, <3.12" -content-hash = "c9bebe280520ae31feec25f734a8404eee0e0f3b3691e9e7e75af0c54a1b02db" +content-hash = "57ed0eb87ee7fe21231b53feb5e4bfdcd154bbb91e52c38d484ab193ff582ec8" [metadata.files] ansiwrap = [ @@ -1297,6 +1312,10 @@ distlib = [ {file = "distlib-0.3.5-py2.py3-none-any.whl", hash = "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c"}, {file = "distlib-0.3.5.tar.gz", hash = "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe"}, ] +entrypoints = [ + {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, + {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, +] execnet = [ {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, @@ -1313,6 +1332,10 @@ flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] +flakeheaven = [ + {file = "flakeheaven-3.2.0-py3-none-any.whl", hash = "sha256:ec5a508c3db64d73128b65cb2a5a2c0a2d9f2e4b435e9fa2bcc03bf0df86da79"}, + {file = "flakeheaven-3.2.0.tar.gz", hash = "sha256:225333d7bf309079f19a2c5f02d427fc7558a0d0c065944de88041ca94f5525c"}, +] ghp-import = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -1516,10 +1539,6 @@ pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] -pyproject-flake8 = [ - {file = "pyproject-flake8-0.0.1a5.tar.gz", hash = "sha256:22542080ba90d4bd80ee060852db15a24aeea61c9a29ed7c16f5b59b0e47a03a"}, - {file = "pyproject_flake8-0.0.1a5-py2.py3-none-any.whl", hash = "sha256:c843d760c49d7b270e9abda58a57765c031918a9d10da25aa43572f5d77cac43"}, -] pytest = [ {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, diff --git a/pyproject.toml b/pyproject.toml index 3ed885c8..8828520e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,11 +44,11 @@ tzlocal = ">=4.0" # https://github.com/regebro/tzlocal/blob/master/CHANGES.txt [tool.poetry.dev-dependencies] black = { version = ">=21.5b2", allow-prereleases = true } +flakeheaven = ">=3.0" ipdb = "*" isort = ">=5.10" mkdocs = ">=1.0,<1.3" poethepoet = "*" -pyproject-flake8 = "*" pytest = ">=6.2" pytest-bdd = ">=4.0.1,<6.0" pytest-clarity = "*" @@ -70,8 +70,9 @@ format-check = [ {cmd = "black --check --diff ."}, ] style-check = [ - {cmd = "pflake8 --version"}, - {cmd = "pflake8 jrnl tests tasks.py"}, + {cmd = "flakeheaven --version"}, + {cmd = "flakeheaven plugins"}, + {cmd = "flakeheaven lint"}, ] sort-run = [ {cmd = "isort ."}, @@ -150,9 +151,25 @@ filterwarnings = [ "ignore:[WinError 5].*" ] -[tool.flake8] -# ignore formatting warnings and errors because we use Black to autoformat -extend-ignore = "E101,E111,E114,E115,E116,E117,E12,E13,E2,E3,E401,E5,E70,W1,W2,W3,W5" +[tool.flakeheaven] +max_line_length = 88 +exclude = [".git", ".tox", ".venv", "node_modules"] + +[tool.flakeheaven.plugins] +"py*" = ["+*"] +pycodestyle = [ + "-E101", + "-E111", "-E114", "-E115", "-E116", "-E117", + "-E12*", + "-E13*", + "-E2*", + "-E3*", + "-E401", + "-E5*", + "-E70", + "-W1*", "-W2*", "-W3*", "-W5*", +] + [build-system] requires = ["poetry-core>=1.0.0"] From 74b7ac834e67ce0f7049b0825b833a8dd4a6737b Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Sat, 29 Oct 2022 14:36:50 -0700 Subject: [PATCH 009/373] Drop Python 3.9 and use Python 3.11 official release (#1611) * Drop Python 3.9 and use Python 3.11 release instead of pre-release * Run poetry lock * Fix need for quoted 3.10 string * Use 3.11.0-rc.2 for Python 3.11 for now * Update minimum Python version in installation docs * Change Python 3.11 RC version to 3.11 * Run docs and release workflows on Python 3.11 * Including Python 3.12 in allowable versions * Restore poetry.lock from develop * Run poetry lock --no-update * Retrieve poetry.lock from develop and lock with --no-update * poetry lock --no-update --- .github/workflows/docs.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/testing_prs.yaml | 2 +- .github/workflows/testing_schedule.yaml | 2 +- docs/installation.md | 2 +- poetry.lock | 39 +++++++------------------ pyproject.toml | 2 +- 7 files changed, 17 insertions(+), 34 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index c51693ab..988e1cfc 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: [ 3.9 ] + python-version: [ '3.11' ] os: [ ubuntu-latest ] steps: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 62614b2d..eff2d991 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -68,7 +68,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.11' - name: Checkout repo uses: actions/checkout@v3 diff --git a/.github/workflows/testing_prs.yaml b/.github/workflows/testing_prs.yaml index f6537237..f28c72af 100644 --- a/.github/workflows/testing_prs.yaml +++ b/.github/workflows/testing_prs.yaml @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ 3.9, '3.10', 3.11-dev ] + python-version: [ '3.10', '3.11' ] os: [ ubuntu-latest, macos-latest, windows-latest ] steps: - run: git config --global core.autocrlf false diff --git a/.github/workflows/testing_schedule.yaml b/.github/workflows/testing_schedule.yaml index 9879bd26..ec6c7f8a 100644 --- a/.github/workflows/testing_schedule.yaml +++ b/.github/workflows/testing_schedule.yaml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ 3.9, '3.10', 3.11-dev ] + python-version: [ '3.10', '3.11' ] os: [ ubuntu-latest, macos-latest, windows-latest ] steps: - run: git config --global core.autocrlf false diff --git a/docs/installation.md b/docs/installation.md index 82b9d450..08defc34 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -14,7 +14,7 @@ On Mac and Linux, the easiest way to install `jrnl` is using brew install jrnl ``` -On other platforms, install `jrnl` using [Python](https://www.python.org/) 3.6+ and [pipx](https://pipxproject.github.io/pipx/): +On other platforms, install `jrnl` using [Python](https://www.python.org/) 3.10+ and [pipx](https://pipxproject.github.io/pipx/): ``` sh pipx install jrnl diff --git a/poetry.lock b/poetry.lock index eda94884..2dcfe584 100644 --- a/poetry.lock +++ b/poetry.lock @@ -51,7 +51,7 @@ python-versions = ">=3.5" dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "backcall" @@ -75,7 +75,6 @@ mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -111,7 +110,7 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode_backport = ["unicodedata2"] +unicode-backport = ["unicodedata2"] [[package]] name = "click" @@ -283,7 +282,7 @@ python-versions = ">=3.5" name = "importlib-metadata" version = "4.12.0" description = "Read metadata from Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -351,7 +350,7 @@ notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test_extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] [[package]] name = "isort" @@ -363,9 +362,9 @@ python-versions = ">=3.6.1,<4.0" [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] -requirements_deprecated_finder = ["pip-api", "pipreqs"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jedi" @@ -417,7 +416,6 @@ optional = false python-versions = ">=3.7" [package.dependencies] -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} @@ -450,9 +448,6 @@ category = "dev" optional = false python-versions = ">=3.7" -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - [package.extras] testing = ["coverage", "pyyaml"] @@ -649,7 +644,7 @@ pastel = ">=0.2.1,<0.3.0" tomli = ">=1.2.2" [package.extras] -poetry_plugin = ["poetry (>=1.0,<2.0)"] +poetry-plugin = ["poetry (>=1.0,<2.0)"] [[package]] name = "pprintpp" @@ -893,7 +888,7 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" @@ -1039,14 +1034,6 @@ python-versions = ">=3.7" [package.extras] test = ["pre-commit", "pytest"] -[[package]] -name = "typing-extensions" -version = "4.3.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" - [[package]] name = "tzdata" version = "2022.1" @@ -1132,7 +1119,7 @@ python-versions = ">=3.4" name = "zipp" version = "3.8.1" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -1142,8 +1129,8 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" -python-versions = ">=3.9.0, <3.12" -content-hash = "57ed0eb87ee7fe21231b53feb5e4bfdcd154bbb91e52c38d484ab193ff582ec8" +python-versions = ">=3.10.0, <3.13" +content-hash = "e2a31438b3c6fbf90093531b3f877818d8dbf85e2c4f95e879888a3aa66a4ee3" [metadata.files] ansiwrap = [ @@ -1700,10 +1687,6 @@ traitlets = [ {file = "traitlets-5.3.0-py3-none-any.whl", hash = "sha256:65fa18961659635933100db8ca120ef6220555286949774b9cfc106f941d1c7a"}, {file = "traitlets-5.3.0.tar.gz", hash = "sha256:0bb9f1f9f017aa8ec187d8b1b2a7a6626a2a1d877116baba52a129bfa124f8e2"}, ] -typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] tzdata = [ {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, diff --git a/pyproject.toml b/pyproject.toml index 8828520e..be70a1e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ "Funding" = "https://opencollective.com/jrnl" [tool.poetry.dependencies] -python = ">=3.9.0, <3.12" +python = ">=3.10.0, <3.13" ansiwrap = "^0.8.4" colorama = ">=0.4" # https://github.com/tartley/colorama/blob/master/CHANGELOG.rst From 5cf93115020ea9a94912fb923f820cc637a954a4 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 29 Oct 2022 21:39:27 +0000 Subject: [PATCH 010/373] Update changelog [ci skip] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec50c1b..2275b0b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [Unreleased](https://github.com/jrnl-org/jrnl/) + +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3...HEAD) + +**Deprecated:** + +- Drop Python 3.9 and use Python 3.11 official release [\#1611](https://github.com/jrnl-org/jrnl/pull/1611) ([micahellison](https://github.com/micahellison)) + ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...v3.3) From 44e2ace833955fba93447acda3a0aed803c66347 Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Sun, 30 Oct 2022 13:06:54 -0700 Subject: [PATCH 011/373] Add double encryption test (#1626) --- tests/bdd/features/encrypt.feature | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/bdd/features/encrypt.feature b/tests/bdd/features/encrypt.feature index 5c201361..b5ef1126 100644 --- a/tests/bdd/features/encrypt.feature +++ b/tests/bdd/features/encrypt.feature @@ -46,6 +46,22 @@ Feature: Encrypting and decrypting journals Then we should be prompted for a password And the output should contain "2013-06-10 15:40 Life is good" + Scenario: Encrypt journal twice and get prompted each time + Given we use the config "simple.yaml" + When we run "jrnl --encrypt" and enter + swordfish + swordfish + y + Then we should get no error + And the output should contain "Journal encrypted" + When we run "jrnl --encrypt" and enter + swordfish + swordfish + y + Then we should get no error + And the output should contain "Journal default is already encrypted. Create a new password." + And we should be prompted for a password + And the config for journal "default" should contain "encrypt: true" Scenario Outline: Running jrnl with encrypt: true on unencryptable journals Given we use the config "" From cd47070894d455f097769d7e82368e36f51c7d73 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Mon, 31 Oct 2022 08:55:23 -0700 Subject: [PATCH 012/373] Add rich handler for better-formatted debug logging (#1627) --- jrnl/cli.py | 8 ++++++-- jrnl/config.py | 6 +++++- jrnl/install.py | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index 7c692c4f..d8009df6 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -5,6 +5,8 @@ import logging import sys import traceback +from rich.logging import RichHandler + from jrnl.args import parse_args from jrnl.exception import JrnlException from jrnl.jrnl import run @@ -21,7 +23,9 @@ def configure_logger(debug=False): logging.basicConfig( level=logging.DEBUG, - format="%(levelname)-8s %(name)-12s %(message)s", + datefmt="[%X]", + format="%(message)s", + handlers=[RichHandler()], ) logging.getLogger("parsedatetime").setLevel(logging.INFO) logging.getLogger("keyring.backend").setLevel(logging.ERROR) @@ -34,7 +38,7 @@ def cli(manual_args=None): args = parse_args(manual_args) configure_logger(args.debug) - logging.debug("Parsed args: %s", args) + logging.debug("Parsed args:\n%s", args) status_code = run(args) diff --git a/jrnl/config.py b/jrnl/config.py index 8e5c5a14..e3290d8b 100644 --- a/jrnl/config.py +++ b/jrnl/config.py @@ -8,6 +8,7 @@ from typing import Callable import colorama import xdg.BaseDirectory +from rich.pretty import pretty_repr from ruamel.yaml import YAML from ruamel.yaml import constructor @@ -126,12 +127,15 @@ def scope_config(config, journal_name): if type(journal_conf) is dict: # We can override the default config on a by-journal basis logging.debug( - "Updating configuration with specific journal overrides %s", journal_conf + "Updating configuration with specific journal overrides:\n%s", + pretty_repr(journal_conf), ) config.update(journal_conf) else: # But also just give them a string to point to the journal file config["journal"] = journal_conf + + logging.debug("Scoped config:\n%s", pretty_repr(config)) return config diff --git a/jrnl/install.py b/jrnl/install.py index b20685f0..be0c7bc3 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -6,6 +6,8 @@ import logging import os import sys +from rich.pretty import pretty_repr + from jrnl.config import DEFAULT_JOURNAL_KEY from jrnl.config import get_config_path from jrnl.config import get_default_config @@ -101,7 +103,7 @@ def load_or_install_jrnl(alt_config_path): logging.debug("Configuration file not found, installing jrnl...") config = install() - logging.debug('Using configuration "%s"', config) + logging.debug('Using configuration:\n"%s"', pretty_repr(config)) return config From 9150f0798411e822ac3ddc1b260d70b98503c639 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Mon, 31 Oct 2022 15:58:15 +0000 Subject: [PATCH 013/373] Update changelog [ci skip] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2275b0b7..a8dc3517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3...HEAD) +**Implemented enhancements:** + +- Add `rich` handler to debug logging [\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren)) + **Deprecated:** - Drop Python 3.9 and use Python 3.11 official release [\#1611](https://github.com/jrnl-org/jrnl/pull/1611) ([micahellison](https://github.com/micahellison)) From 51e9ce563888da6e1180486949c535146a728aea Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Mon, 31 Oct 2022 09:31:17 -0700 Subject: [PATCH 014/373] Move existing linting into `flakeheaven` (#1628) * add black and isort to flakeheaven * update lock file * clean up poe config * run formta on python blocks in markdown file * disable code for black being confused about markdown * add cache timeout for flakeheaven See: https://github.com/flakeheaven/flakeheaven/issues/71 --- docs/encryption.md | 13 ++++++------ poetry.lock | 38 +++++++++++++++++++++++++++++++++- pyproject.toml | 51 ++++++++++++++++------------------------------ 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/docs/encryption.md b/docs/encryption.md index 6dae9a48..b019e4a6 100644 --- a/docs/encryption.md +++ b/docs/encryption.md @@ -100,16 +100,16 @@ something like `pip3 install crytography`) import base64 import getpass from pathlib import Path + from cryptography.fernet import Fernet from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC - filepath = input("journal file path: ") password = getpass.getpass("Password: ") -with open(Path(filepath),"rb") as f: +with open(Path(filepath), "rb") as f: ciphertext = f.read() password = password.encode("utf-8") @@ -123,7 +123,7 @@ kdf = PBKDF2HMAC( key = base64.urlsafe_b64encode(kdf.derive(password)) -print(Fernet(key).decrypt(ciphertext).decode('utf-8')) +print(Fernet(key).decrypt(ciphertext).decode("utf-8")) ``` **Example for jrnl v1 files**: @@ -137,18 +137,19 @@ like `pip3 install pycrypto`) """ import argparse -from Crypto.Cipher import AES import getpass import hashlib +from Crypto.Cipher import AES + 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() +key = hashlib.sha256(pwd.encode("utf-8")).digest() -with open(args.filepath, 'rb') as f: +with open(args.filepath, "rb") as f: ciphertext = f.read() crypto = AES.new(key, AES.MODE_CBC, ciphertext[:16]) diff --git a/poetry.lock b/poetry.lock index 2dcfe584..36475675 100644 --- a/poetry.lock +++ b/poetry.lock @@ -229,6 +229,34 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" +[[package]] +name = "flake8-black" +version = "0.3.3" +description = "flake8 plugin to call black as a code style validator" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +black = ">=22.1.0" +flake8 = ">=3.0.0" +tomli = "*" + +[[package]] +name = "flake8-isort" +version = "5.0.0" +description = "flake8 plugin that integrates isort ." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +flake8 = "*" +isort = ">=4.3.5,<6" + +[package.extras] +test = ["pytest"] + [[package]] name = "flakeheaven" version = "3.2.0" @@ -1130,7 +1158,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = ">=3.10.0, <3.13" -content-hash = "e2a31438b3c6fbf90093531b3f877818d8dbf85e2c4f95e879888a3aa66a4ee3" +content-hash = "13e2102b7ddeb9ac4f1f2fddcfa6275d565c3eec9fa8da1b4657a02e20f900c9" [metadata.files] ansiwrap = [ @@ -1319,6 +1347,14 @@ flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] +flake8-black = [ + {file = "flake8-black-0.3.3.tar.gz", hash = "sha256:8211f5e20e954cb57c709acccf2f3281ce27016d4c4b989c3e51f878bb7ce12a"}, + {file = "flake8_black-0.3.3-py3-none-any.whl", hash = "sha256:7d667d0059fd1aa468de1669d77cc934b7f1feeac258d57bdae69a8e73c4cd90"}, +] +flake8-isort = [ + {file = "flake8-isort-5.0.0.tar.gz", hash = "sha256:e336f928c7edc509684930ab124414194b7f4e237c712af8fcbdf49d8747b10c"}, + {file = "flake8_isort-5.0.0-py3-none-any.whl", hash = "sha256:c73f9cbd1bf209887f602a27b827164ccfeba1676801b2aa23cb49051a1be79c"}, +] flakeheaven = [ {file = "flakeheaven-3.2.0-py3-none-any.whl", hash = "sha256:ec5a508c3db64d73128b65cb2a5a2c0a2d9f2e4b435e9fa2bcc03bf0df86da79"}, {file = "flakeheaven-3.2.0.tar.gz", hash = "sha256:225333d7bf309079f19a2c5f02d427fc7558a0d0c065944de88041ca94f5525c"}, diff --git a/pyproject.toml b/pyproject.toml index be70a1e4..695a1ea7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,8 @@ tzlocal = ">=4.0" # https://github.com/regebro/tzlocal/blob/master/CHANGES.txt [tool.poetry.dev-dependencies] black = { version = ">=21.5b2", allow-prereleases = true } flakeheaven = ">=3.0" +flake8-black = ">=0.3.3" +flake8-isort = ">=5.0.0" ipdb = "*" isort = ">=5.10" mkdocs = ">=1.0,<1.3" @@ -62,26 +64,6 @@ xmltodict = "*" jrnl = 'jrnl.cli:cli' [tool.poe.tasks] -format-run = [ - {cmd = "black ."}, -] -format-check = [ - {cmd = "black --version"}, - {cmd = "black --check --diff ."}, -] -style-check = [ - {cmd = "flakeheaven --version"}, - {cmd = "flakeheaven plugins"}, - {cmd = "flakeheaven lint"}, -] -sort-run = [ - {cmd = "isort ."}, -] -sort-check = [ - {cmd = "isort --version"}, - {cmd = "isort --check ."}, -] - docs-check.default_item_type = "script" docs-check.sequence = [ "tasks:delete_files(['sitemap.xml', 'config.json'])", @@ -100,22 +82,23 @@ test-run = [ {cmd = "tox -q -e py --"}, ] -installer-check = [ - {cmd = "poetry --version"}, - {cmd = "poetry check"}, +# Groups of tasks +format.default_item_type = "cmd" +format.sequence = [ + "isort .", + "black .", ] -# Groups of tasks -format = [ - "format-run", - "sort-run", -] -lint = [ - "installer-check", - "style-check", - "sort-check", - "format-check", +lint.env = { FLAKEHEAVEN_CACHE_TIMEOUT = "0" } +lint.default_item_type = "cmd" +lint.sequence = [ + "poetry --version", + "poetry check", + "flakeheaven --version", + "flakeheaven plugins", + "flakeheaven lint", ] + test = [ "lint", "test-run", @@ -169,6 +152,8 @@ pycodestyle = [ "-E70", "-W1*", "-W2*", "-W3*", "-W5*", ] +"flake8-*" = ["+*"] +flake8-black = ["-BLK901"] [build-system] From 8ad9e2bdd6b40476dcf81d9da4aff93a9342ccdb Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 1 Nov 2022 16:50:25 -0700 Subject: [PATCH 015/373] Fix bug where changelog is always slightly out of date on release tags (#1631) * fix issue where changelog is always slightly out of date on release tags * fix tag step running all the time per CR * update tag name to use more clear variable name --- .github/workflows/changelog.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index c3f8037f..f8b4eca1 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -152,6 +152,14 @@ jobs: git commit -m "Update changelog [ci skip]" git push origin "$BRANCH" + - name: Update tag to include changelog + if: startsWith(env.GITHUB_REF, 'refs/tags/') + run: | + # This is a tag build (releases and prereleases) + # update the tag to include the changelog + git tag -fam "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" + git push --tags --force + - name: Merge to Release branch if: env.FULL_RELEASE == 'true' run: | From 8e482321f2c8a61f428cab2ac8db2b9e90f21f72 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Tue, 1 Nov 2022 23:52:21 +0000 Subject: [PATCH 016/373] Update changelog [ci skip] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8dc3517..f1de227b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ - Drop Python 3.9 and use Python 3.11 official release [\#1611](https://github.com/jrnl-org/jrnl/pull/1611) ([micahellison](https://github.com/micahellison)) +**Build:** + +- Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren)) + +**Documentation:** + +- Document that editors must be blocking processes [\#1624](https://github.com/jrnl-org/jrnl/pull/1624) ([micahellison](https://github.com/micahellison)) + ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...v3.3) From e6130dbf4a7be094c09504ae723602c99c1ab7b8 Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Wed, 2 Nov 2022 19:53:32 -0700 Subject: [PATCH 017/373] Resolve failing GitHub Actions linting by replacing deprecated set-output command (#1632) * Replace deprecated set-output command * Resolve SC2086 - double quote to prevent globbing and word splitting * fix quotes in output command Co-authored-by: Jonathan Wren --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index eff2d991..4b55f253 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -111,7 +111,7 @@ jobs: id: pypi-version-getter run: | pypi_version="$(find dist/jrnl-*.tar.gz | sed -r 's!dist/jrnl-(.*)\.tar\.gz!\1!')" - echo "::set-output name=pypi_version::$pypi_version" + echo "pypi_version=$pypi_version" >> "$GITHUB_OUTPUT" release_homebrew: if: ${{ github.event.inputs.include_brew == 'true' }} From cb7e0ed2892db5e0e22adacb4c5bcfde743c437c Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Thu, 3 Nov 2022 02:55:34 +0000 Subject: [PATCH 018/373] Update changelog [ci skip] --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1de227b..d4d86379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,6 @@ - Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren)) -**Documentation:** - -- Document that editors must be blocking processes [\#1624](https://github.com/jrnl-org/jrnl/pull/1624) ([micahellison](https://github.com/micahellison)) - ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...v3.3) From c1eb0c54a35049b46e0f718cea6cda823852fb93 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Thu, 3 Nov 2022 07:18:35 -0700 Subject: [PATCH 019/373] Add `type-checking` plugin to linting checks (#1629) * add type-checking plugin for flakeheaven * update lock file * fix type-checking issues in current codebase * run linters --- jrnl/exception.py | 8 ++++++-- jrnl/messages/Message.py | 7 +++++-- jrnl/override.py | 7 ++++--- poetry.lock | 30 +++++++++++++++++++++++++++++- pyproject.toml | 1 + 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/jrnl/exception.py b/jrnl/exception.py index 86140cd9..b4ec2be8 100644 --- a/jrnl/exception.py +++ b/jrnl/exception.py @@ -1,14 +1,18 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html -from jrnl.messages import Message +from typing import TYPE_CHECKING + from jrnl.output import print_msg +if TYPE_CHECKING: + from jrnl.messages import Message + class JrnlException(Exception): """Common exceptions raised by jrnl.""" - def __init__(self, *messages: Message): + def __init__(self, *messages: "Message"): self.messages = messages def print(self) -> None: diff --git a/jrnl/messages/Message.py b/jrnl/messages/Message.py index e4d6176a..bb193f5f 100644 --- a/jrnl/messages/Message.py +++ b/jrnl/messages/Message.py @@ -1,14 +1,17 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +from typing import TYPE_CHECKING from typing import Mapping from typing import NamedTuple from jrnl.messages.MsgStyle import MsgStyle -from jrnl.messages.MsgText import MsgText + +if TYPE_CHECKING: + from jrnl.messages.MsgText import MsgText class Message(NamedTuple): - text: MsgText + text: "MsgText" style: MsgStyle = MsgStyle.NORMAL params: Mapping = {} diff --git a/jrnl/override.py b/jrnl/override.py index 4b6506ee..75a400b9 100644 --- a/jrnl/override.py +++ b/jrnl/override.py @@ -1,14 +1,15 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html - -from argparse import Namespace +from typing import TYPE_CHECKING from jrnl.config import make_yaml_valid_dict from jrnl.config import update_config +if TYPE_CHECKING: + from argparse import Namespace # import logging -def apply_overrides(args: Namespace, base_config: dict) -> dict: +def apply_overrides(args: "Namespace", base_config: dict) -> dict: """Unpack CLI provided overrides into the configuration tree. :param overrides: List of configuration key-value pairs collected from the CLI diff --git a/poetry.lock b/poetry.lock index 36475675..9b4d4c65 100644 --- a/poetry.lock +++ b/poetry.lock @@ -112,6 +112,14 @@ python-versions = ">=3.6.0" [package.extras] unicode-backport = ["unicodedata2"] +[[package]] +name = "classify-imports" +version = "4.2.0" +description = "Utilities for refactoring imports in python-like syntax." +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "click" version = "8.1.3" @@ -257,6 +265,18 @@ isort = ">=4.3.5,<6" [package.extras] test = ["pytest"] +[[package]] +name = "flake8-type-checking" +version = "2.2.0" +description = "A flake8 plugin for managing type-checking imports & forward references" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +classify-imports = "*" +flake8 = "*" + [[package]] name = "flakeheaven" version = "3.2.0" @@ -1158,7 +1178,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = ">=3.10.0, <3.13" -content-hash = "13e2102b7ddeb9ac4f1f2fddcfa6275d565c3eec9fa8da1b4657a02e20f900c9" +content-hash = "63f39baa62c8641eb6329472de340a9f06d9ffea3096a4095e90f98ce2986f91" [metadata.files] ansiwrap = [ @@ -1283,6 +1303,10 @@ charset-normalizer = [ {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, ] +classify-imports = [ + {file = "classify_imports-4.2.0-py2.py3-none-any.whl", hash = "sha256:dbbc264b70a470ed8c6c95976a11dfb8b7f63df44ed1af87328bbed2663f5161"}, + {file = "classify_imports-4.2.0.tar.gz", hash = "sha256:7abfb7ea92149b29d046bd34573d247ba6e68cc28100c801eba4af17964fc40e"}, +] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -1355,6 +1379,10 @@ flake8-isort = [ {file = "flake8-isort-5.0.0.tar.gz", hash = "sha256:e336f928c7edc509684930ab124414194b7f4e237c712af8fcbdf49d8747b10c"}, {file = "flake8_isort-5.0.0-py3-none-any.whl", hash = "sha256:c73f9cbd1bf209887f602a27b827164ccfeba1676801b2aa23cb49051a1be79c"}, ] +flake8-type-checking = [ + {file = "flake8_type_checking-2.2.0-py3-none-any.whl", hash = "sha256:c7d9d7adc6cd635a5a1a7859e5e0140f4f8f1705982a22db45872dd9acd49753"}, + {file = "flake8_type_checking-2.2.0.tar.gz", hash = "sha256:f7972fc9102f3f632ace1f4b1c5c20b900b8b7b529f04bb6c1fe0a11801e9658"}, +] flakeheaven = [ {file = "flakeheaven-3.2.0-py3-none-any.whl", hash = "sha256:ec5a508c3db64d73128b65cb2a5a2c0a2d9f2e4b435e9fa2bcc03bf0df86da79"}, {file = "flakeheaven-3.2.0.tar.gz", hash = "sha256:225333d7bf309079f19a2c5f02d427fc7558a0d0c065944de88041ca94f5525c"}, diff --git a/pyproject.toml b/pyproject.toml index 695a1ea7..e3c5592f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ black = { version = ">=21.5b2", allow-prereleases = true } flakeheaven = ">=3.0" flake8-black = ">=0.3.3" flake8-isort = ">=5.0.0" +flake8-type-checking = ">=2.2.0" ipdb = "*" isort = ">=5.10" mkdocs = ">=1.0,<1.3" From 30b41fdb88784cc2de2b203005869c55e4457a26 Mon Sep 17 00:00:00 2001 From: outa Date: Sat, 5 Nov 2022 23:29:50 +0100 Subject: [PATCH 020/373] Add type hints (#1614) * Add type hints * Fix linters * Add remaining type hints * Fix type-checking linter * Update jrnl/DayOneJournal.py Co-authored-by: Jonathan Wren --- jrnl/DayOneJournal.py | 12 +++---- jrnl/Entry.py | 24 ++++++++++---- jrnl/FolderJournal.py | 18 +++++++---- jrnl/args.py | 4 +-- jrnl/cli.py | 4 +-- jrnl/color.py | 14 +++++--- jrnl/config.py | 23 +++++++------ jrnl/editor.py | 4 +-- jrnl/install.py | 14 ++++---- jrnl/jrnl.py | 54 ++++++++++++++++++++----------- jrnl/os_compat.py | 6 ++-- jrnl/output.py | 20 ++++++------ jrnl/override.py | 4 +-- jrnl/path.py | 6 ++-- jrnl/plugins/__init__.py | 6 ++-- jrnl/plugins/dates_exporter.py | 9 ++++-- jrnl/plugins/fancy_exporter.py | 11 +++++-- jrnl/plugins/jrnl_importer.py | 6 +++- jrnl/plugins/json_exporter.py | 11 +++++-- jrnl/plugins/markdown_exporter.py | 9 ++++-- jrnl/plugins/tag_exporter.py | 10 ++++-- jrnl/plugins/text_exporter.py | 19 +++++++---- jrnl/plugins/util.py | 9 ++++-- jrnl/plugins/xml_exporter.py | 13 ++++++-- jrnl/plugins/yaml_exporter.py | 9 ++++-- jrnl/prompt.py | 2 +- jrnl/time.py | 8 +++-- jrnl/upgrade.py | 10 +++--- 28 files changed, 219 insertions(+), 120 deletions(-) diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index d7f06249..4806bdbf 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -38,7 +38,7 @@ class DayOne(Journal.Journal): self.can_be_encrypted = False super().__init__(**kwargs) - def open(self): + def open(self) -> "DayOne": filenames = [] for root, dirnames, f in os.walk(self.config["journal"]): for filename in fnmatch.filter(f, "*.doentry"): @@ -113,7 +113,7 @@ class DayOne(Journal.Journal): self.sort() return self - def write(self): + def write(self) -> None: """Writes only the entries that have been modified into plist files.""" for entry in self.entries: if entry.modified: @@ -177,12 +177,12 @@ class DayOne(Journal.Journal): ) os.remove(filename) - def editable_str(self): + def editable_str(self) -> str: """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([f"{str(e)}\n# {e.uuid}\n" for e in self.entries]) - def _update_old_entry(self, entry, new_entry): + def _update_old_entry(self, entry: Entry, new_entry: Entry) -> None: for attr in ("title", "body", "date"): old_attr = getattr(entry, attr) new_attr = getattr(new_entry, attr) @@ -190,7 +190,7 @@ class DayOne(Journal.Journal): entry.modified = True setattr(entry, attr, new_attr) - def _get_and_remove_uuid_from_entry(self, entry): + def _get_and_remove_uuid_from_entry(self, entry: Entry) -> Entry: uuid_regex = "^ *?# ([a-zA-Z0-9]+) *?$" m = re.search(uuid_regex, entry.body, re.MULTILINE) entry.uuid = m.group(1) if m else None @@ -201,7 +201,7 @@ class DayOne(Journal.Journal): return entry - def parse_editable_str(self, edited): + def parse_editable_str(self, edited: str) -> None: """Parses the output of self.editable_str and updates its entries.""" # Method: create a new list of entries from the edited text, then match # UUIDs of the new entries against self.entries, updating the entries diff --git a/jrnl/Entry.py b/jrnl/Entry.py index b8c3b640..9dfdf00f 100644 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -5,15 +5,25 @@ import datetime import logging import os import re +from typing import TYPE_CHECKING import ansiwrap from .color import colorize from .color import highlight_tags_with_background_color +if TYPE_CHECKING: + from .Journal import Journal + class Entry: - def __init__(self, journal, date=None, text="", starred=False): + def __init__( + self, + journal: "Journal", + date: datetime.datetime | None = None, + text: str = "", + starred: bool = False, + ): self.journal = journal # Reference to journal mainly to access its config self.date = date or datetime.datetime.now() self.text = text @@ -24,7 +34,7 @@ class Entry: self.modified = False @property - def fulltext(self): + def fulltext(self) -> str: return self.title + " " + self.body def _parse_text(self): @@ -68,11 +78,11 @@ class Entry: self._tags = x @staticmethod - def tag_regex(tagsymbols): + def tag_regex(tagsymbols: str) -> re.Pattern: pattern = rf"(? set[str]: tagsymbols = self.journal.config["tagsymbols"] return { tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text) @@ -90,7 +100,7 @@ class Entry: body=self.body.rstrip("\n "), ) - def pprint(self, short=False): + def pprint(self, short: bool = False) -> str: """Returns a pretty-printed version of the entry. If short is true, only print the title.""" # Handle indentation @@ -197,7 +207,7 @@ class Entry: def __hash__(self): return hash(self.__repr__()) - def __eq__(self, other): + def __eq__(self, other: "Entry"): if ( not isinstance(other, Entry) or self.title.strip() != other.title.strip() @@ -230,7 +240,7 @@ SENTENCE_SPLITTER = re.compile( SENTENCE_SPLITTER_ONLY_NEWLINE = re.compile("\n") -def split_title(text): +def split_title(text: str) -> tuple[str, str]: """Splits the first sentence off from a text.""" sep = SENTENCE_SPLITTER_ONLY_NEWLINE.search(text.lstrip()) if not sep: diff --git a/jrnl/FolderJournal.py b/jrnl/FolderJournal.py index 25a927e1..ab9547ed 100644 --- a/jrnl/FolderJournal.py +++ b/jrnl/FolderJournal.py @@ -4,12 +4,16 @@ import codecs import fnmatch import os +from typing import TYPE_CHECKING from jrnl import Journal from jrnl import time +if TYPE_CHECKING: + from jrnl.Entry import Entry -def get_files(journal_config): + +def get_files(journal_config: str) -> list[str]: """Searches through sub directories starting with journal_config and find all text files""" filenames = [] for root, dirnames, f in os.walk(journal_config): @@ -21,13 +25,13 @@ def get_files(journal_config): class Folder(Journal.Journal): """A Journal handling multiple files in a folder""" - def __init__(self, name="default", **kwargs): + def __init__(self, name: str = "default", **kwargs): self.entries = [] self._diff_entry_dates = [] self.can_be_encrypted = False super().__init__(name, **kwargs) - def open(self): + def open(self) -> "Folder": filenames = [] self.entries = [] filenames = get_files(self.config["journal"]) @@ -38,7 +42,7 @@ class Folder(Journal.Journal): self.sort() return self - def write(self): + def write(self) -> None: """Writes only the entries that have been modified into proper files.""" # Create a list of dates of modified entries. Start with diff_entry_dates modified_dates = self._diff_entry_dates @@ -81,13 +85,13 @@ class Folder(Journal.Journal): if os.stat(filename).st_size <= 0: os.remove(filename) - def delete_entries(self, entries_to_delete): + def delete_entries(self, entries_to_delete: list["Entry"]) -> None: """Deletes specific entries from a journal.""" for entry in entries_to_delete: self.entries.remove(entry) self._diff_entry_dates.append(entry.date) - def change_date_entries(self, date): + def change_date_entries(self, date: str) -> None: """Changes entry dates to given date.""" date = time.parse(date) @@ -98,7 +102,7 @@ class Folder(Journal.Journal): self._diff_entry_dates.append(entry.date) entry.date = date - def parse_editable_str(self, edited): + def parse_editable_str(self, edited: str) -> None: """Parses the output of self.editable_str and updates its entries.""" mod_entries = self._parse(edited) diff_entries = set(self.entries) - set(mod_entries) diff --git a/jrnl/args.py b/jrnl/args.py index c6b0f1a0..f7f0eb3a 100644 --- a/jrnl/args.py +++ b/jrnl/args.py @@ -20,7 +20,7 @@ from jrnl.plugins import util class WrappingFormatter(argparse.RawTextHelpFormatter): """Used in help screen""" - def _split_lines(self, text, width): + def _split_lines(self, text: str, width: int) -> list[str]: text = text.split("\n\n") text = map(lambda t: self._whitespace_matcher.sub(" ", t).strip(), text) text = map(lambda t: textwrap.wrap(t, width=56), text) @@ -28,7 +28,7 @@ class WrappingFormatter(argparse.RawTextHelpFormatter): return text -def parse_args(args=[]): +def parse_args(args: list[str] = []) -> argparse.Namespace: """ Argument parsing that is doable before the config is available. Everything else goes into "text" for later parsing. diff --git a/jrnl/cli.py b/jrnl/cli.py index d8009df6..609a8aab 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -16,7 +16,7 @@ from jrnl.messages import MsgText from jrnl.output import print_msg -def configure_logger(debug=False): +def configure_logger(debug: bool = False) -> None: if not debug: logging.disable() return @@ -31,7 +31,7 @@ def configure_logger(debug=False): logging.getLogger("keyring.backend").setLevel(logging.ERROR) -def cli(manual_args=None): +def cli(manual_args: list[str] | None = None) -> int: try: if manual_args is None: manual_args = sys.argv[1:] diff --git a/jrnl/color.py b/jrnl/color.py index f5634bbd..91d56254 100644 --- a/jrnl/color.py +++ b/jrnl/color.py @@ -4,16 +4,20 @@ import re from string import punctuation from string import whitespace +from typing import TYPE_CHECKING import colorama from jrnl.os_compat import on_windows +if TYPE_CHECKING: + from jrnl.Entry import Entry + if on_windows(): colorama.init() -def colorize(string, color, bold=False): +def colorize(string: str, color: str, bold: bool = False) -> str: """Returns the string colored with colorama.Fore.color. If the color set by the user is "NONE" or the color doesn't exist in the colorama.Fore attributes, it returns the string without any modification.""" @@ -26,7 +30,9 @@ def colorize(string, color, bold=False): return colorama.Style.BRIGHT + color_escape + string + colorama.Style.RESET_ALL -def highlight_tags_with_background_color(entry, text, color, is_title=False): +def highlight_tags_with_background_color( + entry: "Entry", text: str, color: str, is_title: bool = False +) -> str: """ Takes a string and colorizes the tags in it based upon the config value for color.tags, while colorizing the rest of the text based on `color`. @@ -45,9 +51,9 @@ def highlight_tags_with_background_color(entry, text, color, is_title=False): :returns [(colorized_str, original_str)]""" for part in fragments: if part and part[0] not in config["tagsymbols"]: - yield (colorize(part, color, bold=is_title), part) + yield colorize(part, color, bold=is_title), part elif part: - yield (colorize(part, config["colors"]["tags"], bold=True), part) + yield colorize(part, config["colors"]["tags"], bold=True), part config = entry.journal.config if config["highlight"]: # highlight tags diff --git a/jrnl/config.py b/jrnl/config.py index e3290d8b..bcd67e2c 100644 --- a/jrnl/config.py +++ b/jrnl/config.py @@ -4,6 +4,7 @@ import argparse import logging import os +from typing import Any from typing import Callable import colorama @@ -57,7 +58,7 @@ def make_yaml_valid_dict(input: list) -> dict: return runtime_modifications -def save_config(config, alt_config_path=None): +def save_config(config: dict, alt_config_path: str | None = None) -> None: """Supply alt_config_path if using an alternate config through --config-file.""" config["version"] = __version__ @@ -72,7 +73,7 @@ def save_config(config, alt_config_path=None): yaml.dump(config, f) -def get_config_path(): +def get_config_path() -> str: try: config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE) except FileExistsError: @@ -91,7 +92,7 @@ def get_config_path(): return os.path.join(config_directory_path or home_dir(), DEFAULT_CONFIG_NAME) -def get_default_config(): +def get_default_config() -> dict[str, Any]: return { "version": __version__, "journals": {"default": {"journal": get_default_journal_path()}}, @@ -114,12 +115,12 @@ def get_default_config(): } -def get_default_journal_path(): +def get_default_journal_path() -> str: journal_data_path = xdg.BaseDirectory.save_data_path(XDG_RESOURCE) or home_dir() return os.path.join(journal_data_path, DEFAULT_JOURNAL_NAME) -def scope_config(config, journal_name): +def scope_config(config: dict, journal_name: str) -> dict: if journal_name not in config["journals"]: return config config = config.copy() @@ -139,7 +140,7 @@ def scope_config(config, journal_name): return config -def verify_config_colors(config): +def verify_config_colors(config: dict) -> bool: """ Ensures the keys set for colors are valid colorama.Fore attributes, or "None" :return: True if all keys are set correctly, False otherwise @@ -164,7 +165,7 @@ def verify_config_colors(config): return all_valid_colors -def load_config(config_path): +def load_config(config_path: str) -> dict: """Tries to load a config file from YAML.""" try: with open(config_path, encoding=YAML_FILE_ENCODING) as f: @@ -187,13 +188,15 @@ def load_config(config_path): return yaml.load(f) -def is_config_json(config_path): +def is_config_json(config_path: str) -> bool: with open(config_path, "r", encoding="utf-8") as f: config_file = f.read() return config_file.strip().startswith("{") -def update_config(config, new_config, scope, force_local=False): +def update_config( + config: dict, new_config: dict, scope: str | None, force_local: bool = False +) -> None: """Updates a config dict with new values - either global if scope is None or config['journals'][scope] is just a string pointing to a journal file, or within the scope""" @@ -206,7 +209,7 @@ def update_config(config, new_config, scope, force_local=False): config.update(new_config) -def get_journal_name(args, config): +def get_journal_name(args: argparse.Namespace, config: dict) -> argparse.Namespace: args.journal_name = DEFAULT_JOURNAL_KEY # The first arg might be a journal name diff --git a/jrnl/editor.py b/jrnl/editor.py index fd063f44..d578e2d8 100644 --- a/jrnl/editor.py +++ b/jrnl/editor.py @@ -17,7 +17,7 @@ from jrnl.os_compat import split_args from jrnl.output import print_msg -def get_text_from_editor(config, template=""): +def get_text_from_editor(config: dict, template: str = "") -> str: suffix = ".jrnl" if config["template"]: template_filename = Path(config["template"]).name @@ -50,7 +50,7 @@ def get_text_from_editor(config, template=""): return raw -def get_text_from_stdin(): +def get_text_from_stdin() -> str: print_msg( Message( MsgText.WritingEntryStart, diff --git a/jrnl/install.py b/jrnl/install.py index be0c7bc3..a37e51f9 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -27,7 +27,7 @@ from jrnl.prompt import yesno from jrnl.upgrade import is_old_version -def upgrade_config(config_data, alt_config_path=None): +def upgrade_config(config_data: dict, alt_config_path: str | None = None) -> None: """Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly. This essentially automatically ports jrnl installations if new config parameters are introduced in later versions. @@ -46,7 +46,7 @@ def upgrade_config(config_data, alt_config_path=None): ) -def find_default_config(): +def find_default_config() -> str: config_path = ( get_config_path() if os.path.exists(get_config_path()) @@ -55,7 +55,7 @@ def find_default_config(): return config_path -def find_alt_config(alt_config): +def find_alt_config(alt_config: str) -> str: if not os.path.exists(alt_config): raise JrnlException( Message( @@ -66,7 +66,7 @@ def find_alt_config(alt_config): return alt_config -def load_or_install_jrnl(alt_config_path): +def load_or_install_jrnl(alt_config_path: str) -> dict: """ If jrnl is already installed, loads and returns a default config object. If alternate config is specified via --config-file flag, it will be used. @@ -107,7 +107,7 @@ def load_or_install_jrnl(alt_config_path): return config -def install(): +def install() -> dict: _initialize_autocomplete() # Where to create the journal? @@ -143,7 +143,7 @@ def install(): return default_config -def _initialize_autocomplete(): +def _initialize_autocomplete() -> None: # readline is not included in Windows Active Python and perhaps some other distributions if sys.modules.get("readline"): import readline @@ -153,7 +153,7 @@ def _initialize_autocomplete(): readline.set_completer(_autocomplete_path) -def _autocomplete_path(text, state): +def _autocomplete_path(text: str, state: int) -> list[str | None]: expansions = glob.glob(expand_path(text) + "*") expansions = [e + "/" if os.path.isdir(e) else e for e in expansions] expansions.append(None) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 5c9ae9ac..39c25b3f 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -1,8 +1,8 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html - import logging import sys +from typing import TYPE_CHECKING from jrnl import install from jrnl import plugins @@ -14,6 +14,7 @@ from jrnl.config import scope_config from jrnl.editor import get_text_from_editor from jrnl.editor import get_text_from_stdin from jrnl.exception import JrnlException +from jrnl.Journal import Journal from jrnl.Journal import open_journal from jrnl.messages import Message from jrnl.messages import MsgStyle @@ -23,8 +24,13 @@ from jrnl.output import print_msgs from jrnl.override import apply_overrides from jrnl.path import expand_path +if TYPE_CHECKING: + from argparse import Namespace -def run(args): + from jrnl.Entry import Entry + + +def run(args: "Namespace"): """ Flow: 1. Run standalone command if it doesn't require config (help, version, etc), then exit @@ -72,10 +78,8 @@ def run(args): search_mode(**kwargs) -def _is_write_mode(args, config, **kwargs): +def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool: """Determines if we are in write mode (as opposed to search mode)""" - write_mode = True - # Are any search filters present? If so, then search mode. write_mode = not any( ( @@ -115,7 +119,7 @@ def _is_write_mode(args, config, **kwargs): return write_mode -def write_mode(args, config, journal, **kwargs): +def write_mode(args: "Namespace", config: dict, journal: Journal, **kwargs) -> None: """ Gets input from the user to write to the journal 1. Check for input from cli @@ -159,7 +163,7 @@ def write_mode(args, config, journal, **kwargs): logging.debug("Write mode: completed journal.write()") -def search_mode(args, journal, **kwargs): +def search_mode(args: "Namespace", journal: Journal, **kwargs) -> None: """ Search for entries in a journal, then either: 1. Send them to configured editor for user manipulation (and also @@ -213,7 +217,7 @@ def search_mode(args, journal, **kwargs): _display_search_results(**kwargs) -def _write_in_editor(config, template=None): +def _write_in_editor(config: dict, template: str | None = None) -> str: if config["editor"]: logging.debug("Write mode: opening editor") if not template: @@ -226,7 +230,7 @@ def _write_in_editor(config, template=None): return raw -def _get_editor_template(config, **kwargs): +def _get_editor_template(config: dict, **kwargs) -> str: logging.debug("Write mode: loading template for entry") if not config["template"]: @@ -251,7 +255,7 @@ def _get_editor_template(config, **kwargs): return template -def _has_search_args(args): +def _has_search_args(args: "Namespace") -> bool: return any( ( args.on_date, @@ -271,7 +275,7 @@ def _has_search_args(args): ) -def _filter_journal_entries(args, journal, **kwargs): +def _filter_journal_entries(args: "Namespace", journal: Journal, **kwargs) -> None: """Filter journal entries in-place based upon search args""" if args.on_date: args.start_date = args.end_date = args.on_date @@ -296,7 +300,7 @@ def _filter_journal_entries(args, journal, **kwargs): journal.limit(args.limit) -def _print_entries_found_count(count, args): +def _print_entries_found_count(count: int, args: "Namespace") -> None: if count == 0: if args.edit or args.change_time: print_msg(Message(MsgText.NothingToModify, MsgStyle.WARNING)) @@ -317,12 +321,14 @@ def _print_entries_found_count(count, args): print_msg(Message(my_msg, MsgStyle.NORMAL, {"num": count})) -def _other_entries(journal, entries): +def _other_entries(journal: Journal, entries: list["Entry"]) -> list["Entry"]: """Find entries that are not in journal""" return [e for e in entries if e not in journal.entries] -def _edit_search_results(config, journal, old_entries, **kwargs): +def _edit_search_results( + config: dict, journal: Journal, old_entries: list["Entry"], **kwargs +) -> None: """ 1. Send the given journal entries to the user-configured editor 2. Print out stats on any modifications to journal @@ -356,7 +362,9 @@ def _edit_search_results(config, journal, old_entries, **kwargs): journal.write() -def _print_edited_summary(journal, old_stats, **kwargs): +def _print_edited_summary( + journal: Journal, old_stats: dict[str, int], **kwargs +) -> None: stats = { "added": len(journal) - old_stats["count"], "deleted": old_stats["count"] - len(journal), @@ -395,11 +403,13 @@ def _print_edited_summary(journal, old_stats, **kwargs): print_msgs(msgs) -def _get_predit_stats(journal): +def _get_predit_stats(journal: Journal) -> dict[str, int]: return {"count": len(journal)} -def _delete_search_results(journal, old_entries, **kwargs): +def _delete_search_results( + journal: Journal, old_entries: list["Entry"], **kwargs +) -> None: entries_to_delete = journal.prompt_action_entries(MsgText.DeleteEntryQuestion) if entries_to_delete: @@ -409,7 +419,13 @@ def _delete_search_results(journal, old_entries, **kwargs): journal.write() -def _change_time_search_results(args, journal, old_entries, no_prompt=False, **kwargs): +def _change_time_search_results( + args: "Namespace", + journal: Journal, + old_entries: list["Entry"], + no_prompt: bool = False, + **kwargs +) -> None: # separate entries we are not editing other_entries = _other_entries(journal, old_entries) @@ -432,7 +448,7 @@ def _change_time_search_results(args, journal, old_entries, no_prompt=False, **k journal.write() -def _display_search_results(args, journal, **kwargs): +def _display_search_results(args: "Namespace", journal: Journal, **kwargs) -> None: # Get export format from config file if not provided at the command line args.export = args.export or kwargs["config"].get("display_format") diff --git a/jrnl/os_compat.py b/jrnl/os_compat.py index 2bb46bc5..ef6d878a 100644 --- a/jrnl/os_compat.py +++ b/jrnl/os_compat.py @@ -5,14 +5,14 @@ import shlex from sys import platform -def on_windows(): +def on_windows() -> bool: return "win32" in platform -def on_posix(): +def on_posix() -> bool: return not on_windows() -def split_args(args): +def split_args(args: str) -> list[str]: """Split arguments and add escape characters as appropriate for the OS""" return shlex.split(args, posix=on_posix()) diff --git a/jrnl/output.py b/jrnl/output.py index 2db0362e..0781263c 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -2,7 +2,7 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html import textwrap -from typing import Union +from typing import Callable from rich.console import Console from rich.text import Text @@ -12,7 +12,9 @@ from jrnl.messages import MsgStyle from jrnl.messages import MsgText -def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): +def deprecated_cmd( + old_cmd: str, new_cmd: str, callback: Callable | None = None, **kwargs +) -> None: print_msg( Message( MsgText.DeprecatedCommand, @@ -25,13 +27,13 @@ def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): callback(**kwargs) -def journal_list_to_json(journal_list): +def journal_list_to_json(journal_list: dict) -> str: import json return json.dumps(journal_list) -def journal_list_to_yaml(journal_list): +def journal_list_to_yaml(journal_list: dict) -> str: from io import StringIO from ruamel.yaml import YAML @@ -41,7 +43,7 @@ def journal_list_to_yaml(journal_list): return output.getvalue() -def journal_list_to_stdout(journal_list): +def journal_list_to_stdout(journal_list: dict) -> str: result = f"Journals defined in config ({journal_list['config_path']})\n" ml = min(max(len(k) for k in journal_list["journals"]), 20) for journal, cfg in journal_list["journals"].items(): @@ -51,7 +53,7 @@ def journal_list_to_stdout(journal_list): return result -def list_journals(configuration, format=None): +def list_journals(configuration: dict, format: str | None = None) -> str: from jrnl import config """List the journals specified in the configuration file""" @@ -69,7 +71,7 @@ def list_journals(configuration, format=None): return journal_list_to_stdout(journal_list) -def print_msg(msg: Message, **kwargs) -> Union[None, str]: +def print_msg(msg: Message, **kwargs) -> str | None: """Helper function to print a single message""" kwargs["style"] = msg.style return print_msgs([msg], **kwargs) @@ -81,7 +83,7 @@ def print_msgs( style: MsgStyle = MsgStyle.NORMAL, get_input: bool = False, hide_input: bool = False, -) -> Union[None, str]: +) -> str | None: # Same as print_msg, but for a list text = Text("", end="") kwargs = style.decoration.args @@ -113,7 +115,7 @@ def _get_console(stderr: bool = True) -> Console: return Console(stderr=stderr) -def _add_extra_style_args_if_needed(args, msg): +def _add_extra_style_args_if_needed(args: dict, msg: Message): args["border_style"] = msg.style.color args["title"] = msg.style.box_title return args diff --git a/jrnl/override.py b/jrnl/override.py index 75a400b9..932add85 100644 --- a/jrnl/override.py +++ b/jrnl/override.py @@ -37,12 +37,12 @@ def apply_overrides(args: "Namespace", base_config: dict) -> dict: return base_config -def _get_key_and_value_from_pair(pairs): +def _get_key_and_value_from_pair(pairs: dict) -> tuple: key_as_dots, override_value = list(pairs.items())[0] return key_as_dots, override_value -def _convert_dots_to_list(key_as_dots): +def _convert_dots_to_list(key_as_dots: str) -> list[str]: keys = key_as_dots.split(".") keys = [k for k in keys if k != ""] # remove empty elements return keys diff --git a/jrnl/path.py b/jrnl/path.py index 0ec3492f..97e04ffe 100644 --- a/jrnl/path.py +++ b/jrnl/path.py @@ -4,13 +4,13 @@ import os.path -def home_dir(): +def home_dir() -> str: return os.path.expanduser("~") -def expand_path(path): +def expand_path(path: str) -> str: return os.path.expanduser(os.path.expandvars(path)) -def absolute_path(path): +def absolute_path(path: str) -> str: return os.path.abspath(expand_path(path)) diff --git a/jrnl/plugins/__init__.py b/jrnl/plugins/__init__.py index ebfe5d3f..f67cebaf 100644 --- a/jrnl/plugins/__init__.py +++ b/jrnl/plugins/__init__.py @@ -1,6 +1,8 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +from typing import Type + from jrnl.plugins.dates_exporter import DatesExporter from jrnl.plugins.fancy_exporter import FancyExporter from jrnl.plugins.jrnl_importer import JRNLImporter @@ -32,14 +34,14 @@ EXPORT_FORMATS = sorted(__exporter_types.keys()) IMPORT_FORMATS = sorted(__importer_types.keys()) -def get_exporter(format): +def get_exporter(format: str) -> Type[TextExporter] | None: for exporter in __exporters: if hasattr(exporter, "names") and format in exporter.names: return exporter return None -def get_importer(format): +def get_importer(format: str) -> Type[JRNLImporter] | None: for importer in __importers: if hasattr(importer, "names") and format in importer.names: return importer diff --git a/jrnl/plugins/dates_exporter.py b/jrnl/plugins/dates_exporter.py index a5032f49..920c6ff9 100644 --- a/jrnl/plugins/dates_exporter.py +++ b/jrnl/plugins/dates_exporter.py @@ -2,9 +2,14 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html from collections import Counter +from typing import TYPE_CHECKING from jrnl.plugins.text_exporter import TextExporter +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class DatesExporter(TextExporter): """This Exporter lists dates and their respective counts, for heatingmapping etc.""" @@ -13,11 +18,11 @@ class DatesExporter(TextExporter): extension = "dates" @classmethod - def export_entry(cls, entry): + def export_entry(cls, entry: "Entry"): raise NotImplementedError @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal: "Journal") -> str: """Returns dates and their frequencies for an entire journal.""" date_counts = Counter() for entry in journal.entries: diff --git a/jrnl/plugins/fancy_exporter.py b/jrnl/plugins/fancy_exporter.py index 1eaf74c0..50189ab3 100644 --- a/jrnl/plugins/fancy_exporter.py +++ b/jrnl/plugins/fancy_exporter.py @@ -4,6 +4,7 @@ import logging import os from textwrap import TextWrapper +from typing import TYPE_CHECKING from jrnl.exception import JrnlException from jrnl.messages import Message @@ -11,6 +12,10 @@ from jrnl.messages import MsgStyle from jrnl.messages import MsgText from jrnl.plugins.text_exporter import TextExporter +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class FancyExporter(TextExporter): """This Exporter can convert entries and journals into text with unicode box drawing characters.""" @@ -35,7 +40,7 @@ class FancyExporter(TextExporter): border_m = "┘" @classmethod - def export_entry(cls, entry): + def export_entry(cls, entry: "Entry") -> str: """Returns a fancy unicode representation of a single entry.""" date_str = entry.date.strftime(entry.journal.config["timeformat"]) @@ -95,12 +100,12 @@ class FancyExporter(TextExporter): return "\n".join(card) @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal) -> str: """Returns a unicode representation of an entire journal.""" return "\n".join(cls.export_entry(entry) for entry in journal) -def check_provided_linewrap_viability(linewrap, card, journal): +def check_provided_linewrap_viability(linewrap: int, card: list[str], journal: "Journal"): if len(card[0]) > linewrap: width_violation = len(card[0]) - linewrap raise JrnlException( diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index c69d4673..e93792de 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -2,6 +2,7 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html import sys +from typing import TYPE_CHECKING from jrnl.exception import JrnlException from jrnl.messages import Message @@ -9,6 +10,9 @@ from jrnl.messages import MsgStyle from jrnl.messages import MsgText from jrnl.output import print_msg +if TYPE_CHECKING: + from jrnl.Journal import Journal + class JRNLImporter: """This plugin imports entries from other jrnl files.""" @@ -16,7 +20,7 @@ class JRNLImporter: names = ["jrnl"] @staticmethod - def import_(journal, input=None): + def import_(journal: "Journal", input: str | None = None) -> None: """Imports from an existing file if input is specified, and standard input otherwise.""" old_cnt = len(journal.entries) diff --git a/jrnl/plugins/json_exporter.py b/jrnl/plugins/json_exporter.py index ae153b70..1a3a5670 100644 --- a/jrnl/plugins/json_exporter.py +++ b/jrnl/plugins/json_exporter.py @@ -2,10 +2,15 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html import json +from typing import TYPE_CHECKING from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.util import get_tags_count +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class JSONExporter(TextExporter): """This Exporter can convert entries and journals into json.""" @@ -14,7 +19,7 @@ class JSONExporter(TextExporter): extension = "json" @classmethod - def entry_to_dict(cls, entry): + def entry_to_dict(cls, entry: "Entry") -> dict: entry_dict = { "title": entry.title, "body": entry.body, @@ -49,12 +54,12 @@ class JSONExporter(TextExporter): return entry_dict @classmethod - def export_entry(cls, entry): + def export_entry(cls, entry: "Entry") -> str: """Returns a json representation of a single entry.""" return json.dumps(cls.entry_to_dict(entry), indent=2) + "\n" @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal: "Journal") -> str: """Returns a json representation of an entire journal.""" tags = get_tags_count(journal) result = { diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index c3476d51..8f0d07b4 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -3,6 +3,7 @@ import os import re +from typing import TYPE_CHECKING from jrnl.messages import Message from jrnl.messages import MsgStyle @@ -10,6 +11,10 @@ from jrnl.messages import MsgText from jrnl.output import print_msg from jrnl.plugins.text_exporter import TextExporter +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class MarkdownExporter(TextExporter): """This Exporter can convert entries and journals into Markdown.""" @@ -18,7 +23,7 @@ class MarkdownExporter(TextExporter): extension = "md" @classmethod - def export_entry(cls, entry, to_multifile=True): + def export_entry(cls, entry: "Entry", to_multifile: bool = True) -> str: """Returns a markdown representation of a single entry.""" date_str = entry.date.strftime(entry.journal.config["timeformat"]) body_wrapper = "\n" if entry.body else "" @@ -73,7 +78,7 @@ class MarkdownExporter(TextExporter): return f"{heading} {date_str} {entry.title}\n{newbody} " @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal: "Journal") -> str: """Returns a Markdown representation of an entire journal.""" out = [] year, month = -1, -1 diff --git a/jrnl/plugins/tag_exporter.py b/jrnl/plugins/tag_exporter.py index b9ab45b3..250f05c7 100644 --- a/jrnl/plugins/tag_exporter.py +++ b/jrnl/plugins/tag_exporter.py @@ -1,9 +1,15 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +from typing import TYPE_CHECKING + from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.util import get_tags_count +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class TagExporter(TextExporter): """This Exporter can lists the tags for entries and journals, exported as a plain text file.""" @@ -12,12 +18,12 @@ class TagExporter(TextExporter): extension = "tags" @classmethod - def export_entry(cls, entry): + def export_entry(cls, entry: "Entry") -> str: """Returns a list of tags for a single entry.""" return ", ".join(entry.tags) @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal: "Journal") -> str: """Returns a list of tags and their frequency for an entire journal.""" tag_counts = get_tags_count(journal) result = "" diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index 4451cfef..7f2321e7 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -5,12 +5,17 @@ import errno import os import re import unicodedata +from typing import TYPE_CHECKING from jrnl.messages import Message from jrnl.messages import MsgStyle from jrnl.messages import MsgText from jrnl.output import print_msg +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class TextExporter: """This Exporter can convert entries and journals into text files.""" @@ -19,17 +24,17 @@ class TextExporter: extension = "txt" @classmethod - def export_entry(cls, entry): + def export_entry(cls, entry: "Entry") -> str: """Returns a string representation of a single entry.""" return str(entry) @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal: "Journal") -> str: """Returns a string representation of an entire journal.""" return "\n".join(cls.export_entry(entry) for entry in journal) @classmethod - def write_file(cls, journal, path): + def write_file(cls, journal: "Journal", path: str) -> str: """Exports a journal into a single file.""" export_str = cls.export_journal(journal) with open(path, "w", encoding="utf-8") as f: @@ -46,13 +51,13 @@ class TextExporter: return "" @classmethod - def make_filename(cls, entry): + def make_filename(cls, entry: "Entry") -> str: return entry.date.strftime("%Y-%m-%d") + "_{}.{}".format( cls._slugify(str(entry.title)), cls.extension ) @classmethod - def write_files(cls, journal, path): + def write_files(cls, journal: "Journal", path: str) -> str: """Exports a journal into individual files for each entry.""" for entry in journal.entries: entry_is_written = False @@ -82,7 +87,7 @@ class TextExporter: ) return "" - def _slugify(string): + def _slugify(string: str) -> str: """Slugifies a string. Based on public domain code from https://github.com/zacharyvoase/slugify """ @@ -92,7 +97,7 @@ class TextExporter: return slug @classmethod - def export(cls, journal, output=None): + def export(cls, journal: "Journal", output: str | None = None) -> str: """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 string if output is None.""" diff --git a/jrnl/plugins/util.py b/jrnl/plugins/util.py index 55ae73c5..009651e9 100644 --- a/jrnl/plugins/util.py +++ b/jrnl/plugins/util.py @@ -1,8 +1,13 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +from typing import TYPE_CHECKING -def get_tags_count(journal): +if TYPE_CHECKING: + from jrnl.Journal import Journal + + +def get_tags_count(journal: "Journal") -> set[tuple[int, str]]: """Returns a set of tuples (count, tag) for all tags present in the journal.""" # Astute reader: should the following line leave you as puzzled as me the first time # I came across this construction, worry not and embrace the ensuing moment of enlightment. @@ -12,7 +17,7 @@ def get_tags_count(journal): return tag_counts -def oxford_list(lst): +def oxford_list(lst: list) -> str: """Return Human-readable list of things obeying the object comma)""" lst = sorted(lst) if not lst: diff --git a/jrnl/plugins/xml_exporter.py b/jrnl/plugins/xml_exporter.py index a6c2fe79..61b6b297 100644 --- a/jrnl/plugins/xml_exporter.py +++ b/jrnl/plugins/xml_exporter.py @@ -1,11 +1,16 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +from typing import TYPE_CHECKING from xml.dom import minidom from jrnl.plugins.json_exporter import JSONExporter from jrnl.plugins.util import get_tags_count +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class XMLExporter(JSONExporter): """This Exporter can convert entries and journals into XML.""" @@ -14,7 +19,9 @@ class XMLExporter(JSONExporter): extension = "xml" @classmethod - def export_entry(cls, entry, doc=None): + def export_entry( + cls, entry: "Entry", doc: minidom.Document | None = None + ) -> minidom.Element | str: """Returns an XML representation of a single entry.""" doc_el = doc or minidom.Document() entry_el = doc_el.createElement("entry") @@ -29,7 +36,7 @@ class XMLExporter(JSONExporter): return entry_el @classmethod - def entry_to_xml(cls, entry, doc): + def entry_to_xml(cls, entry: "Entry", doc: minidom.Document) -> minidom.Element: entry_el = doc.createElement("entry") entry_el.setAttribute("date", entry.date.isoformat()) if hasattr(entry, "uuid"): @@ -44,7 +51,7 @@ class XMLExporter(JSONExporter): return entry_el @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal: "Journal") -> str: """Returns an XML representation of an entire journal.""" tags = get_tags_count(journal) doc = minidom.Document() diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index c97692ad..5f5d5b21 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -3,6 +3,7 @@ import os import re +from typing import TYPE_CHECKING from jrnl.exception import JrnlException from jrnl.messages import Message @@ -11,6 +12,10 @@ from jrnl.messages import MsgText from jrnl.output import print_msg from jrnl.plugins.text_exporter import TextExporter +if TYPE_CHECKING: + from jrnl.Entry import Entry + from jrnl.Journal import Journal + class YAMLExporter(TextExporter): """This Exporter can convert entries and journals into Markdown formatted text with YAML front matter.""" @@ -19,7 +24,7 @@ class YAMLExporter(TextExporter): extension = "md" @classmethod - def export_entry(cls, entry, to_multifile=True): + def export_entry(cls, entry: "Entry", to_multifile: bool = True) -> str: """Returns a markdown representation of a single entry, with YAML front matter.""" if to_multifile is False: raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR)) @@ -124,6 +129,6 @@ class YAMLExporter(TextExporter): ) @classmethod - def export_journal(cls, journal): + def export_journal(cls, journal: "Journal"): """Returns an error, as YAML export requires a directory as a target.""" raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR)) diff --git a/jrnl/prompt.py b/jrnl/prompt.py index f4d62ae4..8f7e36c9 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -42,7 +42,7 @@ def create_password(journal_name: str) -> str: return pw -def yesno(prompt: Message, default: bool = True) -> bool: +def yesno(prompt: Message | str, default: bool = True) -> bool: response = print_msgs( [ prompt, diff --git a/jrnl/time.py b/jrnl/time.py index fd17ca0a..ca1668b3 100644 --- a/jrnl/time.py +++ b/jrnl/time.py @@ -22,8 +22,12 @@ def __get_pdt_calendar(): def parse( - date_str, inclusive=False, default_hour=None, default_minute=None, bracketed=False -): + date_str: str | datetime.datetime, + inclusive: bool = False, + default_hour: int | None = None, + default_minute: int | None = None, + bracketed: bool = False, +) -> datetime.datetime | None: """Parses a string containing a fuzzy date and returns a datetime.datetime object""" if not date_str: return None diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 206b9405..e620537d 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -19,7 +19,7 @@ from jrnl.path import expand_path from jrnl.prompt import yesno -def backup(filename, binary=False): +def backup(filename: str, binary: bool = False): filename = expand_path(filename) try: @@ -42,14 +42,14 @@ def backup(filename, binary=False): raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING)) -def check_exists(path): +def check_exists(path: str) -> bool: """ Checks if a given path exists. """ return os.path.exists(path) -def upgrade_jrnl(config_path): +def upgrade_jrnl(config_path: str) -> None: config = load_config(config_path) print_msg(Message(MsgText.WelcomeToJrnl, MsgStyle.NORMAL, {"version": __version__})) @@ -115,7 +115,7 @@ def upgrade_jrnl(config_path): cont = yesno(Message(MsgText.ContinueUpgrade), default=False) if not cont: - raise JrnlException(Message(MsgText.UpgradeAborted), MsgStyle.WARNING) + raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING)) for journal_name, path in encrypted_journals.items(): print_msg( @@ -178,7 +178,7 @@ def upgrade_jrnl(config_path): print_msg(Message(MsgText.AllDoneUpgrade, MsgStyle.NORMAL)) -def is_old_version(config_path): +def is_old_version(config_path: str) -> bool: return is_config_json(config_path) From 1e694957284034818822ba6b330af716b45ac2e8 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 5 Nov 2022 22:31:41 +0000 Subject: [PATCH 021/373] Update changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d86379..93eaf62d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ **Build:** - Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren)) +- Add type hints [\#1614](https://github.com/jrnl-org/jrnl/pull/1614) ([outa](https://github.com/outa)) ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) From b508ed6c9a307a50800894663f0725589a11b229 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 5 Nov 2022 15:56:46 -0700 Subject: [PATCH 022/373] Add `simplify` plugin to linting checks (#1630) * add simplify plugin for flakeheaven * update lock file * fix linting issues in current codebase --- jrnl/DayOneJournal.py | 32 ++++++++----------------------- jrnl/EncryptedJournal.py | 6 +++--- jrnl/Journal.py | 5 +---- jrnl/install.py | 5 ++--- jrnl/jrnl.py | 3 ++- jrnl/messages/__init__.py | 12 ++++++------ jrnl/plugins/markdown_exporter.py | 4 ++-- poetry.lock | 30 ++++++++++++++++++++++++++++- pyproject.toml | 1 + tests/unit/test_parse_args.py | 2 +- 10 files changed, 55 insertions(+), 45 deletions(-) diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index 4806bdbf..e65ca8d9 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -1,6 +1,7 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +import contextlib import datetime import fnmatch import os @@ -75,40 +76,23 @@ class DayOne(Journal.Journal): ] """Extended DayOne attributes""" - try: + # just ignore it if the keys don't exist + with contextlib.suppress(KeyError): entry.creator_device_agent = dict_entry["Creator"][ "Device Agent" ] - except: # noqa: E722 - pass - try: - entry.creator_generation_date = dict_entry["Creator"][ - "Generation Date" - ] - except: # noqa: E722 - entry.creator_generation_date = date - try: entry.creator_host_name = dict_entry["Creator"]["Host Name"] - except: # noqa: E722 - pass - try: entry.creator_os_agent = dict_entry["Creator"]["OS Agent"] - except: # noqa: E722 - pass - try: entry.creator_software_agent = dict_entry["Creator"][ "Software Agent" ] - except: # noqa: E722 - pass - try: entry.location = dict_entry["Location"] - except: # noqa: E722 - pass - try: entry.weather = dict_entry["Weather"] - except: # noqa: E722 - pass + + entry.creator_generation_date = dict_entry.get("Creator", {}).get( + "Generation Date", date + ) + self.entries.append(entry) self.sort() return self diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index 8b742d29..74fdbdf8 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -2,6 +2,7 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html import base64 +import contextlib import hashlib import logging import os @@ -202,10 +203,9 @@ def set_keychain(journal_name, password): import keyring if password is None: - try: + cm = contextlib.suppress(keyring.errors.KeyringError) + with cm: keyring.delete_password("jrnl", journal_name) - except keyring.errors.KeyringError: - pass else: try: keyring.set_password("jrnl", journal_name, password) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 2fa1d465..3521fbaf 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -120,10 +120,7 @@ class Journal: def validate_parsing(self): """Confirms that the jrnl is still parsed correctly after being dumped to text.""" new_entries = self._parse(self._to_text()) - for i, entry in enumerate(self.entries): - if entry != new_entries[i]: - return False - return True + return all(entry == new_entries[i] for i, entry in enumerate(self.entries)) @staticmethod def create_file(filename): diff --git a/jrnl/install.py b/jrnl/install.py index a37e51f9..b5ec636e 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -1,6 +1,7 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +import contextlib import glob import logging import os @@ -128,10 +129,8 @@ def install() -> dict: # If the folder doesn't exist, create it path = os.path.split(journal_path)[0] - try: + with contextlib.suppress(OSError): os.makedirs(path) - except OSError: - pass # Encrypt it? encrypt = yesno(Message(MsgText.EncryptJournalQuestion), default=False) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 39c25b3f..c79d46e0 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -240,7 +240,8 @@ def _get_editor_template(config: dict, **kwargs) -> str: template_path = expand_path(config["template"]) try: - template = open(template_path).read() + with open(template_path) as f: + template = f.read() logging.debug("Write mode: template loaded: %s", template) except OSError: logging.error("Write mode: template not loaded") diff --git a/jrnl/messages/__init__.py b/jrnl/messages/__init__.py index 381ee332..6bdd9a11 100644 --- a/jrnl/messages/__init__.py +++ b/jrnl/messages/__init__.py @@ -1,10 +1,10 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html -from jrnl.messages.Message import Message -from jrnl.messages.MsgStyle import MsgStyle -from jrnl.messages.MsgText import MsgText +from jrnl.messages import Message +from jrnl.messages import MsgStyle +from jrnl.messages import MsgText -Message = Message -MsgStyle = MsgStyle -MsgText = MsgText +Message = Message.Message +MsgStyle = MsgStyle.MsgStyle +MsgText = MsgText.MsgText diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index 8f0d07b4..8c079c63 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -83,11 +83,11 @@ class MarkdownExporter(TextExporter): out = [] year, month = -1, -1 for e in journal.entries: - if not e.date.year == year: + if e.date.year != year: year = e.date.year out.append("# " + str(year)) out.append("") - if not e.date.month == month: + if e.date.month != month: month = e.date.month out.append("## " + e.date.strftime("%B")) out.append("") diff --git a/poetry.lock b/poetry.lock index 9b4d4c65..8c5b6616 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,6 +17,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "astor" +version = "0.8.1" +description = "Read/rewrite/write Python ASTs" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + [[package]] name = "asttokens" version = "2.0.5" @@ -265,6 +273,18 @@ isort = ">=4.3.5,<6" [package.extras] test = ["pytest"] +[[package]] +name = "flake8-simplify" +version = "0.19.3" +description = "flake8 plugin which checks for code that can be simplified" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +astor = ">=0.1" +flake8 = ">=3.7" + [[package]] name = "flake8-type-checking" version = "2.2.0" @@ -1178,7 +1198,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = ">=3.10.0, <3.13" -content-hash = "63f39baa62c8641eb6329472de340a9f06d9ffea3096a4095e90f98ce2986f91" +content-hash = "d386601320306164cb6332391f68aca47c114da7cad2dfa273d1fcb70824db16" [metadata.files] ansiwrap = [ @@ -1189,6 +1209,10 @@ appnope = [ {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] +astor = [ + {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, + {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, +] asttokens = [ {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, @@ -1379,6 +1403,10 @@ flake8-isort = [ {file = "flake8-isort-5.0.0.tar.gz", hash = "sha256:e336f928c7edc509684930ab124414194b7f4e237c712af8fcbdf49d8747b10c"}, {file = "flake8_isort-5.0.0-py3-none-any.whl", hash = "sha256:c73f9cbd1bf209887f602a27b827164ccfeba1676801b2aa23cb49051a1be79c"}, ] +flake8-simplify = [ + {file = "flake8_simplify-0.19.3-py3-none-any.whl", hash = "sha256:1057320e9312d75849541fee822900d27bcad05b2405edc84713affee635629e"}, + {file = "flake8_simplify-0.19.3.tar.gz", hash = "sha256:2fb083bf5142a98d9c9554755cf2f56f8926eb4a33eae30c0809041b1546879e"}, +] flake8-type-checking = [ {file = "flake8_type_checking-2.2.0-py3-none-any.whl", hash = "sha256:c7d9d7adc6cd635a5a1a7859e5e0140f4f8f1705982a22db45872dd9acd49753"}, {file = "flake8_type_checking-2.2.0.tar.gz", hash = "sha256:f7972fc9102f3f632ace1f4b1c5c20b900b8b7b529f04bb6c1fe0a11801e9658"}, diff --git a/pyproject.toml b/pyproject.toml index e3c5592f..ecd5f015 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ flakeheaven = ">=3.0" flake8-black = ">=0.3.3" flake8-isort = ">=5.0.0" flake8-type-checking = ">=2.2.0" +flake8-simplify = ">=0.19" ipdb = "*" isort = ">=5.10" mkdocs = ">=1.0,<1.3" diff --git a/tests/unit/test_parse_args.py b/tests/unit/test_parse_args.py index 441f06e7..a420daa9 100644 --- a/tests/unit/test_parse_args.py +++ b/tests/unit/test_parse_args.py @@ -293,7 +293,7 @@ class TestDeserialization: runtime_config = make_yaml_valid_dict(input_str) assert runtime_config.__class__ == dict - assert input_str[0] in runtime_config.keys() + assert input_str[0] in runtime_config assert runtime_config[input_str[0]] == input_str[1] def test_deserialize_multiple_datatypes(self): From b7a3110e7eb252eaf5341e3aa814eae3384cf194 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 5 Nov 2022 22:58:54 +0000 Subject: [PATCH 023/373] Update changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93eaf62d..26abe019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ **Build:** - Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren)) +- Add `simplify` plugin to linting checks [\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren)) - Add type hints [\#1614](https://github.com/jrnl-org/jrnl/pull/1614) ([outa](https://github.com/outa)) ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) From 92a8ec4f68a305d5ebb979e8d41ca42d08f9fb7d Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Sat, 5 Nov 2022 16:05:36 -0700 Subject: [PATCH 024/373] Remove Windows 7 known issue since jrnl no longer supports (#1636) --- docs/advanced.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/advanced.md b/docs/advanced.md index 7d46fbd7..140e5b25 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -115,12 +115,3 @@ jrnl --config-file ~/foo/jrnl/work-config.yaml # Use default configuration file (created on first run) jrnl ``` - -## Known Issues - -### Unicode on Windows - -The Windows shell prior to Windows 7 has issues with unicode encoding. -To use non-ascii characters, first tweak Python to recognize the encoding by adding `'cp65001': 'utf_8'`, to `Lib/encoding/aliases.py`. Then, change the codepage with `chcp 1252` before using `jrnl`. - -(Related issue: [#486](https://github.com/jrnl-org/jrnl/issues/486)) From e6e08e5d3e0762a13d903b9abf712bcd2c9f8682 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 5 Nov 2022 23:07:28 +0000 Subject: [PATCH 025/373] Update changelog [ci skip] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26abe019..37038099 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ - Add `simplify` plugin to linting checks [\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren)) - Add type hints [\#1614](https://github.com/jrnl-org/jrnl/pull/1614) ([outa](https://github.com/outa)) +**Documentation:** + +- Remove Windows 7 known issue since Windows 7 is no longer supported [\#1636](https://github.com/jrnl-org/jrnl/pull/1636) ([micahellison](https://github.com/micahellison)) + ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...v3.3) From fcc8d8e3fae1c50ada9584ded2a4f4da81a8a413 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 19 Nov 2022 13:39:39 -0800 Subject: [PATCH 026/373] Rework Encryption to enable future support of other encryption methods (#1602) - initial pass through to rework encryption into separate module - little more cleanup - rename function, fix some linting issues - more cleaning - fix password bug in encryption - fix linting issue - more cleanup - move prompt into prompt.py - more cleanup - update the upgrade process for new encryption classes - general cleanup - turn into enum instead of strings - store status code so tests don't fail - standardize the load and store methods in journals - get rid of old PlainJournal class - typing cleanup - more cleanup - format - fix linting issue - Fix obscure Windows line ending issue with decode See https://bugs.python.org/issue40863 - fix for python 3.11 - add more typing - don't use class variables because that's not what we want - fix more type hints - jrnlv1 encryption doesn't support encryption anymore (it's deprecated) - keep logic for password attemps inside the class that uses it - take out old line of code - add some more logging - update logging statements - clean up logging statements - run linters - fix typo - Fix for new test from develop branch There was a new test added for re-encrypting a journal. This updates the refactor to match the old (previously untested) behavior of jrnl. Co-authored-by: Micah Jerome Ellison --- jrnl/EncryptedJournal.py | 217 ---------------------- jrnl/Journal.py | 55 +++--- jrnl/cli.py | 1 + jrnl/commands.py | 33 ++-- jrnl/encryption/BaseEncryption.py | 52 ++++++ jrnl/encryption/BaseKeyEncryption.py | 7 + jrnl/encryption/BasePasswordEncryption.py | 81 ++++++++ jrnl/encryption/Jrnlv1Encryption.py | 41 ++++ jrnl/encryption/Jrnlv2Encryption.py | 58 ++++++ jrnl/encryption/NoEncryption.py | 19 ++ jrnl/encryption/__init__.py | 34 ++++ jrnl/keyring.py | 28 +++ jrnl/messages/MsgText.py | 2 + jrnl/plugins/fancy_exporter.py | 4 +- jrnl/prompt.py | 18 +- jrnl/upgrade.py | 16 +- tests/bdd/features/encrypt.feature | 20 ++ tests/lib/given_steps.py | 16 +- tests/lib/when_steps.py | 2 +- 19 files changed, 437 insertions(+), 267 deletions(-) delete mode 100644 jrnl/EncryptedJournal.py create mode 100644 jrnl/encryption/BaseEncryption.py create mode 100644 jrnl/encryption/BaseKeyEncryption.py create mode 100644 jrnl/encryption/BasePasswordEncryption.py create mode 100644 jrnl/encryption/Jrnlv1Encryption.py create mode 100644 jrnl/encryption/Jrnlv2Encryption.py create mode 100644 jrnl/encryption/NoEncryption.py create mode 100644 jrnl/encryption/__init__.py create mode 100644 jrnl/keyring.py diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py deleted file mode 100644 index 74fdbdf8..00000000 --- a/jrnl/EncryptedJournal.py +++ /dev/null @@ -1,217 +0,0 @@ -# Copyright © 2012-2022 jrnl contributors -# License: https://www.gnu.org/licenses/gpl-3.0.html - -import base64 -import contextlib -import hashlib -import logging -import os -from typing import Callable -from typing import Optional - -from cryptography.fernet import Fernet -from cryptography.fernet import InvalidToken -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives import padding -from cryptography.hazmat.primitives.ciphers import Cipher -from cryptography.hazmat.primitives.ciphers import algorithms -from cryptography.hazmat.primitives.ciphers import modes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC - -from jrnl.exception import JrnlException -from jrnl.Journal import Journal -from jrnl.Journal import LegacyJournal -from jrnl.messages import Message -from jrnl.messages import MsgStyle -from jrnl.messages import MsgText -from jrnl.output import print_msg -from jrnl.prompt import create_password - - -def make_key(password): - password = password.encode("utf-8") - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - # Salt is hard-coded - salt=b"\xf2\xd5q\x0e\xc1\x8d.\xde\xdc\x8e6t\x89\x04\xce\xf8", - iterations=100_000, - backend=default_backend(), - ) - key = kdf.derive(password) - return base64.urlsafe_b64encode(key) - - -def decrypt_content( - decrypt_func: Callable[[str], Optional[str]], - keychain: str = None, - max_attempts: int = 3, -) -> str: - def get_pw(): - return print_msg( - Message(MsgText.Password, MsgStyle.PROMPT), get_input=True, hide_input=True - ) - - pwd_from_keychain = keychain and get_keychain(keychain) - password = pwd_from_keychain or get_pw() - result = decrypt_func(password) - # Password is bad: - if result is None and pwd_from_keychain: - set_keychain(keychain, None) - attempt = 1 - while result is None and attempt < max_attempts: - print_msg(Message(MsgText.WrongPasswordTryAgain, MsgStyle.WARNING)) - password = get_pw() - result = decrypt_func(password) - attempt += 1 - - if result is None: - raise JrnlException(Message(MsgText.PasswordMaxTriesExceeded, MsgStyle.ERROR)) - - return result - - -class EncryptedJournal(Journal): - def __init__(self, name="default", **kwargs): - super().__init__(name, **kwargs) - self.config["encrypt"] = True - self.password = None - - def open(self, filename=None): - """Opens the journal file defined in the config and parses it into a list of Entries. - Entries have the form (date, title, body).""" - filename = filename or self.config["journal"] - dirname = os.path.dirname(filename) - if not os.path.exists(filename): - if not os.path.isdir(dirname): - os.makedirs(dirname) - print_msg( - Message( - MsgText.DirectoryCreated, - MsgStyle.NORMAL, - {"directory_name": dirname}, - ) - ) - self.create_file(filename) - self.password = create_password(self.name) - - print_msg( - Message( - MsgText.JournalCreated, - MsgStyle.NORMAL, - {"journal_name": self.name, "filename": filename}, - ) - ) - - text = self._load(filename) - self.entries = self._parse(text) - self.sort() - logging.debug("opened %s with %d entries", self.__class__.__name__, len(self)) - return self - - def _load(self, filename): - """Loads an encrypted journal from a file and tries to decrypt it. - If password is not provided, will look for password in the keychain - and otherwise ask the user to enter a password up to three times. - If the password is provided but wrong (or corrupt), this will simply - return None.""" - with open(filename, "rb") as f: - journal_encrypted = f.read() - - def decrypt_journal(password): - key = make_key(password) - try: - plain = Fernet(key).decrypt(journal_encrypted).decode("utf-8") - self.password = password - return plain - except (InvalidToken, IndexError): - return None - - if self.password: - return decrypt_journal(self.password) - - return decrypt_content(keychain=self.name, decrypt_func=decrypt_journal) - - def _store(self, filename, text): - key = make_key(self.password) - journal = Fernet(key).encrypt(text.encode("utf-8")) - with open(filename, "wb") as f: - f.write(journal) - - @classmethod - def from_journal(cls, other: Journal): - new_journal = super().from_journal(other) - new_journal.password = ( - other.password - if hasattr(other, "password") - else create_password(other.name) - ) - - return new_journal - - -class LegacyEncryptedJournal(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().__init__(name, **kwargs) - self.config["encrypt"] = True - self.password = None - - def _load(self, filename): - with open(filename, "rb") as f: - journal_encrypted = f.read() - iv, cipher = journal_encrypted[:16], journal_encrypted[16:] - - def decrypt_journal(password): - decryption_key = hashlib.sha256(password.encode("utf-8")).digest() - decryptor = Cipher( - algorithms.AES(decryption_key), modes.CBC(iv), default_backend() - ).decryptor() - try: - plain_padded = decryptor.update(cipher) + decryptor.finalize() - self.password = password - if plain_padded[-1] in (" ", 32): - # Ancient versions of jrnl. Do not judge me. - return plain_padded.decode("utf-8").rstrip(" ") - else: - unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() - plain = unpadder.update(plain_padded) + unpadder.finalize() - return plain.decode("utf-8") - except ValueError: - return None - - if self.password: - return decrypt_journal(self.password) - return decrypt_content(keychain=self.name, decrypt_func=decrypt_journal) - - -def get_keychain(journal_name): - import keyring - - try: - return keyring.get_password("jrnl", journal_name) - except keyring.errors.KeyringError as e: - if not isinstance(e, keyring.errors.NoKeyringError): - print_msg(Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR)) - return "" - - -def set_keychain(journal_name, password): - import keyring - - if password is None: - cm = contextlib.suppress(keyring.errors.KeyringError) - with cm: - keyring.delete_password("jrnl", journal_name) - else: - try: - keyring.set_password("jrnl", journal_name, password) - except keyring.errors.KeyringError as e: - if isinstance(e, keyring.errors.NoKeyringError): - msg = Message(MsgText.KeyringBackendNotFound, MsgStyle.WARNING) - else: - msg = Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR) - print_msg(msg) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 3521fbaf..2326ac5a 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -9,6 +9,7 @@ import re from jrnl import Entry from jrnl import time from jrnl.config import validate_journal_name +from jrnl.encryption import determine_encryption_method from jrnl.messages import Message from jrnl.messages import MsgStyle from jrnl.messages import MsgText @@ -47,6 +48,7 @@ class Journal: self.search_tags = None # Store tags we're highlighting self.name = name self.entries = [] + self.encryption_method = None def __len__(self): """Returns the number of entries""" @@ -78,6 +80,22 @@ class Journal: self.entries = list(frozenset(self.entries) | frozenset(imported_entries)) self.sort() + def _get_encryption_method(self) -> None: + encryption_method = determine_encryption_method(self.config["encrypt"]) + self.encryption_method = encryption_method(self.name, self.config) + + def _decrypt(self, text: bytes) -> str: + if self.encryption_method is None: + self._get_encryption_method() + + return self.encryption_method.decrypt(text) + + def _encrypt(self, text: str) -> bytes: + if self.encryption_method is None: + self._get_encryption_method() + + return self.encryption_method.encrypt(text) + def open(self, filename=None): """Opens the journal file defined in the config and parses it into a list of Entries. Entries have the form (date, title, body).""" @@ -106,6 +124,7 @@ class Journal: ) text = self._load(filename) + text = self._decrypt(text) self.entries = self._parse(text) self.sort() logging.debug("opened %s with %d entries", self.__class__.__name__, len(self)) @@ -115,6 +134,7 @@ class Journal: """Dumps the journal into the config file, overwriting it""" filename = filename or self.config["journal"] text = self._to_text() + text = self._encrypt(text) self._store(filename, text) def validate_parsing(self): @@ -131,11 +151,12 @@ class Journal: return "\n".join([str(e) for e in self.entries]) def _load(self, filename): - raise NotImplementedError + with open(filename, "rb") as f: + return f.read() - @classmethod - def _store(filename, text): - raise NotImplementedError + def _store(self, filename, text): + with open(filename, "wb") as f: + f.write(text) def _parse(self, journal_txt): """Parses a journal that's stored in a string and returns a list of entries""" @@ -342,7 +363,7 @@ class Journal: 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.""" + manually and later be parsed with self.parse_editable_str.""" return "\n".join([str(e) for e in self.entries]) def parse_editable_str(self, edited): @@ -356,25 +377,11 @@ class Journal: self.entries = mod_entries -class PlainJournal(Journal): - def _load(self, filename): - with open(filename, "r", encoding="utf-8") as f: - return f.read() - - def _store(self, filename, text): - with open(filename, "w", encoding="utf-8") as f: - f.write(text) - - class LegacyJournal(Journal): """Legacy class to support opening journals formatted with the jrnl 1.x 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 open(filename, "r", encoding="utf-8") as f: - return f.read() - def _parse(self, journal_txt): """Parses a journal that's stored in a string and returns a list of entries""" # Entries start with a line that looks like 'date title' - let's figure out how @@ -428,6 +435,7 @@ def open_journal(journal_name, config, legacy=False): If legacy is True, it will open Journals with legacy classes build for backwards compatibility with jrnl 1.x """ + logging.debug("open_journal start") validate_journal_name(journal_name, config) config = config.copy() config["journal"] = expand_path(config["journal"]) @@ -462,10 +470,9 @@ def open_journal(journal_name, config, legacy=False): from jrnl import FolderJournal return FolderJournal.Folder(journal_name, **config).open() - return PlainJournal(journal_name, **config).open() - - from jrnl import EncryptedJournal + return Journal(journal_name, **config).open() if legacy: - return EncryptedJournal.LegacyEncryptedJournal(journal_name, **config).open() - return EncryptedJournal.EncryptedJournal(journal_name, **config).open() + config["encrypt"] = "jrnlv1" + return LegacyJournal(journal_name, **config).open() + return Journal(journal_name, **config).open() diff --git a/jrnl/cli.py b/jrnl/cli.py index 609a8aab..054a34e9 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -29,6 +29,7 @@ def configure_logger(debug: bool = False) -> None: ) logging.getLogger("parsedatetime").setLevel(logging.INFO) logging.getLogger("keyring.backend").setLevel(logging.ERROR) + logging.debug("Logging start") def cli(manual_args: list[str] | None = None) -> int: diff --git a/jrnl/commands.py b/jrnl/commands.py index b1fc81e0..912b21cc 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -15,6 +15,7 @@ Also, please note that all (non-builtin) imports should be scoped to each functi avoid any possible overhead for these standalone commands. """ import argparse +import logging import platform import sys @@ -24,7 +25,6 @@ from jrnl.messages import Message from jrnl.messages import MsgStyle from jrnl.messages import MsgText from jrnl.output import print_msg -from jrnl.prompt import create_password def preconfig_diagnostic(_): @@ -88,7 +88,6 @@ def postconfig_encrypt( Encrypt a journal in place, or optionally to a new file """ from jrnl.config import update_config - from jrnl.EncryptedJournal import EncryptedJournal from jrnl.install import save_config from jrnl.Journal import open_journal @@ -107,21 +106,24 @@ def postconfig_encrypt( ) ) - new_journal = EncryptedJournal.from_journal(journal) - # If journal is encrypted, create new password - if journal.config["encrypt"] is True: - print(f"Journal {journal.name} is already encrypted. Create a new password.") - new_journal.password = create_password(new_journal.name) + logging.debug("Clearing encryption method...") - journal.config["encrypt"] = True - new_journal.write(args.filename) + if journal.config["encrypt"] is True: + logging.debug("Journal already encrypted. Re-encrypting...") + print(f"Journal {journal.name} is already encrypted. Create a new password.") + journal.encryption_method.clear() + else: + journal.config["encrypt"] = True + journal.encryption_method = None + + journal.write(args.filename) print_msg( Message( MsgText.JournalEncryptedTo, MsgStyle.NORMAL, - {"path": args.filename or new_journal.config["journal"]}, + {"path": args.filename or journal.config["journal"]}, ) ) @@ -142,19 +144,20 @@ def postconfig_decrypt( """Decrypts into new file. If filename is not set, we encrypt the journal file itself.""" from jrnl.config import update_config from jrnl.install import save_config - from jrnl.Journal import PlainJournal from jrnl.Journal import open_journal journal = open_journal(args.journal_name, config) - journal.config["encrypt"] = False - new_journal = PlainJournal.from_journal(journal) - new_journal.write(args.filename) + logging.debug("Clearing encryption method...") + journal.config["encrypt"] = False + journal.encryption_method = None + + journal.write(args.filename) print_msg( Message( MsgText.JournalDecryptedTo, MsgStyle.NORMAL, - {"path": args.filename or new_journal.config["journal"]}, + {"path": args.filename or journal.config["journal"]}, ) ) diff --git a/jrnl/encryption/BaseEncryption.py b/jrnl/encryption/BaseEncryption.py new file mode 100644 index 00000000..8fc0fa76 --- /dev/null +++ b/jrnl/encryption/BaseEncryption.py @@ -0,0 +1,52 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +import logging +from abc import ABC +from abc import abstractmethod + +from jrnl.exception import JrnlException +from jrnl.messages import Message +from jrnl.messages import MsgStyle +from jrnl.messages import MsgText + + +class BaseEncryption(ABC): + def __init__(self, journal_name: str, config: dict): + logging.debug("start") + self._encoding: str = "utf-8" + self._journal_name: str = journal_name + self._config: dict = config + + def clear(self) -> None: + pass + + def encrypt(self, text: str) -> bytes: + logging.debug("encrypting") + return self._encrypt(text) + + def decrypt(self, text: bytes) -> str: + logging.debug("decrypting") + if (result := self._decrypt(text)) is None: + raise JrnlException( + Message(MsgText.DecryptionFailedGeneric, MsgStyle.ERROR) + ) + + return result + + @abstractmethod + def _encrypt(self, text: str) -> bytes: + """ + This is needed because self.decrypt might need + to perform actions (e.g. prompt for password) + before actually encrypting. + """ + pass + + @abstractmethod + def _decrypt(self, text: bytes) -> str | None: + """ + This is needed because self.decrypt might need + to perform actions (e.g. prompt for password) + before actually decrypting. + """ + pass diff --git a/jrnl/encryption/BaseKeyEncryption.py b/jrnl/encryption/BaseKeyEncryption.py new file mode 100644 index 00000000..1336796b --- /dev/null +++ b/jrnl/encryption/BaseKeyEncryption.py @@ -0,0 +1,7 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +from .BaseEncryption import BaseEncryption + + +class BaseKeyEncryption(BaseEncryption): + pass diff --git a/jrnl/encryption/BasePasswordEncryption.py b/jrnl/encryption/BasePasswordEncryption.py new file mode 100644 index 00000000..f2642263 --- /dev/null +++ b/jrnl/encryption/BasePasswordEncryption.py @@ -0,0 +1,81 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +import logging + +from jrnl.encryption.BaseEncryption import BaseEncryption +from jrnl.exception import JrnlException +from jrnl.keyring import get_keyring_password +from jrnl.messages import Message +from jrnl.messages import MsgStyle +from jrnl.messages import MsgText +from jrnl.prompt import create_password +from jrnl.prompt import prompt_password + + +class BasePasswordEncryption(BaseEncryption): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + logging.debug("start") + self._attempts: int = 0 + self._max_attempts: int = 3 + self._password: str = "" + self._check_keyring: bool = True + + @property + def check_keyring(self) -> bool: + return self._check_keyring + + @check_keyring.setter + def check_keyring(self, value: bool) -> None: + self._check_keyring = value + + @property + def password(self) -> str | None: + return self._password + + @password.setter + def password(self, value: str) -> None: + self._password = value + + def clear(self): + self.password = None + self.check_keyring = False + + def encrypt(self, text: str) -> bytes: + logging.debug("encrypting") + if not self.password: + if self.check_keyring and ( + keyring_pw := get_keyring_password(self._journal_name) + ): + self.password = keyring_pw + + if not self.password: + self.password = create_password(self._journal_name) + + return self._encrypt(text) + + def decrypt(self, text: bytes) -> str: + logging.debug("decrypting") + if not self.password: + if self.check_keyring and ( + keyring_pw := get_keyring_password(self._journal_name) + ): + self.password = keyring_pw + + if not self.password: + self._prompt_password() + + while (result := self._decrypt(text)) is None: + self._prompt_password() + + return result + + def _prompt_password(self) -> None: + if self._attempts >= self._max_attempts: + raise JrnlException( + Message(MsgText.PasswordMaxTriesExceeded, MsgStyle.ERROR) + ) + + first_try = self._attempts == 0 + self.password = prompt_password(first_try=first_try) + self._attempts += 1 diff --git a/jrnl/encryption/Jrnlv1Encryption.py b/jrnl/encryption/Jrnlv1Encryption.py new file mode 100644 index 00000000..c6343380 --- /dev/null +++ b/jrnl/encryption/Jrnlv1Encryption.py @@ -0,0 +1,41 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +import hashlib +import logging + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import modes + +from jrnl.encryption.BasePasswordEncryption import BasePasswordEncryption + + +class Jrnlv1Encryption(BasePasswordEncryption): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + logging.debug("start") + + def _encrypt(self, _: str) -> bytes: + raise NotImplementedError + + def _decrypt(self, text: bytes) -> str | None: + logging.debug("decrypting") + iv, cipher = text[:16], text[16:] + password = self._password or "" + decryption_key = hashlib.sha256(password.encode(self._encoding)).digest() + decryptor = Cipher( + algorithms.AES(decryption_key), modes.CBC(iv), default_backend() + ).decryptor() + try: + plain_padded = decryptor.update(cipher) + decryptor.finalize() + if plain_padded[-1] in (" ", 32): + # Ancient versions of jrnl. Do not judge me. + return plain_padded.decode(self._encoding).rstrip(" ") + else: + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() + plain = unpadder.update(plain_padded) + unpadder.finalize() + return plain.decode(self._encoding) + except ValueError: + return None diff --git a/jrnl/encryption/Jrnlv2Encryption.py b/jrnl/encryption/Jrnlv2Encryption.py new file mode 100644 index 00000000..c78c5412 --- /dev/null +++ b/jrnl/encryption/Jrnlv2Encryption.py @@ -0,0 +1,58 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +import base64 +import logging + +from cryptography.fernet import Fernet +from cryptography.fernet import InvalidToken +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +from .BasePasswordEncryption import BasePasswordEncryption + + +class Jrnlv2Encryption(BasePasswordEncryption): + def __init__(self, *args, **kwargs) -> None: + # Salt is hard-coded + self._salt: bytes = b"\xf2\xd5q\x0e\xc1\x8d.\xde\xdc\x8e6t\x89\x04\xce\xf8" + self._key: bytes = b"" + + super().__init__(*args, **kwargs) + logging.debug("start") + + @property + def password(self): + return self._password + + @password.setter + def password(self, value: str | None): + self._password = value + self._make_key() + + def _make_key(self) -> None: + if self._password is None: + # Password was removed after being set + self._key = None + return + password = self.password.encode(self._encoding) + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=self._salt, + iterations=100_000, + backend=default_backend(), + ) + key = kdf.derive(password) + self._key = base64.urlsafe_b64encode(key) + + def _encrypt(self, text: str) -> bytes: + logging.debug("encrypting") + return Fernet(self._key).encrypt(text.encode(self._encoding)) + + def _decrypt(self, text: bytes) -> str | None: + logging.debug("decrypting") + try: + return Fernet(self._key).decrypt(text).decode(self._encoding) + except (InvalidToken, IndexError): + return None diff --git a/jrnl/encryption/NoEncryption.py b/jrnl/encryption/NoEncryption.py new file mode 100644 index 00000000..9196389c --- /dev/null +++ b/jrnl/encryption/NoEncryption.py @@ -0,0 +1,19 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +import logging + +from jrnl.encryption.BaseEncryption import BaseEncryption + + +class NoEncryption(BaseEncryption): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + logging.debug("start") + + def _encrypt(self, text: str) -> bytes: + logging.debug("encrypting") + return text.encode(self._encoding) + + def _decrypt(self, text: bytes) -> str: + logging.debug("decrypting") + return text.decode(self._encoding) diff --git a/jrnl/encryption/__init__.py b/jrnl/encryption/__init__.py new file mode 100644 index 00000000..56e36c2c --- /dev/null +++ b/jrnl/encryption/__init__.py @@ -0,0 +1,34 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +from enum import Enum +from importlib import import_module +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .BaseEncryption import BaseEncryption + + +class EncryptionMethods(str, Enum): + def __str__(self) -> str: + return self.value + + NONE = "NoEncryption" + JRNLV1 = "Jrnlv1Encryption" + JRNLV2 = "Jrnlv2Encryption" + + +def determine_encryption_method(config: str | bool) -> "BaseEncryption": + ENCRYPTION_METHODS = { + True: EncryptionMethods.JRNLV2, # the default + False: EncryptionMethods.NONE, + "jrnlv1": EncryptionMethods.JRNLV1, + "jrnlv2": EncryptionMethods.JRNLV2, + } + + key = config + if isinstance(config, str): + key = config.lower() + + my_class = ENCRYPTION_METHODS[key] + + return getattr(import_module(f"jrnl.encryption.{my_class}"), my_class) diff --git a/jrnl/keyring.py b/jrnl/keyring.py new file mode 100644 index 00000000..250a0bee --- /dev/null +++ b/jrnl/keyring.py @@ -0,0 +1,28 @@ +# Copyright © 2012-2022 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +import keyring + +from jrnl.messages import Message +from jrnl.messages import MsgStyle +from jrnl.messages import MsgText +from jrnl.output import print_msg + + +def get_keyring_password(journal_name: str = "default") -> str | None: + try: + return keyring.get_password("jrnl", journal_name) + except keyring.errors.KeyringError as e: + if not isinstance(e, keyring.errors.NoKeyringError): + print_msg(Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR)) + return None + + +def set_keyring_password(password: str, journal_name: str = "default"): + try: + return keyring.set_password("jrnl", journal_name, password) + except keyring.errors.KeyringError as e: + if isinstance(e, keyring.errors.NoKeyringError): + msg = Message(MsgText.KeyringBackendNotFound, MsgStyle.WARNING) + else: + msg = Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR) + print_msg(msg) diff --git a/jrnl/messages/MsgText.py b/jrnl/messages/MsgText.py index b3cc50e7..8bcb7695 100644 --- a/jrnl/messages/MsgText.py +++ b/jrnl/messages/MsgText.py @@ -93,6 +93,8 @@ class MsgText(Enum): of journal can't be encrypted. Please fix your config file. """ + DecryptionFailedGeneric = "The decryption of journal data failed." + KeyboardInterruptMsg = "Aborted by user" CantReadTemplate = """ diff --git a/jrnl/plugins/fancy_exporter.py b/jrnl/plugins/fancy_exporter.py index 50189ab3..68b79155 100644 --- a/jrnl/plugins/fancy_exporter.py +++ b/jrnl/plugins/fancy_exporter.py @@ -105,7 +105,9 @@ class FancyExporter(TextExporter): return "\n".join(cls.export_entry(entry) for entry in journal) -def check_provided_linewrap_viability(linewrap: int, card: list[str], journal: "Journal"): +def check_provided_linewrap_viability( + linewrap: int, card: list[str], journal: "Journal" +): if len(card[0]) > linewrap: width_violation = len(card[0]) - linewrap raise JrnlException( diff --git a/jrnl/prompt.py b/jrnl/prompt.py index 8f7e36c9..f4fa681a 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -35,13 +35,27 @@ def create_password(journal_name: str) -> str: print_msg(Message(MsgText.PasswordDidNotMatch, MsgStyle.ERROR)) if yesno(Message(MsgText.PasswordStoreInKeychain), default=True): - from jrnl.EncryptedJournal import set_keychain + from jrnl.keyring import set_keyring_password - set_keychain(journal_name, pw) + set_keyring_password(pw, journal_name) return pw +def prompt_password(first_try: bool = True) -> str: + if not first_try: + print_msg(Message(MsgText.WrongPasswordTryAgain, MsgStyle.WARNING)) + + return ( + print_msg( + Message(MsgText.Password, MsgStyle.PROMPT), + get_input=True, + hide_input=True, + ) + or "" + ) + + def yesno(prompt: Message | str, default: bool = True) -> bool: response = print_msgs( [ diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index e620537d..775a84bf 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -1,6 +1,7 @@ # Copyright © 2012-2022 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +import logging import os from jrnl import Journal @@ -8,7 +9,6 @@ from jrnl import __version__ from jrnl.config import is_config_json from jrnl.config import load_config from jrnl.config import scope_config -from jrnl.EncryptedJournal import EncryptedJournal from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgStyle @@ -132,7 +132,17 @@ def upgrade_jrnl(config_path: str) -> None: old_journal = Journal.open_journal( journal_name, scope_config(config, journal_name), legacy=True ) - all_journals.append(EncryptedJournal.from_journal(old_journal)) + + logging.debug(f"Clearing encryption method for '{journal_name}' journal") + + # Update the encryption method + new_journal = Journal.Journal.from_journal(old_journal) + new_journal.config["encrypt"] = "jrnlv2" + new_journal._get_encryption_method() + # Copy over password (jrnlv1 only supported password-based encryption) + new_journal.encryption_method.password = old_journal.encryption_method.password + + all_journals.append(new_journal) for journal_name, path in plain_journals.items(): print_msg( @@ -149,7 +159,7 @@ def upgrade_jrnl(config_path: str) -> None: old_journal = Journal.open_journal( journal_name, scope_config(config, journal_name), legacy=True ) - all_journals.append(Journal.PlainJournal.from_journal(old_journal)) + all_journals.append(Journal.Journal.from_journal(old_journal)) # loop through lists to validate failed_journals = [j for j in all_journals if not j.validate_parsing()] diff --git a/tests/bdd/features/encrypt.feature b/tests/bdd/features/encrypt.feature index b5ef1126..8442ca66 100644 --- a/tests/bdd/features/encrypt.feature +++ b/tests/bdd/features/encrypt.feature @@ -48,6 +48,7 @@ Feature: Encrypting and decrypting journals Scenario: Encrypt journal twice and get prompted each time Given we use the config "simple.yaml" + And we don't have a keyring When we run "jrnl --encrypt" and enter swordfish swordfish @@ -56,7 +57,26 @@ Feature: Encrypting and decrypting journals And the output should contain "Journal encrypted" When we run "jrnl --encrypt" and enter swordfish + tuna + tuna + y + Then we should get no error + And the output should contain "Journal default is already encrypted. Create a new password." + And we should be prompted for a password + And the config for journal "default" should contain "encrypt: true" + + Scenario: Encrypt journal twice and get prompted each time with keyring + Given we use the config "simple.yaml" + And we have a keyring + When we run "jrnl --encrypt" and enter swordfish + swordfish + y + Then we should get no error + And the output should contain "Journal encrypted" + When we run "jrnl --encrypt" and enter + tuna + tuna y Then we should get no error And the output should contain "Journal default is already encrypted. Create a new password." diff --git a/tests/lib/given_steps.py b/tests/lib/given_steps.py index 5ee88ab7..f0c691d1 100644 --- a/tests/lib/given_steps.py +++ b/tests/lib/given_steps.py @@ -17,6 +17,7 @@ from pytest_bdd.parsers import parse from jrnl import __version__ from jrnl.time import __get_pdt_calendar from tests.lib.fixtures import FailedKeyring +from tests.lib.fixtures import NoKeyring from tests.lib.fixtures import TestKeyring from tests.lib.helpers import get_fixture @@ -67,13 +68,20 @@ def now_is_str(date_str, mock_factories): ) +@given("we don't have a keyring", target_fixture="keyring") +def we_dont_have_keyring(keyring_type): + return NoKeyring() + + @given("we have a keyring", target_fixture="keyring") @given(parse("we have a {keyring_type} keyring"), target_fixture="keyring") def we_have_type_of_keyring(keyring_type): - if keyring_type == "failed": - return FailedKeyring() - else: - return TestKeyring() + match keyring_type: + case "failed": + return FailedKeyring() + + case _: + return TestKeyring() @given(parse('we use the config "{config_file}"'), target_fixture="config_path") diff --git a/tests/lib/when_steps.py b/tests/lib/when_steps.py index 920d66d4..e93d5adf 100644 --- a/tests/lib/when_steps.py +++ b/tests/lib/when_steps.py @@ -44,7 +44,7 @@ def we_run_jrnl(cli_run, capsys, keyring): mocks[id] = stack.enter_context(factories[id]()) try: - cli() + cli_run["status"] = cli() or 0 except StopIteration: # This happens when input is expected, but don't have any input left pass From fe08cfd3abdb161b6a529702333e6eb0cf6f04bc Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 19 Nov 2022 21:41:41 +0000 Subject: [PATCH 027/373] Update changelog [ci skip] --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37038099..fcdc229f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ **Implemented enhancements:** +- Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521) - Add `rich` handler to debug logging [\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren)) +- Rework Encryption to enable future support of other encryption methods [\#1602](https://github.com/jrnl-org/jrnl/pull/1602) ([wren](https://github.com/wren)) **Deprecated:** From d94325127f01f5d0b024a1cbd30350a68249754c Mon Sep 17 00:00:00 2001 From: Jonathan van der Steege Date: Sat, 7 Jan 2023 20:46:12 +0100 Subject: [PATCH 028/373] Update version key in config file after version changes (#1646) --- jrnl/install.py | 9 +++++++++ tests/bdd/features/config_file.feature | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/jrnl/install.py b/jrnl/install.py index b5ec636e..5ae14f36 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -9,6 +9,7 @@ import sys from rich.pretty import pretty_repr +from jrnl import __version__ from jrnl.config import DEFAULT_JOURNAL_KEY from jrnl.config import get_config_path from jrnl.config import get_default_config @@ -32,12 +33,20 @@ def upgrade_config(config_data: dict, alt_config_path: str | None = None) -> Non """Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly. This essentially automatically ports jrnl installations if new config parameters are introduced in later versions. + Also checks for existence of and difference in version number between config dict and current jrnl version, + and if so, update the config file accordingly. Supply alt_config_path if using an alternate config through --config-file.""" default_config = get_default_config() missing_keys = set(default_config).difference(config_data) if missing_keys: for key in missing_keys: config_data[key] = default_config[key] + + different_version = (config_data["version"] != __version__) + if different_version: + config_data["version"] = __version__ + + if missing_keys or different_version: save_config(config_data, alt_config_path) config_path = alt_config_path if alt_config_path else get_config_path() print_msg( diff --git a/tests/bdd/features/config_file.feature b/tests/bdd/features/config_file.feature index 3369e666..2d2aade0 100644 --- a/tests/bdd/features/config_file.feature +++ b/tests/bdd/features/config_file.feature @@ -133,3 +133,9 @@ Feature: Multiple journals Given we use the config "multiple.yaml" When we run "jrnl --config-override highlight false" Then the output should not contain "There is at least one duplicate key in your configuration file" + + Scenario: Update version number in config file when running newer version + Given we use the config "format_md.yaml" + When we run "jrnl -1" + Then the output should contain "Configuration updated to newest version at" + And the version in the config file should be up-to-date \ No newline at end of file From 7229d77bda56084bd9698f45878654e57bed0a3f Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 7 Jan 2023 19:48:26 +0000 Subject: [PATCH 029/373] Update changelog [ci skip] --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcdc229f..5010a84f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ - Add `rich` handler to debug logging [\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren)) - Rework Encryption to enable future support of other encryption methods [\#1602](https://github.com/jrnl-org/jrnl/pull/1602) ([wren](https://github.com/wren)) +**Fixed bugs:** + +- jrnl does not update version key in config file [\#1638](https://github.com/jrnl-org/jrnl/issues/1638) +- Update version key in config file after version changes [\#1646](https://github.com/jrnl-org/jrnl/pull/1646) ([jonakeys](https://github.com/jonakeys)) + **Deprecated:** - Drop Python 3.9 and use Python 3.11 official release [\#1611](https://github.com/jrnl-org/jrnl/pull/1611) ([micahellison](https://github.com/micahellison)) @@ -22,6 +27,7 @@ **Documentation:** +- Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651) - Remove Windows 7 known issue since Windows 7 is no longer supported [\#1636](https://github.com/jrnl-org/jrnl/pull/1636) ([micahellison](https://github.com/micahellison)) ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) From 907566b39f156e23a1369fd7425e29c10bffc7d2 Mon Sep 17 00:00:00 2001 From: Jonathan van der Steege Date: Sat, 7 Jan 2023 21:51:07 +0100 Subject: [PATCH 030/373] Add tag to XML file when edited DayOne entry and is searchable afterward (#1648) * Add tag to XML file when edited DayOne entry * Remove forbidden change * undo edits * Tags working for DayOne journal * Correction doentries to prevent time-error * Add sorting to tags * Delete test statements * Revert time changes --- jrnl/DayOneJournal.py | 8 +++++++- tests/bdd/features/write.feature | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index e65ca8d9..b97ea060 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -74,6 +74,7 @@ class DayOne(Journal.Journal): self.config["tagsymbols"][0] + tag.lower() for tag in dict_entry.get("Tags", []) ] + if entry._tags: entry._tags.sort() """Extended DayOne attributes""" # just ignore it if the keys don't exist @@ -167,7 +168,7 @@ class DayOne(Journal.Journal): return "\n".join([f"{str(e)}\n# {e.uuid}\n" for e in self.entries]) def _update_old_entry(self, entry: Entry, new_entry: Entry) -> None: - for attr in ("title", "body", "date"): + for attr in ("title", "body", "date", "tags"): old_attr = getattr(entry, attr) new_attr = getattr(new_entry, attr) if old_attr != new_attr: @@ -195,6 +196,7 @@ class DayOne(Journal.Journal): for entry in entries_from_editor: entry = self._get_and_remove_uuid_from_entry(entry) + if entry._tags: entry._tags.sort() # Remove deleted entries edited_uuids = [e.uuid for e in entries_from_editor] @@ -204,5 +206,9 @@ class DayOne(Journal.Journal): for entry in entries_from_editor: for old_entry in self.entries: if entry.uuid == old_entry.uuid: + if old_entry._tags: + tags_not_in_body = [tag for tag in old_entry._tags if(tag not in entry._body)] + if tags_not_in_body: + entry._tags.extend(tags_not_in_body.sort()) self._update_old_entry(old_entry, entry) break diff --git a/tests/bdd/features/write.feature b/tests/bdd/features/write.feature index 33b81cdd..1fb6b42a 100644 --- a/tests/bdd/features/write.feature +++ b/tests/bdd/features/write.feature @@ -334,3 +334,20 @@ Feature: Writing new entries. When we run "jrnl work This is a new entry" Then the output should contain "Entry added to work journal" And we should get no error + + Scenario Outline: Tags are saved when an entry is edited with --edit and can be searched afterward + Given we use the config "" + And we use the password "test" if prompted + And we append to the editor if opened + @newtag + When we run "jrnl --edit -1" + When we run "jrnl --tags @newtag" + Then the output should contain + 1 entry found + + Examples: configs + | config_file | + | basic_onefile.yaml | + | basic_encrypted.yaml | + | basic_folder.yaml | + | basic_dayone.yaml | From d5d39971b4f8356c39564880599c3a747ad697ad Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 7 Jan 2023 20:53:14 +0000 Subject: [PATCH 031/373] Update changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5010a84f..a157b717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ **Fixed bugs:** - jrnl does not update version key in config file [\#1638](https://github.com/jrnl-org/jrnl/issues/1638) +- Add tag to XML file when edited DayOne entry and is searchable afterward [\#1648](https://github.com/jrnl-org/jrnl/pull/1648) ([jonakeys](https://github.com/jonakeys)) - Update version key in config file after version changes [\#1646](https://github.com/jrnl-org/jrnl/pull/1646) ([jonakeys](https://github.com/jonakeys)) **Deprecated:** From 9274ab16ed2a06913d544c5d48db8f533d8699e5 Mon Sep 17 00:00:00 2001 From: Micah Jerome Ellison Date: Sat, 7 Jan 2023 13:37:43 -0800 Subject: [PATCH 032/373] Add basic template test (#1659) * Add very basic test template * Ensure that templates are copied to temporary test workspace * Add basic template test * Update copyright for this new file --- tests/bdd/features/template.feature | 19 +++++++++++++++++++ tests/bdd/test_features.py | 1 + tests/data/templates/basic.template | 1 + tests/lib/given_steps.py | 6 ++++++ 4 files changed, 27 insertions(+) create mode 100644 tests/bdd/features/template.feature create mode 100644 tests/data/templates/basic.template diff --git a/tests/bdd/features/template.feature b/tests/bdd/features/template.feature new file mode 100644 index 00000000..02e2a3bc --- /dev/null +++ b/tests/bdd/features/template.feature @@ -0,0 +1,19 @@ +# Copyright © 2012-2023 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html + +Feature: Using templates + + Scenario Outline: Template contents should be used in new entry + Given we use the config "" + And we use the password "test" if prompted + When we run "jrnl --config-override template features/templates/basic.template" + And we run "jrnl -1" + Then the output should contain "This text is in the basic template" + + Examples: configs + | config_file | + | basic_onefile.yaml | + | basic_encrypted.yaml | + | basic_folder.yaml | + | basic_dayone.yaml | + diff --git a/tests/bdd/test_features.py b/tests/bdd/test_features.py index 387bd6cc..196f1cd9 100644 --- a/tests/bdd/test_features.py +++ b/tests/bdd/test_features.py @@ -20,5 +20,6 @@ scenarios("features/password.feature") scenarios("features/search.feature") scenarios("features/star.feature") scenarios("features/tag.feature") +scenarios("features/template.feature") scenarios("features/upgrade.feature") scenarios("features/write.feature") diff --git a/tests/data/templates/basic.template b/tests/data/templates/basic.template new file mode 100644 index 00000000..9834eb39 --- /dev/null +++ b/tests/data/templates/basic.template @@ -0,0 +1 @@ +This text is in the basic template diff --git a/tests/lib/given_steps.py b/tests/lib/given_steps.py index f0c691d1..8e930a31 100644 --- a/tests/lib/given_steps.py +++ b/tests/lib/given_steps.py @@ -106,6 +106,12 @@ def we_use_the_config(request, temp_dir, working_dir): journal_dest = os.path.join(temp_dir.name, "features", "journals") shutil.copytree(journal_source, journal_dest) + # @todo maybe only copy needed templates over? + # Copy all of the templates over + template_source = os.path.join(working_dir, "data", "templates") + template_dest = os.path.join(temp_dir.name, "features", "templates") + shutil.copytree(template_source, template_dest) + # @todo get rid of this by using default config values # merge in version number if ( From 8da6029624529d52e63144b91c2b54b2b1175172 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 7 Jan 2023 13:39:07 -0800 Subject: [PATCH 033/373] Update copyright notices for 2023 (#1660) * update copyright notice for 2023 * standardize whitespace after copyright notice --- .github/workflows/changelog.yaml | 2 +- .github/workflows/docs.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/testing_pipelines.yaml | 2 +- .github/workflows/testing_prs.yaml | 2 +- .github/workflows/testing_schedule.yaml | 2 +- CONTRIBUTING.md | 2 +- README.md | 2 +- docs/advanced.md | 2 +- docs/contributing.md | 2 +- docs/encryption.md | 2 +- docs/external-editors.md | 2 +- docs/formats.md | 2 +- docs/installation.md | 2 +- docs/journal-types.md | 2 +- docs/overview.md | 2 +- docs/privacy-and-security.md | 2 +- docs/reference-command-line.md | 2 +- docs/reference-config-file.md | 2 +- docs/tips-and-tricks.md | 2 +- docs/usage.md | 2 +- docs_theme/assets/colors.css | 2 +- docs_theme/assets/highlight.css | 2 +- docs_theme/assets/index.css | 2 +- docs_theme/assets/theme.css | 2 +- docs_theme/index.html | 2 +- docs_theme/main.html | 2 +- docs_theme/search.html | 2 +- jrnl/DayOneJournal.py | 2 +- jrnl/Entry.py | 2 +- jrnl/FolderJournal.py | 2 +- jrnl/Journal.py | 2 +- jrnl/__init__.py | 2 +- jrnl/__main__.py | 2 +- jrnl/args.py | 2 +- jrnl/cli.py | 2 +- jrnl/color.py | 2 +- jrnl/commands.py | 5 +++-- jrnl/config.py | 2 +- jrnl/editor.py | 2 +- jrnl/encryption/BaseEncryption.py | 3 ++- jrnl/encryption/BaseKeyEncryption.py | 3 ++- jrnl/encryption/BasePasswordEncryption.py | 3 ++- jrnl/encryption/Jrnlv1Encryption.py | 3 ++- jrnl/encryption/Jrnlv2Encryption.py | 3 ++- jrnl/encryption/NoEncryption.py | 3 ++- jrnl/encryption/__init__.py | 3 ++- jrnl/exception.py | 2 +- jrnl/install.py | 2 +- jrnl/jrnl.py | 3 ++- jrnl/keyring.py | 3 ++- jrnl/messages/Message.py | 2 +- jrnl/messages/MsgStyle.py | 2 +- jrnl/messages/MsgText.py | 2 +- jrnl/messages/__init__.py | 2 +- jrnl/os_compat.py | 2 +- jrnl/output.py | 2 +- jrnl/override.py | 3 ++- jrnl/path.py | 2 +- jrnl/plugins/__init__.py | 2 +- jrnl/plugins/dates_exporter.py | 2 +- jrnl/plugins/fancy_exporter.py | 2 +- jrnl/plugins/jrnl_importer.py | 2 +- jrnl/plugins/json_exporter.py | 2 +- jrnl/plugins/markdown_exporter.py | 2 +- jrnl/plugins/tag_exporter.py | 2 +- jrnl/plugins/text_exporter.py | 2 +- jrnl/plugins/util.py | 2 +- jrnl/plugins/xml_exporter.py | 2 +- jrnl/plugins/yaml_exporter.py | 2 +- jrnl/prompt.py | 2 +- jrnl/time.py | 2 +- jrnl/upgrade.py | 2 +- tasks.py | 2 +- tests/bdd/features/build.feature | 2 +- tests/bdd/features/change_time.feature | 2 +- tests/bdd/features/config_file.feature | 2 +- tests/bdd/features/core.feature | 2 +- tests/bdd/features/datetime.feature | 2 +- tests/bdd/features/delete.feature | 2 +- tests/bdd/features/encrypt.feature | 2 +- tests/bdd/features/file_storage.feature | 2 +- tests/bdd/features/format.feature | 2 +- tests/bdd/features/import.feature | 2 +- tests/bdd/features/multiple_journals.feature | 2 +- tests/bdd/features/override.feature | 2 +- tests/bdd/features/password.feature | 2 +- tests/bdd/features/search.feature | 2 +- tests/bdd/features/star.feature | 2 +- tests/bdd/features/tag.feature | 2 +- tests/bdd/features/upgrade.feature | 2 +- tests/bdd/features/write.feature | 2 +- tests/bdd/test_features.py | 2 +- tests/conftest.py | 2 +- tests/lib/fixtures.py | 2 +- tests/lib/given_steps.py | 2 +- tests/lib/helpers.py | 2 +- tests/lib/then_steps.py | 2 +- tests/lib/when_steps.py | 2 +- tests/unit/test_color.py | 2 +- tests/unit/test_config_file.py | 2 +- tests/unit/test_export.py | 2 +- tests/unit/test_install.py | 2 +- tests/unit/test_jrnl.py | 2 +- tests/unit/test_os_compat.py | 2 +- tests/unit/test_output.py | 2 +- tests/unit/test_override.py | 2 +- tests/unit/test_parse_args.py | 2 +- tests/unit/test_path.py | 2 +- tests/unit/test_time.py | 2 +- 110 files changed, 122 insertions(+), 111 deletions(-) diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index f8b4eca1..0c40ce8f 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html name: Changelog diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 988e1cfc..1e822dc7 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html name: Docs diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4b55f253..82d9b8a7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html name: Release diff --git a/.github/workflows/testing_pipelines.yaml b/.github/workflows/testing_pipelines.yaml index 48da48e4..46b353e2 100644 --- a/.github/workflows/testing_pipelines.yaml +++ b/.github/workflows/testing_pipelines.yaml @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html name: Testing Pipeline Files diff --git a/.github/workflows/testing_prs.yaml b/.github/workflows/testing_prs.yaml index f28c72af..0469bbda 100644 --- a/.github/workflows/testing_prs.yaml +++ b/.github/workflows/testing_prs.yaml @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html name: Testing diff --git a/.github/workflows/testing_schedule.yaml b/.github/workflows/testing_schedule.yaml index ec6c7f8a..dc0ebab2 100644 --- a/.github/workflows/testing_schedule.yaml +++ b/.github/workflows/testing_schedule.yaml @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html name: Testing diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0481aae2..dc078f3a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ diff --git a/README.md b/README.md index e7804059..87c1a859 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ diff --git a/docs/advanced.md b/docs/advanced.md index 140e5b25..e4f37b42 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -1,5 +1,5 @@ diff --git a/docs/contributing.md b/docs/contributing.md index d69f0599..393add7b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,5 +1,5 @@ diff --git a/docs/encryption.md b/docs/encryption.md index b019e4a6..a612bfd1 100644 --- a/docs/encryption.md +++ b/docs/encryption.md @@ -1,5 +1,5 @@ diff --git a/docs/external-editors.md b/docs/external-editors.md index 4e8f70b7..f679d19e 100644 --- a/docs/external-editors.md +++ b/docs/external-editors.md @@ -1,5 +1,5 @@ diff --git a/docs/formats.md b/docs/formats.md index c557f9dc..e78194eb 100644 --- a/docs/formats.md +++ b/docs/formats.md @@ -1,5 +1,5 @@ diff --git a/docs/installation.md b/docs/installation.md index 08defc34..8844f851 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,5 +1,5 @@ diff --git a/docs/journal-types.md b/docs/journal-types.md index a8e79220..8f505215 100644 --- a/docs/journal-types.md +++ b/docs/journal-types.md @@ -1,5 +1,5 @@ diff --git a/docs/overview.md b/docs/overview.md index 5ab25c1a..7b6cc310 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,5 +1,5 @@ diff --git a/docs/privacy-and-security.md b/docs/privacy-and-security.md index f62f1cf6..a55e6fa3 100644 --- a/docs/privacy-and-security.md +++ b/docs/privacy-and-security.md @@ -1,5 +1,5 @@ diff --git a/docs/reference-command-line.md b/docs/reference-command-line.md index 8f906f8d..c78ac76e 100644 --- a/docs/reference-command-line.md +++ b/docs/reference-command-line.md @@ -1,5 +1,5 @@ diff --git a/docs/reference-config-file.md b/docs/reference-config-file.md index 1c58a3a2..c25a8454 100644 --- a/docs/reference-config-file.md +++ b/docs/reference-config-file.md @@ -1,5 +1,5 @@ diff --git a/docs/tips-and-tricks.md b/docs/tips-and-tricks.md index 0c41cc81..a03bb79b 100644 --- a/docs/tips-and-tricks.md +++ b/docs/tips-and-tricks.md @@ -1,5 +1,5 @@ diff --git a/docs/usage.md b/docs/usage.md index 13589e09..dd75c815 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,5 +1,5 @@ diff --git a/docs_theme/assets/colors.css b/docs_theme/assets/colors.css index 4cfe4c79..9f2103f8 100644 --- a/docs_theme/assets/colors.css +++ b/docs_theme/assets/colors.css @@ -1,5 +1,5 @@ /* -Copyright © 2012-2022 jrnl contributors +Copyright © 2012-2023 jrnl contributors License: https://www.gnu.org/licenses/gpl-3.0.html */ diff --git a/docs_theme/assets/highlight.css b/docs_theme/assets/highlight.css index 22f3fabe..45dcd523 100644 --- a/docs_theme/assets/highlight.css +++ b/docs_theme/assets/highlight.css @@ -1,5 +1,5 @@ /* -Copyright © 2012-2022 jrnl contributors +Copyright © 2012-2023 jrnl contributors License: https://www.gnu.org/licenses/gpl-3.0.html Atom One Dark With support for ReasonML by Gidi Morris, based off work by diff --git a/docs_theme/assets/index.css b/docs_theme/assets/index.css index 4aa66b1a..d2d7e7b1 100644 --- a/docs_theme/assets/index.css +++ b/docs_theme/assets/index.css @@ -1,5 +1,5 @@ /* -Copyright © 2012-2022 jrnl contributors +Copyright © 2012-2023 jrnl contributors License: https://www.gnu.org/licenses/gpl-3.0.html */ diff --git a/docs_theme/assets/theme.css b/docs_theme/assets/theme.css index a854022e..6354b9d6 100644 --- a/docs_theme/assets/theme.css +++ b/docs_theme/assets/theme.css @@ -1,5 +1,5 @@ /* -Copyright © 2012-2022 jrnl contributors +Copyright © 2012-2023 jrnl contributors License: https://www.gnu.org/licenses/gpl-3.0.html */ diff --git a/docs_theme/index.html b/docs_theme/index.html index 96d35ebe..b65dd88b 100644 --- a/docs_theme/index.html +++ b/docs_theme/index.html @@ -1,5 +1,5 @@ diff --git a/docs_theme/main.html b/docs_theme/main.html index 456924fe..01a77878 100644 --- a/docs_theme/main.html +++ b/docs_theme/main.html @@ -1,5 +1,5 @@ diff --git a/docs_theme/search.html b/docs_theme/search.html index fe3a23e7..7176ab37 100644 --- a/docs_theme/search.html +++ b/docs_theme/search.html @@ -1,5 +1,5 @@ diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index b97ea060..627b9c65 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import contextlib diff --git a/jrnl/Entry.py b/jrnl/Entry.py index 9dfdf00f..78575027 100644 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import datetime diff --git a/jrnl/FolderJournal.py b/jrnl/FolderJournal.py index ab9547ed..055d9408 100644 --- a/jrnl/FolderJournal.py +++ b/jrnl/FolderJournal.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import codecs diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 2326ac5a..51c0b746 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import datetime diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 6fe12d10..6186dc2e 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html try: diff --git a/jrnl/__main__.py b/jrnl/__main__.py index 7ff8fd4d..69a9fd5e 100644 --- a/jrnl/__main__.py +++ b/jrnl/__main__.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import sys diff --git a/jrnl/args.py b/jrnl/args.py index f7f0eb3a..d23e1e53 100644 --- a/jrnl/args.py +++ b/jrnl/args.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import argparse diff --git a/jrnl/cli.py b/jrnl/cli.py index 054a34e9..a6b159db 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import logging diff --git a/jrnl/color.py b/jrnl/color.py index 91d56254..2aaadad5 100644 --- a/jrnl/color.py +++ b/jrnl/color.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import re diff --git a/jrnl/commands.py b/jrnl/commands.py index 912b21cc..a85e97e9 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html """ @@ -14,6 +14,7 @@ run. Also, please note that all (non-builtin) imports should be scoped to each function to avoid any possible overhead for these standalone commands. """ + import argparse import logging import platform @@ -47,7 +48,7 @@ def preconfig_version(_): output = f""" {__title__} {__version__} - Copyright © 2012-2022 jrnl contributors + Copyright © 2012-2023 jrnl contributors This is free software, and you are welcome to redistribute it under certain conditions; for details, see: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/jrnl/config.py b/jrnl/config.py index bcd67e2c..a8733612 100644 --- a/jrnl/config.py +++ b/jrnl/config.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import argparse diff --git a/jrnl/editor.py b/jrnl/editor.py index d578e2d8..b6799b41 100644 --- a/jrnl/editor.py +++ b/jrnl/editor.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import logging diff --git a/jrnl/encryption/BaseEncryption.py b/jrnl/encryption/BaseEncryption.py index 8fc0fa76..efecd87d 100644 --- a/jrnl/encryption/BaseEncryption.py +++ b/jrnl/encryption/BaseEncryption.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + import logging from abc import ABC from abc import abstractmethod diff --git a/jrnl/encryption/BaseKeyEncryption.py b/jrnl/encryption/BaseKeyEncryption.py index 1336796b..f8c20bc9 100644 --- a/jrnl/encryption/BaseKeyEncryption.py +++ b/jrnl/encryption/BaseKeyEncryption.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + from .BaseEncryption import BaseEncryption diff --git a/jrnl/encryption/BasePasswordEncryption.py b/jrnl/encryption/BasePasswordEncryption.py index f2642263..29a28b76 100644 --- a/jrnl/encryption/BasePasswordEncryption.py +++ b/jrnl/encryption/BasePasswordEncryption.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + import logging from jrnl.encryption.BaseEncryption import BaseEncryption diff --git a/jrnl/encryption/Jrnlv1Encryption.py b/jrnl/encryption/Jrnlv1Encryption.py index c6343380..18a9782b 100644 --- a/jrnl/encryption/Jrnlv1Encryption.py +++ b/jrnl/encryption/Jrnlv1Encryption.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + import hashlib import logging diff --git a/jrnl/encryption/Jrnlv2Encryption.py b/jrnl/encryption/Jrnlv2Encryption.py index c78c5412..97a2ec37 100644 --- a/jrnl/encryption/Jrnlv2Encryption.py +++ b/jrnl/encryption/Jrnlv2Encryption.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + import base64 import logging diff --git a/jrnl/encryption/NoEncryption.py b/jrnl/encryption/NoEncryption.py index 9196389c..66fa4f80 100644 --- a/jrnl/encryption/NoEncryption.py +++ b/jrnl/encryption/NoEncryption.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + import logging from jrnl.encryption.BaseEncryption import BaseEncryption diff --git a/jrnl/encryption/__init__.py b/jrnl/encryption/__init__.py index 56e36c2c..04a016df 100644 --- a/jrnl/encryption/__init__.py +++ b/jrnl/encryption/__init__.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + from enum import Enum from importlib import import_module from typing import TYPE_CHECKING diff --git a/jrnl/exception.py b/jrnl/exception.py index b4ec2be8..2679c855 100644 --- a/jrnl/exception.py +++ b/jrnl/exception.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from typing import TYPE_CHECKING diff --git a/jrnl/install.py b/jrnl/install.py index 5ae14f36..9fab98cc 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import contextlib diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index c79d46e0..cda124cd 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + import logging import sys from typing import TYPE_CHECKING diff --git a/jrnl/keyring.py b/jrnl/keyring.py index 250a0bee..8c093ff9 100644 --- a/jrnl/keyring.py +++ b/jrnl/keyring.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + import keyring from jrnl.messages import Message diff --git a/jrnl/messages/Message.py b/jrnl/messages/Message.py index bb193f5f..92562a59 100644 --- a/jrnl/messages/Message.py +++ b/jrnl/messages/Message.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from typing import TYPE_CHECKING diff --git a/jrnl/messages/MsgStyle.py b/jrnl/messages/MsgStyle.py index 26f383ce..ee898fdb 100644 --- a/jrnl/messages/MsgStyle.py +++ b/jrnl/messages/MsgStyle.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from enum import Enum diff --git a/jrnl/messages/MsgText.py b/jrnl/messages/MsgText.py index 8bcb7695..4a8b6c61 100644 --- a/jrnl/messages/MsgText.py +++ b/jrnl/messages/MsgText.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from enum import Enum diff --git a/jrnl/messages/__init__.py b/jrnl/messages/__init__.py index 6bdd9a11..5f520c10 100644 --- a/jrnl/messages/__init__.py +++ b/jrnl/messages/__init__.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from jrnl.messages import Message diff --git a/jrnl/os_compat.py b/jrnl/os_compat.py index ef6d878a..16a689a0 100644 --- a/jrnl/os_compat.py +++ b/jrnl/os_compat.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import shlex diff --git a/jrnl/output.py b/jrnl/output.py index 0781263c..f11f2382 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import textwrap diff --git a/jrnl/override.py b/jrnl/override.py index 932add85..695bcf4a 100644 --- a/jrnl/override.py +++ b/jrnl/override.py @@ -1,5 +1,6 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html + from typing import TYPE_CHECKING from jrnl.config import make_yaml_valid_dict diff --git a/jrnl/path.py b/jrnl/path.py index 97e04ffe..306e34cc 100644 --- a/jrnl/path.py +++ b/jrnl/path.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import os.path diff --git a/jrnl/plugins/__init__.py b/jrnl/plugins/__init__.py index f67cebaf..993d8686 100644 --- a/jrnl/plugins/__init__.py +++ b/jrnl/plugins/__init__.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from typing import Type diff --git a/jrnl/plugins/dates_exporter.py b/jrnl/plugins/dates_exporter.py index 920c6ff9..1e2ae0dc 100644 --- a/jrnl/plugins/dates_exporter.py +++ b/jrnl/plugins/dates_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from collections import Counter diff --git a/jrnl/plugins/fancy_exporter.py b/jrnl/plugins/fancy_exporter.py index 68b79155..4b800754 100644 --- a/jrnl/plugins/fancy_exporter.py +++ b/jrnl/plugins/fancy_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import logging diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index e93792de..b5bc0490 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import sys diff --git a/jrnl/plugins/json_exporter.py b/jrnl/plugins/json_exporter.py index 1a3a5670..3a7c56ac 100644 --- a/jrnl/plugins/json_exporter.py +++ b/jrnl/plugins/json_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import json diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index 8c079c63..9335847f 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import os diff --git a/jrnl/plugins/tag_exporter.py b/jrnl/plugins/tag_exporter.py index 250f05c7..5702978d 100644 --- a/jrnl/plugins/tag_exporter.py +++ b/jrnl/plugins/tag_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from typing import TYPE_CHECKING diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index 7f2321e7..a839ee88 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import errno diff --git a/jrnl/plugins/util.py b/jrnl/plugins/util.py index 009651e9..86eb5b17 100644 --- a/jrnl/plugins/util.py +++ b/jrnl/plugins/util.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from typing import TYPE_CHECKING diff --git a/jrnl/plugins/xml_exporter.py b/jrnl/plugins/xml_exporter.py index 61b6b297..3bda38e6 100644 --- a/jrnl/plugins/xml_exporter.py +++ b/jrnl/plugins/xml_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from typing import TYPE_CHECKING diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index 5f5d5b21..2927243e 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import os diff --git a/jrnl/prompt.py b/jrnl/prompt.py index f4fa681a..e4071480 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from jrnl.messages import Message diff --git a/jrnl/time.py b/jrnl/time.py index ca1668b3..f91d490c 100644 --- a/jrnl/time.py +++ b/jrnl/time.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import datetime diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 775a84bf..d390844c 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import logging diff --git a/tasks.py b/tasks.py index ea48a174..478f74da 100644 --- a/tasks.py +++ b/tasks.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import json diff --git a/tests/bdd/features/build.feature b/tests/bdd/features/build.feature index d57e489f..19d7a907 100644 --- a/tests/bdd/features/build.feature +++ b/tests/bdd/features/build.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Build process diff --git a/tests/bdd/features/change_time.feature b/tests/bdd/features/change_time.feature index 6bcf8fe7..603dfd44 100644 --- a/tests/bdd/features/change_time.feature +++ b/tests/bdd/features/change_time.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Change entry times in journal diff --git a/tests/bdd/features/config_file.feature b/tests/bdd/features/config_file.feature index 2d2aade0..913e36fd 100644 --- a/tests/bdd/features/config_file.feature +++ b/tests/bdd/features/config_file.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Multiple journals diff --git a/tests/bdd/features/core.feature b/tests/bdd/features/core.feature index 4cd5328b..6fd91f75 100644 --- a/tests/bdd/features/core.feature +++ b/tests/bdd/features/core.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Functionality of jrnl outside of actually handling journals diff --git a/tests/bdd/features/datetime.feature b/tests/bdd/features/datetime.feature index 48166f20..1e7ae722 100644 --- a/tests/bdd/features/datetime.feature +++ b/tests/bdd/features/datetime.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Reading and writing to journal with custom date formats diff --git a/tests/bdd/features/delete.feature b/tests/bdd/features/delete.feature index 6980cc26..f147a8b1 100644 --- a/tests/bdd/features/delete.feature +++ b/tests/bdd/features/delete.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Delete entries from journal diff --git a/tests/bdd/features/encrypt.feature b/tests/bdd/features/encrypt.feature index 8442ca66..a00812eb 100644 --- a/tests/bdd/features/encrypt.feature +++ b/tests/bdd/features/encrypt.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Encrypting and decrypting journals diff --git a/tests/bdd/features/file_storage.feature b/tests/bdd/features/file_storage.feature index 29728cee..b705ddba 100644 --- a/tests/bdd/features/file_storage.feature +++ b/tests/bdd/features/file_storage.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Journals iteracting with the file system in a way that users can see diff --git a/tests/bdd/features/format.feature b/tests/bdd/features/format.feature index 54715495..d03b1ab2 100644 --- a/tests/bdd/features/format.feature +++ b/tests/bdd/features/format.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Custom formats diff --git a/tests/bdd/features/import.feature b/tests/bdd/features/import.feature index 70dda0b3..50f90b09 100644 --- a/tests/bdd/features/import.feature +++ b/tests/bdd/features/import.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Importing data diff --git a/tests/bdd/features/multiple_journals.feature b/tests/bdd/features/multiple_journals.feature index ee90b8a9..fa373452 100644 --- a/tests/bdd/features/multiple_journals.feature +++ b/tests/bdd/features/multiple_journals.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Multiple journals diff --git a/tests/bdd/features/override.feature b/tests/bdd/features/override.feature index edb43b2f..9ddf4a19 100644 --- a/tests/bdd/features/override.feature +++ b/tests/bdd/features/override.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Implementing Runtime Overrides for Select Configuration Keys diff --git a/tests/bdd/features/password.feature b/tests/bdd/features/password.feature index 2aa1b850..e3f4dbd1 100644 --- a/tests/bdd/features/password.feature +++ b/tests/bdd/features/password.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Using the installed keyring diff --git a/tests/bdd/features/search.feature b/tests/bdd/features/search.feature index a38db59a..d653a621 100644 --- a/tests/bdd/features/search.feature +++ b/tests/bdd/features/search.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Searching in a journal diff --git a/tests/bdd/features/star.feature b/tests/bdd/features/star.feature index 0359fca0..d160b9aa 100644 --- a/tests/bdd/features/star.feature +++ b/tests/bdd/features/star.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Starring entries diff --git a/tests/bdd/features/tag.feature b/tests/bdd/features/tag.feature index 4eb9d904..64ceab9f 100644 --- a/tests/bdd/features/tag.feature +++ b/tests/bdd/features/tag.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Tagging diff --git a/tests/bdd/features/upgrade.feature b/tests/bdd/features/upgrade.feature index 3a6b1ee6..b1c056bf 100644 --- a/tests/bdd/features/upgrade.feature +++ b/tests/bdd/features/upgrade.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Upgrading Journals from 1.x.x to 2.x.x diff --git a/tests/bdd/features/write.feature b/tests/bdd/features/write.feature index 1fb6b42a..ecc1965c 100644 --- a/tests/bdd/features/write.feature +++ b/tests/bdd/features/write.feature @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html Feature: Writing new entries. diff --git a/tests/bdd/test_features.py b/tests/bdd/test_features.py index 196f1cd9..9fce25f5 100644 --- a/tests/bdd/test_features.py +++ b/tests/bdd/test_features.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from pytest_bdd import scenarios diff --git a/tests/conftest.py b/tests/conftest.py index d97ef47f..c64820c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from pytest import mark diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index e791e1a0..1dc09378 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import os diff --git a/tests/lib/given_steps.py b/tests/lib/given_steps.py index 8e930a31..808739fc 100644 --- a/tests/lib/given_steps.py +++ b/tests/lib/given_steps.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import json diff --git a/tests/lib/helpers.py b/tests/lib/helpers.py index c5db9ffd..b50edd9f 100644 --- a/tests/lib/helpers.py +++ b/tests/lib/helpers.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import functools diff --git a/tests/lib/then_steps.py b/tests/lib/then_steps.py index 61916262..5502787d 100644 --- a/tests/lib/then_steps.py +++ b/tests/lib/then_steps.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import json diff --git a/tests/lib/when_steps.py b/tests/lib/when_steps.py index e93d5adf..d8ab8e72 100644 --- a/tests/lib/when_steps.py +++ b/tests/lib/when_steps.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import os diff --git a/tests/unit/test_color.py b/tests/unit/test_color.py index 4f23955d..50ed2bdd 100644 --- a/tests/unit/test_color.py +++ b/tests/unit/test_color.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import pytest diff --git a/tests/unit/test_config_file.py b/tests/unit/test_config_file.py index acba3b64..d90dde35 100644 --- a/tests/unit/test_config_file.py +++ b/tests/unit/test_config_file.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import os diff --git a/tests/unit/test_export.py b/tests/unit/test_export.py index 1fd8663e..fb53aa33 100644 --- a/tests/unit/test_export.py +++ b/tests/unit/test_export.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from unittest import mock diff --git a/tests/unit/test_install.py b/tests/unit/test_install.py index f4e19381..1c6c0924 100644 --- a/tests/unit/test_install.py +++ b/tests/unit/test_install.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import sys diff --git a/tests/unit/test_jrnl.py b/tests/unit/test_jrnl.py index 369d6964..9e6d9b25 100644 --- a/tests/unit/test_jrnl.py +++ b/tests/unit/test_jrnl.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import random diff --git a/tests/unit/test_os_compat.py b/tests/unit/test_os_compat.py index a18c1e75..88b678a8 100644 --- a/tests/unit/test_os_compat.py +++ b/tests/unit/test_os_compat.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from unittest import mock diff --git a/tests/unit/test_output.py b/tests/unit/test_output.py index 33f05264..6797ba6c 100644 --- a/tests/unit/test_output.py +++ b/tests/unit/test_output.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from unittest.mock import Mock diff --git a/tests/unit/test_override.py b/tests/unit/test_override.py index 065ee3ee..bbbdd381 100644 --- a/tests/unit/test_override.py +++ b/tests/unit/test_override.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html from argparse import Namespace diff --git a/tests/unit/test_parse_args.py b/tests/unit/test_parse_args.py index a420daa9..ccb7f5a2 100644 --- a/tests/unit/test_parse_args.py +++ b/tests/unit/test_parse_args.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import shlex diff --git a/tests/unit/test_path.py b/tests/unit/test_path.py index 9f6bfce9..8df73b68 100644 --- a/tests/unit/test_path.py +++ b/tests/unit/test_path.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import random diff --git a/tests/unit/test_time.py b/tests/unit/test_time.py index 20290a14..8bc5ac8e 100644 --- a/tests/unit/test_time.py +++ b/tests/unit/test_time.py @@ -1,4 +1,4 @@ -# Copyright © 2012-2022 jrnl contributors +# Copyright © 2012-2023 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html import datetime From 2cec8bdc5b271494dadb6d8248f64b989abea6af Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 7 Jan 2023 21:41:17 +0000 Subject: [PATCH 034/373] Update changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a157b717..e63da6b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ **Build:** +- Update copyright notices for 2023 [\#1660](https://github.com/jrnl-org/jrnl/pull/1660) ([wren](https://github.com/wren)) - Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren)) - Add `simplify` plugin to linting checks [\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren)) - Add type hints [\#1614](https://github.com/jrnl-org/jrnl/pull/1614) ([outa](https://github.com/outa)) From 458faff8a885528250491f30213d64c36e740a5e Mon Sep 17 00:00:00 2001 From: Jonathan Ballet Date: Sat, 7 Jan 2023 22:58:49 +0100 Subject: [PATCH 035/373] Fix markup in "Advanced Usage" doc (#1655) * Fix YAML indentiation in a configuration example * Improve Markdown markup --- docs/advanced.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/advanced.md b/docs/advanced.md index e4f37b42..764ff98a 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -20,8 +20,8 @@ for example: ``` yaml journals: - default: ~\journal.txt - work: ~\work.txt + default: ~/journal.txt + work: ~/work.txt ``` The `default` journal gets created the first time you start `jrnl` @@ -42,11 +42,11 @@ If your `jrnl.yaml` looks like this: ``` yaml encrypt: false journals: -default: ~/journal.txt -work: - journal: ~/work.txt - encrypt: true -food: ~/my_recipes.txt + default: ~/journal.txt + work: + journal: ~/work.txt + encrypt: true + food: ~/my_recipes.txt ``` Your `default` and your `food` journals won't be encrypted, however your @@ -59,7 +59,7 @@ that journal. Consider the following example configuration -```yaml +``` yaml editor: vi -c startinsert journals: default: ~/journal.txt @@ -80,22 +80,22 @@ The `work` journal is encrypted, prints to `json` by default, and is edited usin You can override a configuration field for the current instance of `jrnl` using `--config-override CONFIG_KEY CONFIG_VALUE` where `CONFIG_KEY` is a valid configuration field, specified in dot notation and `CONFIG_VALUE` is the (valid) desired override value. The dot notation can be used to change config keys within other keys, such as `colors.title` for the `title` key within the `colors` key. You can specify multiple overrides as multiple calls to `--config-override`. + !!! note These overrides allow you to modify ***any*** field of your jrnl configuration. We trust that you know what you are doing. #### Examples: ``` sh -#Create an entry using the `stdin` prompt, for rapid logging +# Create an entry using the `stdin` prompt, for rapid logging jrnl --config-override editor "" -#Populate a project's log +# Populate a project's log jrnl --config-override journals.todo "$(git rev-parse --show-toplevel)/todo.txt" todo find my towel -#Pass multiple overrides +# Pass multiple overrides jrnl --config-override display_format fancy --config-override linewrap 20 \ --config-override colors.title green - ``` ### Using an alternate config @@ -105,7 +105,7 @@ You can specify an alternate configuration file for the current instance of `jrn #### Examples: -``` +``` sh # Use personalised configuration file for personal journal entries jrnl --config-file ~/foo/jrnl/personal-config.yaml From a6f2d34b7e994f1e705223ae3bb0f95f2b5aa592 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Jan 2023 13:59:40 -0800 Subject: [PATCH 036/373] Update dependency rich to v13 (#1654) * Update dependency rich to v13 * Update pyproject.toml Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jonathan Wren --- poetry.lock | 2498 ++++++++++++++++++++++++------------------------ pyproject.toml | 2 +- 2 files changed, 1250 insertions(+), 1250 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8c5b6616..14f99f8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "ansiwrap" version = "0.8.4" @@ -5,6 +7,10 @@ description = "textwrap, but savvy to ANSI colors and styles" category = "main" optional = false python-versions = "*" +files = [ + {file = "ansiwrap-0.8.4-py2.py3-none-any.whl", hash = "sha256:7b053567c88e1ad9eed030d3ac41b722125e4c1271c8a99ade797faff1f49fb1"}, + {file = "ansiwrap-0.8.4.zip", hash = "sha256:ca0c740734cde59bf919f8ff2c386f74f9a369818cdc60efe94893d01ea8d9b7"}, +] [package.dependencies] textwrap3 = ">=0.9.2" @@ -16,6 +22,10 @@ description = "Disable App Nap on macOS >= 10.9" category = "dev" optional = false python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] [[package]] name = "astor" @@ -24,6 +34,10 @@ description = "Read/rewrite/write Python ASTs" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +files = [ + {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, + {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, +] [[package]] name = "asttokens" @@ -32,6 +46,10 @@ description = "Annotate AST trees with source code positions" category = "dev" optional = false python-versions = "*" +files = [ + {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, + {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, +] [package.dependencies] six = "*" @@ -46,6 +64,9 @@ description = "Atomic file writes." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] [[package]] name = "attrs" @@ -54,6 +75,10 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -68,6 +93,10 @@ description = "Specifications for callback functions passed in to an API" category = "dev" optional = false python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] [[package]] name = "black" @@ -76,1159 +105,7 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2022.6.15" -description = "Python package for providing Mozilla's CA Bundle." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "cffi" -version = "1.15.1" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "2.1.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" -optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "classify-imports" -version = "4.2.0" -description = "Utilities for refactoring imports in python-like syntax." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.5" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -category = "main" -optional = false -python-versions = "*" - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - -[[package]] -name = "cryptography" -version = "37.0.4" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "distlib" -version = "0.3.5" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "entrypoints" -version = "0.4" -description = "Discover and load entry points from installed packages." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "execnet" -version = "1.9.0" -description = "execnet: rapid multi-Python deployment" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -testing = ["pre-commit"] - -[[package]] -name = "executing" -version = "0.9.1" -description = "Get the currently executing AST node of a frame, and other information" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "filelock" -version = "3.7.1" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] - -[[package]] -name = "flake8" -version = "4.0.1" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" - -[[package]] -name = "flake8-black" -version = "0.3.3" -description = "flake8 plugin to call black as a code style validator" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -black = ">=22.1.0" -flake8 = ">=3.0.0" -tomli = "*" - -[[package]] -name = "flake8-isort" -version = "5.0.0" -description = "flake8 plugin that integrates isort ." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -flake8 = "*" -isort = ">=4.3.5,<6" - -[package.extras] -test = ["pytest"] - -[[package]] -name = "flake8-simplify" -version = "0.19.3" -description = "flake8 plugin which checks for code that can be simplified" -category = "dev" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -astor = ">=0.1" -flake8 = ">=3.7" - -[[package]] -name = "flake8-type-checking" -version = "2.2.0" -description = "A flake8 plugin for managing type-checking imports & forward references" -category = "dev" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -classify-imports = "*" -flake8 = "*" - -[[package]] -name = "flakeheaven" -version = "3.2.0" -description = "FlakeHeaven is a [Flake8](https://gitlab.com/pycqa/flake8) wrapper to make it cool." -category = "dev" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -colorama = "*" -entrypoints = "*" -flake8 = ">=4.0.1,<5.0.0" -pygments = "*" -toml = "*" -urllib3 = "*" - -[package.extras] -docs = ["alabaster", "myst-parser (>=0.18.0,<0.19.0)", "pygments-github-lexers", "sphinx"] - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "glob2" -version = "0.7" -description = "Version of the glob module that can capture patterns and supports recursive wildcards" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "idna" -version = "3.3" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "importlib-metadata" -version = "4.12.0" -description = "Read metadata from Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "ipdb" -version = "0.13.9" -description = "IPython-enabled pdb" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -decorator = {version = "*", markers = "python_version > \"3.6\""} -ipython = {version = ">=7.17.0", markers = "python_version > \"3.6\""} -setuptools = "*" -toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""} - -[[package]] -name = "ipython" -version = "8.4.0" -description = "IPython: Productive Interactive Computing" -category = "dev" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" -pygments = ">=2.4.0" -setuptools = ">=18.5" -stack-data = "*" -traitlets = ">=5" - -[package.extras] -all = ["Sphinx (>=1.3)", "black", "curio", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "testpath", "trio"] -black = ["black"] -doc = ["Sphinx (>=1.3)"] -kernel = ["ipykernel"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] - -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "jedi" -version = "0.18.1" -description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -parso = ">=0.8.0,<0.9.0" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] - -[[package]] -name = "jeepney" -version = "0.8.0" -description = "Low-level, pure Python DBus protocol wrapper." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["async_generator", "trio"] - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "keyring" -version = "23.8.2" -description = "Store and access your passwords safely." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} -pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} -SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} - -[package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[[package]] -name = "Mako" -version = "1.2.2" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "markdown" -version = "3.4.1" -description = "Python implementation of Markdown." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "matplotlib-inline" -version = "0.1.3" -description = "Inline Matplotlib backend for Jupyter" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -traitlets = "*" - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "mkdocs" -version = "1.2.4" -description = "Project documentation with Markdown." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -click = ">=3.3" -ghp-import = ">=1.0" -importlib-metadata = ">=3.10" -Jinja2 = ">=2.10.1" -Markdown = ">=3.2.1" -mergedeep = ">=1.3.4" -packaging = ">=20.5" -PyYAML = ">=3.10" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "parse" -version = "1.19.0" -description = "parse() is the opposite of format()" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "parse-type" -version = "0.6.0" -description = "Simplifies to build parse types based on the parse module" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*" - -[package.dependencies] -parse = {version = ">=1.18.0", markers = "python_version >= \"3.0\""} -six = ">=1.11" - -[package.extras] -develop = ["coverage (>=4.4)", "pytest (<5.0)", "pytest (>=5.0)", "pytest-cov", "pytest-html (>=1.19.0)", "tox (>=2.8)"] -docs = ["sphinx (>=1.2)"] - -[[package]] -name = "parsedatetime" -version = "2.6" -description = "Parse human-readable date/time text." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "parso" -version = "0.8.3" -description = "A Python Parser" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - -[[package]] -name = "pastel" -version = "0.2.1" -description = "Bring colors to your terminal." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "pexpect" -version = "4.8.0" -description = "Pexpect allows easy control of interactive console applications." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "poethepoet" -version = "0.16.0" -description = "A task runner that works well with poetry." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -pastel = ">=0.2.1,<0.3.0" -tomli = ">=1.2.2" - -[package.extras] -poetry-plugin = ["poetry (>=1.0,<2.0)"] - -[[package]] -name = "pprintpp" -version = "0.4.0" -description = "A drop-in replacement for pprint that's actually pretty" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "prompt-toolkit" -version = "3.0.30" -description = "Library for building powerful interactive command lines in Python" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "pure-eval" -version = "0.2.2" -description = "Safely evaluate AST nodes without side effects" -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycodestyle" -version = "2.8.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyflakes" -version = "2.4.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pygments" -version = "2.12.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "7.1.2" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-bdd" -version = "5.0.0" -description = "BDD for pytest" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -glob2 = "*" -Mako = "*" -parse = "*" -parse-type = "*" -py = "*" -pytest = ">=4.3" - -[[package]] -name = "pytest-clarity" -version = "1.0.1" -description = "A plugin providing an alternative, colourful diff output for failing assertions." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -pprintpp = ">=0.4.0" -pytest = ">=3.5.0" -rich = ">=8.0.0" - -[[package]] -name = "pytest-forked" -version = "1.4.0" -description = "run tests in isolated forked subprocesses" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -py = "*" -pytest = ">=3.10" - -[[package]] -name = "pytest-xdist" -version = "2.5.0" -description = "pytest xdist plugin for distributed testing and loop-on-failing modes" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" -pytest-forked = "*" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz-deprecation-shim" -version = "0.1.0.post0" -description = "Shims to make deprecation of pytz easier" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" - -[package.dependencies] -tzdata = {version = "*", markers = "python_version >= \"3.6\""} - -[[package]] -name = "pywin32-ctypes" -version = "0.2.0" -description = "" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyxdg" -version = "0.28" -description = "PyXDG contains implementations of freedesktop.org standards in python." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyyaml" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "12.5.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.6.3,<4.0.0" - -[package.dependencies] -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] - -[[package]] -name = "ruamel.yaml" -version = "0.17.21" -description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -category = "main" -optional = false -python-versions = ">=3" - -[package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} - -[package.extras] -docs = ["ryd"] -jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] - -[[package]] -name = "ruamel.yaml.clib" -version = "0.2.6" -description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "secretstorage" -version = "3.3.2" -description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cryptography = ">=2.0" -jeepney = ">=0.6" - -[[package]] -name = "setuptools" -version = "65.3.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "stack-data" -version = "0.3.0" -description = "Extract data from python stack frames and tracebacks for informative displays" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -asttokens = "*" -executing = "*" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "textwrap3" -version = "0.9.2" -description = "textwrap from Python 3.6 backport (plus a few tweaks)" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tox" -version = "3.25.1" -description = "tox is a generic virtualenv management and test command line tool" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -filelock = ">=3.0.0" -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -toml = ">=0.9.4" -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] - -[[package]] -name = "traitlets" -version = "5.3.0" -description = "" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -test = ["pre-commit", "pytest"] - -[[package]] -name = "tzdata" -version = "2022.1" -description = "Provider of IANA time zone data" -category = "main" -optional = false -python-versions = ">=2" - -[[package]] -name = "tzlocal" -version = "4.2" -description = "tzinfo object for the local timezone" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pytz-deprecation-shim = "*" -tzdata = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] -test = ["pytest (>=4.3)", "pytest-mock (>=3.3)"] - -[[package]] -name = "urllib3" -version = "1.26.11" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.16.2" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -platformdirs = ">=2,<3" - -[package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] - -[[package]] -name = "watchdog" -version = "2.1.9" -description = "Filesystem events monitoring" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "wcwidth" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "xmltodict" -version = "0.13.0" -description = "Makes working with XML feel like you are working with JSON" -category = "dev" -optional = false -python-versions = ">=3.4" - -[[package]] -name = "zipp" -version = "3.8.1" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = ">=3.10.0, <3.13" -content-hash = "d386601320306164cb6332391f68aca47c114da7cad2dfa273d1fcb70824db16" - -[metadata.files] -ansiwrap = [ - {file = "ansiwrap-0.8.4-py2.py3-none-any.whl", hash = "sha256:7b053567c88e1ad9eed030d3ac41b722125e4c1271c8a99ade797faff1f49fb1"}, - {file = "ansiwrap-0.8.4.zip", hash = "sha256:ca0c740734cde59bf919f8ff2c386f74f9a369818cdc60efe94893d01ea8d9b7"}, -] -appnope = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] -astor = [ - {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, - {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, -] -asttokens = [ - {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, - {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -backcall = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] -black = [ +files = [ {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"}, @@ -1253,11 +130,40 @@ black = [ {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"}, {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"}, ] -certifi = [ + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2022.6.15" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, ] -cffi = [ + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, @@ -1323,27 +229,87 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] -charset-normalizer = [ + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, ] -classify-imports = [ + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "classify-imports" +version = "4.2.0" +description = "Utilities for refactoring imports in python-like syntax." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "classify_imports-4.2.0-py2.py3-none-any.whl", hash = "sha256:dbbc264b70a470ed8c6c95976a11dfb8b7f63df44ed1af87328bbed2663f5161"}, {file = "classify_imports-4.2.0.tar.gz", hash = "sha256:7abfb7ea92149b29d046bd34573d247ba6e68cc28100c801eba4af17964fc40e"}, ] -click = [ + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] -colorama = [ + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] -commonmark = [ + +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] -cryptography = [ + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + +[[package]] +name = "cryptography" +version = "37.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, @@ -1367,109 +333,470 @@ cryptography = [ {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, ] -decorator = [ + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] -distlib = [ + +[[package]] +name = "distlib" +version = "0.3.5" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "distlib-0.3.5-py2.py3-none-any.whl", hash = "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c"}, {file = "distlib-0.3.5.tar.gz", hash = "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe"}, ] -entrypoints = [ + +[[package]] +name = "entrypoints" +version = "0.4" +description = "Discover and load entry points from installed packages." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, ] -execnet = [ + +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, ] -executing = [ + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "executing" +version = "0.9.1" +description = "Get the currently executing AST node of a frame, and other information" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "executing-0.9.1-py2.py3-none-any.whl", hash = "sha256:4ce4d6082d99361c0231fc31ac1a0f56979363cc6819de0b1410784f99e49105"}, {file = "executing-0.9.1.tar.gz", hash = "sha256:ea278e2cf90cbbacd24f1080dd1f0ac25b71b2e21f50ab439b7ba45dd3195587"}, ] -filelock = [ + +[[package]] +name = "filelock" +version = "3.7.1" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, ] -flake8 = [ + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] -flake8-black = [ + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "flake8-black" +version = "0.3.3" +description = "flake8 plugin to call black as a code style validator" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "flake8-black-0.3.3.tar.gz", hash = "sha256:8211f5e20e954cb57c709acccf2f3281ce27016d4c4b989c3e51f878bb7ce12a"}, {file = "flake8_black-0.3.3-py3-none-any.whl", hash = "sha256:7d667d0059fd1aa468de1669d77cc934b7f1feeac258d57bdae69a8e73c4cd90"}, ] -flake8-isort = [ + +[package.dependencies] +black = ">=22.1.0" +flake8 = ">=3.0.0" +tomli = "*" + +[[package]] +name = "flake8-isort" +version = "5.0.0" +description = "flake8 plugin that integrates isort ." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "flake8-isort-5.0.0.tar.gz", hash = "sha256:e336f928c7edc509684930ab124414194b7f4e237c712af8fcbdf49d8747b10c"}, {file = "flake8_isort-5.0.0-py3-none-any.whl", hash = "sha256:c73f9cbd1bf209887f602a27b827164ccfeba1676801b2aa23cb49051a1be79c"}, ] -flake8-simplify = [ + +[package.dependencies] +flake8 = "*" +isort = ">=4.3.5,<6" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "flake8-simplify" +version = "0.19.3" +description = "flake8 plugin which checks for code that can be simplified" +category = "dev" +optional = false +python-versions = ">=3.6.1" +files = [ {file = "flake8_simplify-0.19.3-py3-none-any.whl", hash = "sha256:1057320e9312d75849541fee822900d27bcad05b2405edc84713affee635629e"}, {file = "flake8_simplify-0.19.3.tar.gz", hash = "sha256:2fb083bf5142a98d9c9554755cf2f56f8926eb4a33eae30c0809041b1546879e"}, ] -flake8-type-checking = [ + +[package.dependencies] +astor = ">=0.1" +flake8 = ">=3.7" + +[[package]] +name = "flake8-type-checking" +version = "2.2.0" +description = "A flake8 plugin for managing type-checking imports & forward references" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ {file = "flake8_type_checking-2.2.0-py3-none-any.whl", hash = "sha256:c7d9d7adc6cd635a5a1a7859e5e0140f4f8f1705982a22db45872dd9acd49753"}, {file = "flake8_type_checking-2.2.0.tar.gz", hash = "sha256:f7972fc9102f3f632ace1f4b1c5c20b900b8b7b529f04bb6c1fe0a11801e9658"}, ] -flakeheaven = [ + +[package.dependencies] +classify-imports = "*" +flake8 = "*" + +[[package]] +name = "flakeheaven" +version = "3.2.0" +description = "FlakeHeaven is a [Flake8](https://gitlab.com/pycqa/flake8) wrapper to make it cool." +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" +files = [ {file = "flakeheaven-3.2.0-py3-none-any.whl", hash = "sha256:ec5a508c3db64d73128b65cb2a5a2c0a2d9f2e4b435e9fa2bcc03bf0df86da79"}, {file = "flakeheaven-3.2.0.tar.gz", hash = "sha256:225333d7bf309079f19a2c5f02d427fc7558a0d0c065944de88041ca94f5525c"}, ] -ghp-import = [ + +[package.dependencies] +colorama = "*" +entrypoints = "*" +flake8 = ">=4.0.1,<5.0.0" +pygments = "*" +toml = "*" +urllib3 = "*" + +[package.extras] +docs = ["alabaster", "myst-parser (>=0.18.0,<0.19.0)", "pygments-github-lexers", "sphinx"] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, ] -glob2 = [ + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "glob2" +version = "0.7" +description = "Version of the glob module that can capture patterns and supports recursive wildcards" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "glob2-0.7.tar.gz", hash = "sha256:85c3dbd07c8aa26d63d7aacee34fa86e9a91a3873bc30bf62ec46e531f92ab8c"}, ] -idna = [ + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] -importlib-metadata = [ + +[[package]] +name = "importlib-metadata" +version = "4.12.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] -iniconfig = [ + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -ipdb = [ + +[[package]] +name = "ipdb" +version = "0.13.9" +description = "IPython-enabled pdb" +category = "dev" +optional = false +python-versions = ">=2.7" +files = [ {file = "ipdb-0.13.9.tar.gz", hash = "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"}, ] -ipython = [ + +[package.dependencies] +decorator = {version = "*", markers = "python_version > \"3.6\""} +ipython = {version = ">=7.17.0", markers = "python_version > \"3.6\""} +setuptools = "*" +toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""} + +[[package]] +name = "ipython" +version = "8.4.0" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ {file = "ipython-8.4.0-py3-none-any.whl", hash = "sha256:7ca74052a38fa25fe9bedf52da0be7d3fdd2fb027c3b778ea78dfe8c212937d1"}, {file = "ipython-8.4.0.tar.gz", hash = "sha256:f2db3a10254241d9b447232cec8b424847f338d9d36f9a577a6192c332a46abd"}, ] -isort = [ + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = ">=2.4.0" +setuptools = ">=18.5" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["Sphinx (>=1.3)", "black", "curio", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "testpath", "trio"] +black = ["black"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" +files = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] -jedi = [ + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jedi" +version = "0.18.1" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, ] -jeepney = [ + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, ] -jinja2 = [ + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] -keyring = [ + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "keyring" +version = "23.8.2" +description = "Store and access your passwords safely." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "keyring-23.8.2-py3-none-any.whl", hash = "sha256:10d2a8639663fe2090705a00b8c47c687cacdf97598ea9c11456679fa974473a"}, {file = "keyring-23.8.2.tar.gz", hash = "sha256:0d9973f8891850f1ade5f26aafd06bb16865fbbae3fc56b0defb6a14a2624003"}, ] -Mako = [ + +[package.dependencies] +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "Mako" +version = "1.2.2" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "Mako-1.2.2-py3-none-any.whl", hash = "sha256:8efcb8004681b5f71d09c983ad5a9e6f5c40601a6ec469148753292abc0da534"}, {file = "Mako-1.2.2.tar.gz", hash = "sha256:3724869b363ba630a272a5f89f68c070352137b8fd1757650017b7e06fda163f"}, ] -markdown = [ + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markdown" +version = "3.4.1" +description = "Python implementation of Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"}, {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"}, ] -markupsafe = [ + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, @@ -1511,149 +838,551 @@ markupsafe = [ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] -matplotlib-inline = [ + +[[package]] +name = "matplotlib-inline" +version = "0.1.3" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, ] -mccabe = [ + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] -mergedeep = [ + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, ] -mkdocs = [ + +[[package]] +name = "mkdocs" +version = "1.2.4" +description = "Project documentation with Markdown." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "mkdocs-1.2.4-py3-none-any.whl", hash = "sha256:f108e7ab5a7ed3e30826dbf82f37638f0d90d11161644616cc4f01a1e2ab3940"}, {file = "mkdocs-1.2.4.tar.gz", hash = "sha256:8e7970a26183487fe2a1041940c6fd03aa0dbe5549e50c3e7194f565cb3c678a"}, ] -mypy-extensions = [ + +[package.dependencies] +click = ">=3.3" +ghp-import = ">=1.0" +importlib-metadata = ">=3.10" +Jinja2 = ">=2.10.1" +Markdown = ">=3.2.1" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +PyYAML = ">=3.10" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -packaging = [ + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -parse = [ + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "parse" +version = "1.19.0" +description = "parse() is the opposite of format()" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "parse-1.19.0.tar.gz", hash = "sha256:9ff82852bcb65d139813e2a5197627a94966245c897796760a3a2a8eb66f020b"}, ] -parse-type = [ + +[[package]] +name = "parse-type" +version = "0.6.0" +description = "Simplifies to build parse types based on the parse module" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*" +files = [ {file = "parse_type-0.6.0-py2.py3-none-any.whl", hash = "sha256:c148e88436bd54dab16484108e882be3367f44952c649c9cd6b82a7370b650cb"}, {file = "parse_type-0.6.0.tar.gz", hash = "sha256:20b43c660e48ed47f433bce5873a2a3d4b9b6a7ba47bd7f7d2a7cec4bec5551f"}, ] -parsedatetime = [ + +[package.dependencies] +parse = {version = ">=1.18.0", markers = "python_version >= \"3.0\""} +six = ">=1.11" + +[package.extras] +develop = ["coverage (>=4.4)", "pytest (<5.0)", "pytest (>=5.0)", "pytest-cov", "pytest-html (>=1.19.0)", "tox (>=2.8)"] +docs = ["sphinx (>=1.2)"] + +[[package]] +name = "parsedatetime" +version = "2.6" +description = "Parse human-readable date/time text." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b"}, {file = "parsedatetime-2.6.tar.gz", hash = "sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455"}, ] -parso = [ + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] -pastel = [ + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, ] -pathspec = [ + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] -pexpect = [ + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, ] -pickleshare = [ + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] -platformdirs = [ + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] -pluggy = [ + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -poethepoet = [ + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poethepoet" +version = "0.16.0" +description = "A task runner that works well with poetry." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "poethepoet-0.16.0-py3-none-any.whl", hash = "sha256:87482ea8bba4e5db4abbd8e6360baee73b2ce0f3d5f5e99e81cdfa39d72d118f"}, {file = "poethepoet-0.16.0.tar.gz", hash = "sha256:6455aec39f198be92dbf210a4416e1635119e967204c092b431c8b10024db1d1"}, ] -pprintpp = [ + +[package.dependencies] +pastel = ">=0.2.1,<0.3.0" +tomli = ">=1.2.2" + +[package.extras] +poetry-plugin = ["poetry (>=1.0,<2.0)"] + +[[package]] +name = "pprintpp" +version = "0.4.0" +description = "A drop-in replacement for pprint that's actually pretty" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"}, {file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"}, ] -prompt-toolkit = [ + +[[package]] +name = "prompt-toolkit" +version = "3.0.30" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.6.2" +files = [ {file = "prompt_toolkit-3.0.30-py3-none-any.whl", hash = "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"}, {file = "prompt_toolkit-3.0.30.tar.gz", hash = "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0"}, ] -ptyprocess = [ + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] -pure-eval = [ + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, ] -py = [ + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -pycodestyle = [ + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] -pycparser = [ + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pyflakes = [ + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] -pygments = [ + +[[package]] +name = "pygments" +version = "2.12.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] -pyparsing = [ + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" +files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] -pytest = [ + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] -pytest-bdd = [ + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-bdd" +version = "5.0.0" +description = "BDD for pytest" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pytest-bdd-5.0.0.tar.gz", hash = "sha256:fab7093ed3d5e51ee0c68de093c90e4f40de345bd9a54a188b2991ce2a2a39cf"}, {file = "pytest_bdd-5.0.0-py3-none-any.whl", hash = "sha256:c7cf12209606421f61f36b5dc63beccd0c82d29446c0592cf68af2dad0a9761d"}, ] -pytest-clarity = [ + +[package.dependencies] +glob2 = "*" +Mako = "*" +parse = "*" +parse-type = "*" +py = "*" +pytest = ">=4.3" + +[[package]] +name = "pytest-clarity" +version = "1.0.1" +description = "A plugin providing an alternative, colourful diff output for failing assertions." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pytest-clarity-1.0.1.tar.gz", hash = "sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772"}, ] -pytest-forked = [ + +[package.dependencies] +pprintpp = ">=0.4.0" +pytest = ">=3.5.0" +rich = ">=8.0.0" + +[[package]] +name = "pytest-forked" +version = "1.4.0" +description = "run tests in isolated forked subprocesses" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, ] -pytest-xdist = [ + +[package.dependencies] +py = "*" +pytest = ">=3.10" + +[[package]] +name = "pytest-xdist" +version = "2.5.0" +description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, ] -python-dateutil = [ + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" +pytest-forked = "*" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -pytz-deprecation-shim = [ + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz-deprecation-shim" +version = "0.1.0.post0" +description = "Shims to make deprecation of pytz easier" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] -pywin32-ctypes = [ + +[package.dependencies] +tzdata = {version = "*", markers = "python_version >= \"3.6\""} + +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] -pyxdg = [ + +[[package]] +name = "pyxdg" +version = "0.28" +description = "PyXDG contains implementations of freedesktop.org standards in python." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"}, {file = "pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4"}, ] -pyyaml = [ + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, @@ -1695,23 +1424,90 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] -pyyaml-env-tag = [ + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] -requests = [ + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" +files = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] -rich = [ - {file = "rich-12.5.1-py3-none-any.whl", hash = "sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb"}, - {file = "rich-12.5.1.tar.gz", hash = "sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca"}, + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.0.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.0.0-py3-none-any.whl", hash = "sha256:12b1d77ee7edf251b741531323f0d990f5f570a4e7c054d0bfb59fb7981ad977"}, + {file = "rich-13.0.0.tar.gz", hash = "sha256:3aa9eba7219b8c575c6494446a59f702552efe1aa261e7eeb95548fa586e1950"}, ] -"ruamel.yaml" = [ + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + +[[package]] +name = "ruamel.yaml" +version = "0.17.21" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +category = "main" +optional = false +python-versions = ">=3" +files = [ {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, ] -"ruamel.yaml.clib" = [ + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel.yaml.clib" +version = "0.2.6" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:066f886bc90cc2ce44df8b5f7acfc6a7e2b2e672713f027136464492b0c34d7c"}, {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, @@ -1743,59 +1539,227 @@ rich = [ {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, ] -secretstorage = [ + +[[package]] +name = "secretstorage" +version = "3.3.2" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, ] -setuptools = [ + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "setuptools" +version = "65.3.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "setuptools-65.3.0-py3-none-any.whl", hash = "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82"}, {file = "setuptools-65.3.0.tar.gz", hash = "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"}, ] -six = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -stack-data = [ + +[[package]] +name = "stack-data" +version = "0.3.0" +description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "stack_data-0.3.0-py3-none-any.whl", hash = "sha256:aa1d52d14d09c7a9a12bb740e6bdfffe0f5e8f4f9218d85e7c73a8c37f7ae38d"}, {file = "stack_data-0.3.0.tar.gz", hash = "sha256:77bec1402dcd0987e9022326473fdbcc767304892a533ed8c29888dacb7dddbc"}, ] -textwrap3 = [ + +[package.dependencies] +asttokens = "*" +executing = "*" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "textwrap3" +version = "0.9.2" +description = "textwrap from Python 3.6 backport (plus a few tweaks)" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "textwrap3-0.9.2-py2.py3-none-any.whl", hash = "sha256:bf5f4c40faf2a9ff00a9e0791fed5da7415481054cef45bb4a3cfb1f69044ae0"}, {file = "textwrap3-0.9.2.zip", hash = "sha256:5008eeebdb236f6303dcd68f18b856d355f6197511d952ba74bc75e40e0c3414"}, ] -toml = [ + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tomli = [ + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -tox = [ + +[[package]] +name = "tox" +version = "3.25.1" +description = "tox is a generic virtualenv management and test command line tool" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ {file = "tox-3.25.1-py2.py3-none-any.whl", hash = "sha256:c38e15f4733683a9cc0129fba078633e07eb0961f550a010ada879e95fb32632"}, {file = "tox-3.25.1.tar.gz", hash = "sha256:c138327815f53bc6da4fe56baec5f25f00622ae69ef3fe4e1e385720e22486f9"}, ] -traitlets = [ + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +toml = ">=0.9.4" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] + +[[package]] +name = "traitlets" +version = "5.3.0" +description = "" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "traitlets-5.3.0-py3-none-any.whl", hash = "sha256:65fa18961659635933100db8ca120ef6220555286949774b9cfc106f941d1c7a"}, {file = "traitlets-5.3.0.tar.gz", hash = "sha256:0bb9f1f9f017aa8ec187d8b1b2a7a6626a2a1d877116baba52a129bfa124f8e2"}, ] -tzdata = [ + +[package.extras] +test = ["pre-commit", "pytest"] + +[[package]] +name = "tzdata" +version = "2022.1" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" +files = [ {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, ] -tzlocal = [ + +[[package]] +name = "tzlocal" +version = "4.2" +description = "tzinfo object for the local timezone" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, ] -urllib3 = [ + +[package.dependencies] +pytz-deprecation-shim = "*" +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] +test = ["pytest (>=4.3)", "pytest-mock (>=3.3)"] + +[[package]] +name = "urllib3" +version = "1.26.11" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +files = [ {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, ] -virtualenv = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.16.2" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, ] -watchdog = [ + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +platformdirs = ">=2,<3" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] + +[[package]] +name = "watchdog" +version = "2.1.9" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"}, @@ -1822,15 +1786,51 @@ watchdog = [ {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"}, {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"}, ] -wcwidth = [ + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] -xmltodict = [ + +[[package]] +name = "xmltodict" +version = "0.13.0" +description = "Makes working with XML feel like you are working with JSON" +category = "dev" +optional = false +python-versions = ">=3.4" +files = [ {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, ] -zipp = [ + +[[package]] +name = "zipp" +version = "3.8.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, ] + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10.0, <3.13" +content-hash = "6b1481f2e3dddb80ce48ed4d7eee5c10bee04a0997ca36c4eefc0918afb1eb94" diff --git a/pyproject.toml b/pyproject.toml index ecd5f015..26dc4316 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ parsedatetime = ">=2.6" python-dateutil = "^2.8" # https://github.com/dateutil/dateutil/blob/master/RELEASING pyxdg = ">=0.27.0" "ruamel.yaml" = "^0.17.21" -rich = "^12.2.0" +rich = ">=12.2.0, <14.0.0" # dayone-only deps tzlocal = ">=4.0" # https://github.com/regebro/tzlocal/blob/master/CHANGES.txt From 120c54a29e5701a80507cf6ecad760f8013cf4e3 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 7 Jan 2023 22:01:41 +0000 Subject: [PATCH 037/373] Update changelog [ci skip] --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e63da6b6..2ddd1347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,8 +30,13 @@ **Documentation:** - Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651) +- Fix markup in "Advanced Usage" doc [\#1655](https://github.com/jrnl-org/jrnl/pull/1655) ([multani](https://github.com/multani)) - Remove Windows 7 known issue since Windows 7 is no longer supported [\#1636](https://github.com/jrnl-org/jrnl/pull/1636) ([micahellison](https://github.com/micahellison)) +**Packaging:** + +- Update dependency rich to v13 [\#1654](https://github.com/jrnl-org/jrnl/pull/1654) ([renovate[bot]](https://github.com/apps/renovate)) + ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...v3.3) From 9547411390b36a7f2b66bef2acf6e5b8faf3742f Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Sat, 7 Jan 2023 14:04:33 -0800 Subject: [PATCH 038/373] Update docs to include time and title in arguments with `--edit` (#1657) --- docs/external-editors.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/external-editors.md b/docs/external-editors.md index f679d19e..977a9de2 100644 --- a/docs/external-editors.md +++ b/docs/external-editors.md @@ -6,10 +6,31 @@ License: https://www.gnu.org/licenses/gpl-3.0.html # External editors Configure your preferred external editor by updating the `editor` option -in your [configuration file](./reference-config-file.md#editor) +in your [configuration file](./reference-config-file.md#editor). If your editor is not +in your operating system's `PATH` environment variable, then you will have to +enter the full path of your editor. -If your editor is not in your operating system's `PATH` environment variable, -then you will have to enter in the full path of your editor. +Once it's configured, you can create an entry as a new document in your editor using the `jrnl` +command by itself: + +``` text +jrnl +``` + +You can specify the time and title of the entry as usual on the first line of the document. + +If you want, you can skip the editor by including a quick entry with the `jrnl` command: + +``` text +jrnl yesterday: All my troubles seemed so far away. +``` + +If you want to start the entry on the command line and continue writing in your chosen editor, +use the `--edit` flag. For example: + +``` text +jrnl yesterday: All my troubles seemed so far away. --edit +``` !!! note To save and log any entry edits, save and close the file. @@ -99,4 +120,4 @@ When you're done editing the message, save and `C-x #` to close the buffer and s ## Other editors -If you're using another editor and would like to share, feel free to [contribute documentation](./contributing.md#editing-documentation) on it. \ No newline at end of file +If you're using another editor and would like to share, feel free to [contribute documentation](./contributing.md#editing-documentation) on it. From a13726d4c52b1d8a090bc4bb9ce654bf722d1844 Mon Sep 17 00:00:00 2001 From: outa Date: Sat, 14 Jan 2023 22:22:31 +0100 Subject: [PATCH 039/373] Add more type hints (#1642) --- jrnl/Entry.py | 14 ++++---- jrnl/Journal.py | 64 ++++++++++++++++++------------------- jrnl/commands.py | 4 +-- jrnl/encryption/__init__.py | 3 +- jrnl/keyring.py | 2 +- 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/jrnl/Entry.py b/jrnl/Entry.py index 78575027..810cf86f 100644 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -48,33 +48,33 @@ class Entry: self._tags = list(self._parse_tags()) @property - def title(self): + def title(self) -> str: if self._title is None: self._parse_text() return self._title @title.setter - def title(self, x): + def title(self, x: str): self._title = x @property - def body(self): + def body(self) -> str: if self._body is None: self._parse_text() return self._body @body.setter - def body(self, x): + def body(self, x: str): self._body = x @property - def tags(self): + def tags(self) -> list[str]: if self._tags is None: self._parse_text() return self._tags @tags.setter - def tags(self, x): + def tags(self, x: list[str]): self._tags = x @staticmethod @@ -218,7 +218,7 @@ class Entry: return False return True - def __ne__(self, other): + def __ne__(self, other: "Entry"): return not self.__eq__(other) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 51c0b746..1b444fc5 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -59,7 +59,7 @@ class Journal: return (entry for entry in self.entries) @classmethod - def from_journal(cls, other): + def from_journal(cls, other: "Journal") -> "Journal": """Creates a new journal by copying configuration and entries from another journal object""" new_journal = cls(other.name, **other.config) @@ -72,7 +72,7 @@ class Journal: ) return new_journal - def import_(self, other_journal_txt): + def import_(self, other_journal_txt: str) -> None: imported_entries = self._parse(other_journal_txt) for entry in imported_entries: entry.modified = True @@ -96,7 +96,7 @@ class Journal: return self.encryption_method.encrypt(text) - def open(self, filename=None): + def open(self, filename: str | None = None) -> "Journal": """Opens the journal file defined in the config and parses it into a list of Entries. Entries have the form (date, title, body).""" filename = filename or self.config["journal"] @@ -130,35 +130,35 @@ class Journal: logging.debug("opened %s with %d entries", self.__class__.__name__, len(self)) return self - def write(self, filename=None): + def write(self, filename: str | None = None) -> None: """Dumps the journal into the config file, overwriting it""" filename = filename or self.config["journal"] text = self._to_text() text = self._encrypt(text) self._store(filename, text) - def validate_parsing(self): + def validate_parsing(self) -> bool: """Confirms that the jrnl is still parsed correctly after being dumped to text.""" new_entries = self._parse(self._to_text()) return all(entry == new_entries[i] for i, entry in enumerate(self.entries)) @staticmethod - def create_file(filename): + def create_file(filename: str) -> None: with open(filename, "w"): pass - def _to_text(self): + def _to_text(self) -> str: return "\n".join([str(e) for e in self.entries]) - def _load(self, filename): + def _load(self, filename: str) -> bytes: with open(filename, "rb") as f: return f.read() - def _store(self, filename, text): + def _store(self, filename: str, text: bytes) -> None: with open(filename, "wb") as f: f.write(text) - def _parse(self, journal_txt): + def _parse(self, journal_txt: str) -> list[Entry]: """Parses a journal that's stored in a string and returns a list of entries""" # Return empty array if the journal is blank @@ -197,7 +197,7 @@ class Journal: entry._parse_text() return entries - def pprint(self, short=False): + def pprint(self, short: bool = False) -> str: """Prettyprints the journal's entries""" return "\n".join([e.pprint(short=short) for e in self.entries]) @@ -207,17 +207,17 @@ class Journal: def __repr__(self): return f"" - def sort(self): + def sort(self) -> None: """Sorts the Journal's entries by date""" self.entries = sorted(self.entries, key=lambda entry: entry.date) - def limit(self, n=None): + def limit(self, n: int | None = None) -> None: """Removes all but the last n entries""" if n: self.entries = self.entries[-n:] @property - def tags(self): + def tags(self) -> list[Tag]: """Returns a set of tuples (count, tag) for all tags present in the journal.""" # Astute reader: should the following line leave you as puzzled as me the first time # I came across this construction, worry not and embrace the ensuing moment of enlightment. @@ -228,16 +228,16 @@ class Journal: def filter( self, - tags=[], - month=None, - day=None, - year=None, - start_date=None, - end_date=None, - starred=False, - strict=False, - contains=None, - exclude=[], + tags: list = [], + month: str | int | None = None, + day: str | int | None = None, + year: str | None = None, + start_date: str | None = None, + end_date: str | None = None, + starred: bool = False, + strict: bool = False, + contains: bool = None, + exclude: list = [], ): """Removes all entries from the journal that don't match the filter. @@ -293,19 +293,19 @@ class Journal: self.entries = result - def delete_entries(self, entries_to_delete): + def delete_entries(self, entries_to_delete: list[Entry]) -> None: """Deletes specific entries from a journal.""" for entry in entries_to_delete: self.entries.remove(entry) - def change_date_entries(self, date): + def change_date_entries(self, date: datetime.datetime | None) -> None: """Changes entry dates to given date.""" date = time.parse(date) for entry in self.entries: entry.date = date - def prompt_action_entries(self, msg: MsgText): + def prompt_action_entries(self, msg: MsgText) -> list[Entry]: """Prompts for action for each entry in a journal, using given message. Returns the entries the user wishes to apply the action on.""" to_act = [] @@ -325,7 +325,7 @@ class Journal: return to_act - def new_entry(self, raw, date=None, sort=True): + def new_entry(self, raw: str, date=None, sort: bool = True) -> Entry: """Constructs a new entry from some raw text input. If a date is given, it will parse and use this, otherwise scan for a date in the input first.""" @@ -361,12 +361,12 @@ class Journal: self.sort() return entry - def editable_str(self): + def editable_str(self) -> str: """Turns the journal into a string of entries that can be edited manually and later be parsed with self.parse_editable_str.""" return "\n".join([str(e) for e in self.entries]) - def parse_editable_str(self, edited): + def parse_editable_str(self, edited: str) -> None: """Parses the output of self.editable_str and updates it's entries.""" mod_entries = self._parse(edited) # Match those entries that can be found in self.entries and set @@ -382,7 +382,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 _parse(self, journal_txt): + def _parse(self, journal_txt: str) -> list[Entry]: """Parses a journal that's stored in a string and returns a list of entries""" # Entries start with a line that looks like 'date title' - let's figure out how # long the date will be by constructing one @@ -429,7 +429,7 @@ class LegacyJournal(Journal): return entries -def open_journal(journal_name, config, legacy=False): +def open_journal(journal_name: str, config: dict, legacy: bool = False) -> Journal: """ Creates a normal, encrypted or DayOne journal based on the passed config. If legacy is True, it will open Journals with legacy classes build for diff --git a/jrnl/commands.py b/jrnl/commands.py index a85e97e9..9b6783ed 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -28,7 +28,7 @@ from jrnl.messages import MsgText from jrnl.output import print_msg -def preconfig_diagnostic(_): +def preconfig_diagnostic(_) -> None: from jrnl import __title__ from jrnl import __version__ @@ -39,7 +39,7 @@ def preconfig_diagnostic(_): ) -def preconfig_version(_): +def preconfig_version(_) -> None: import textwrap from jrnl import __title__ diff --git a/jrnl/encryption/__init__.py b/jrnl/encryption/__init__.py index 04a016df..afa92edb 100644 --- a/jrnl/encryption/__init__.py +++ b/jrnl/encryption/__init__.py @@ -4,6 +4,7 @@ from enum import Enum from importlib import import_module from typing import TYPE_CHECKING +from typing import Type if TYPE_CHECKING: from .BaseEncryption import BaseEncryption @@ -18,7 +19,7 @@ class EncryptionMethods(str, Enum): JRNLV2 = "Jrnlv2Encryption" -def determine_encryption_method(config: str | bool) -> "BaseEncryption": +def determine_encryption_method(config: str | bool) -> Type["BaseEncryption"]: ENCRYPTION_METHODS = { True: EncryptionMethods.JRNLV2, # the default False: EncryptionMethods.NONE, diff --git a/jrnl/keyring.py b/jrnl/keyring.py index 8c093ff9..42396a7a 100644 --- a/jrnl/keyring.py +++ b/jrnl/keyring.py @@ -18,7 +18,7 @@ def get_keyring_password(journal_name: str = "default") -> str | None: return None -def set_keyring_password(password: str, journal_name: str = "default"): +def set_keyring_password(password: str, journal_name: str = "default") -> None: try: return keyring.set_password("jrnl", journal_name, password) except keyring.errors.KeyringError as e: From 7be67accc18f1f7cab1db1ca9fa73a7269e08edf Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 14 Jan 2023 21:24:31 +0000 Subject: [PATCH 040/373] Update changelog [ci skip] --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ddd1347..e7767543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ **Implemented enhancements:** - Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521) +- Add more type hints [\#1642](https://github.com/jrnl-org/jrnl/pull/1642) ([outa](https://github.com/outa)) - Add `rich` handler to debug logging [\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren)) - Rework Encryption to enable future support of other encryption methods [\#1602](https://github.com/jrnl-org/jrnl/pull/1602) ([wren](https://github.com/wren)) @@ -30,6 +31,7 @@ **Documentation:** - Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651) +- Update docs to include time and title in arguments with `--edit` [\#1657](https://github.com/jrnl-org/jrnl/pull/1657) ([pconrad-fb](https://github.com/pconrad-fb)) - Fix markup in "Advanced Usage" doc [\#1655](https://github.com/jrnl-org/jrnl/pull/1655) ([multani](https://github.com/multani)) - Remove Windows 7 known issue since Windows 7 is no longer supported [\#1636](https://github.com/jrnl-org/jrnl/pull/1636) ([micahellison](https://github.com/micahellison)) From fff05eb6467dd15ee50964945933aed3db193f3c Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 14 Jan 2023 14:42:29 -0800 Subject: [PATCH 041/373] Refactor flow for easier access to some files (avoid things like `jrnl.Journal.Journal` and `jrnl.jrnl` co-existing) (#1662) * run format * rename cli.py to main.py * rename jrnl.py to controller.py * move journal class files into journals dir * rename start -> run in controller.py --- jrnl/__main__.py | 4 ++-- jrnl/color.py | 2 +- jrnl/commands.py | 6 ++--- jrnl/{jrnl.py => controller.py} | 6 ++--- jrnl/install.py | 4 ++-- jrnl/{ => journals}/DayOneJournal.py | 19 +++++++++------ jrnl/{ => journals}/Entry.py | 4 ++-- jrnl/{ => journals}/FolderJournal.py | 7 +++--- jrnl/{ => journals}/Journal.py | 23 ++++++++++--------- jrnl/journals/__init__.py | 5 ++++ jrnl/{cli.py => main.py} | 6 ++--- jrnl/plugins/dates_exporter.py | 4 ++-- jrnl/plugins/fancy_exporter.py | 4 ++-- jrnl/plugins/jrnl_importer.py | 2 +- jrnl/plugins/json_exporter.py | 4 ++-- jrnl/plugins/markdown_exporter.py | 4 ++-- jrnl/plugins/tag_exporter.py | 4 ++-- jrnl/plugins/text_exporter.py | 4 ++-- jrnl/plugins/util.py | 2 +- jrnl/plugins/xml_exporter.py | 4 ++-- jrnl/plugins/yaml_exporter.py | 6 ++--- jrnl/upgrade.py | 11 +++++---- pyproject.toml | 4 +++- tests/lib/fixtures.py | 2 +- tests/lib/when_steps.py | 4 ++-- .../unit/{test_jrnl.py => test_controller.py} | 8 +++---- 26 files changed, 84 insertions(+), 69 deletions(-) rename jrnl/{jrnl.py => controller.py} (99%) rename jrnl/{ => journals}/DayOneJournal.py (95%) rename jrnl/{ => journals}/Entry.py (98%) rename jrnl/{ => journals}/FolderJournal.py (97%) rename jrnl/{ => journals}/Journal.py (96%) create mode 100644 jrnl/journals/__init__.py rename jrnl/{cli.py => main.py} (94%) rename tests/unit/{test_jrnl.py => test_controller.py} (86%) diff --git a/jrnl/__main__.py b/jrnl/__main__.py index 69a9fd5e..085da2a0 100644 --- a/jrnl/__main__.py +++ b/jrnl/__main__.py @@ -3,7 +3,7 @@ import sys -from jrnl.cli import cli +from jrnl.main import run if __name__ == "__main__": - sys.exit(cli()) + sys.exit(run()) diff --git a/jrnl/color.py b/jrnl/color.py index 2aaadad5..37b7a631 100644 --- a/jrnl/color.py +++ b/jrnl/color.py @@ -11,7 +11,7 @@ import colorama from jrnl.os_compat import on_windows if TYPE_CHECKING: - from jrnl.Entry import Entry + from jrnl.journals import Entry if on_windows(): colorama.init() diff --git a/jrnl/commands.py b/jrnl/commands.py index 9b6783ed..83618ce2 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -69,7 +69,7 @@ def postconfig_list(args: argparse.Namespace, config: dict, **_) -> int: @cmd_requires_valid_journal_name def postconfig_import(args: argparse.Namespace, config: dict, **_) -> int: - from jrnl.Journal import open_journal + from jrnl.journals import open_journal from jrnl.plugins import get_importer # Requires opening the journal @@ -90,7 +90,7 @@ def postconfig_encrypt( """ from jrnl.config import update_config from jrnl.install import save_config - from jrnl.Journal import open_journal + from jrnl.journals import open_journal # Open the journal journal = open_journal(args.journal_name, config) @@ -145,7 +145,7 @@ def postconfig_decrypt( """Decrypts into new file. If filename is not set, we encrypt the journal file itself.""" from jrnl.config import update_config from jrnl.install import save_config - from jrnl.Journal import open_journal + from jrnl.journals import open_journal journal = open_journal(args.journal_name, config) diff --git a/jrnl/jrnl.py b/jrnl/controller.py similarity index 99% rename from jrnl/jrnl.py rename to jrnl/controller.py index cda124cd..7d7c87ce 100644 --- a/jrnl/jrnl.py +++ b/jrnl/controller.py @@ -15,8 +15,8 @@ from jrnl.config import scope_config from jrnl.editor import get_text_from_editor from jrnl.editor import get_text_from_stdin from jrnl.exception import JrnlException -from jrnl.Journal import Journal -from jrnl.Journal import open_journal +from jrnl.journals import Journal +from jrnl.journals import open_journal from jrnl.messages import Message from jrnl.messages import MsgStyle from jrnl.messages import MsgText @@ -28,7 +28,7 @@ from jrnl.path import expand_path if TYPE_CHECKING: from argparse import Namespace - from jrnl.Entry import Entry + from jrnl.journals import Entry def run(args: "Namespace"): diff --git a/jrnl/install.py b/jrnl/install.py index 9fab98cc..239f07be 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -41,8 +41,8 @@ def upgrade_config(config_data: dict, alt_config_path: str | None = None) -> Non if missing_keys: for key in missing_keys: config_data[key] = default_config[key] - - different_version = (config_data["version"] != __version__) + + different_version = config_data["version"] != __version__ if different_version: config_data["version"] = __version__ diff --git a/jrnl/DayOneJournal.py b/jrnl/journals/DayOneJournal.py similarity index 95% rename from jrnl/DayOneJournal.py rename to jrnl/journals/DayOneJournal.py index 627b9c65..ace6a5f4 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/journals/DayOneJournal.py @@ -17,13 +17,14 @@ from xml.parsers.expat import ExpatError import tzlocal -from jrnl import Entry -from jrnl import Journal from jrnl import __title__ from jrnl import __version__ +from .Entry import Entry +from .Journal import Journal -class DayOne(Journal.Journal): + +class DayOne(Journal): """A special Journal handling DayOne files""" # InvalidFileException was added to plistlib in Python3.4 @@ -63,7 +64,7 @@ class DayOne(Journal.Journal): if timezone.key != "UTC": date = date.replace(fold=1) + timezone.utcoffset(date) - entry = Entry.Entry( + entry = Entry( self, date, text=dict_entry["Entry Text"], @@ -74,7 +75,8 @@ class DayOne(Journal.Journal): self.config["tagsymbols"][0] + tag.lower() for tag in dict_entry.get("Tags", []) ] - if entry._tags: entry._tags.sort() + if entry._tags: + entry._tags.sort() """Extended DayOne attributes""" # just ignore it if the keys don't exist @@ -196,7 +198,8 @@ class DayOne(Journal.Journal): for entry in entries_from_editor: entry = self._get_and_remove_uuid_from_entry(entry) - if entry._tags: entry._tags.sort() + if entry._tags: + entry._tags.sort() # Remove deleted entries edited_uuids = [e.uuid for e in entries_from_editor] @@ -207,7 +210,9 @@ class DayOne(Journal.Journal): for old_entry in self.entries: if entry.uuid == old_entry.uuid: if old_entry._tags: - tags_not_in_body = [tag for tag in old_entry._tags if(tag not in entry._body)] + tags_not_in_body = [ + tag for tag in old_entry._tags if (tag not in entry._body) + ] if tags_not_in_body: entry._tags.extend(tags_not_in_body.sort()) self._update_old_entry(old_entry, entry) diff --git a/jrnl/Entry.py b/jrnl/journals/Entry.py similarity index 98% rename from jrnl/Entry.py rename to jrnl/journals/Entry.py index 810cf86f..93b825f8 100644 --- a/jrnl/Entry.py +++ b/jrnl/journals/Entry.py @@ -9,8 +9,8 @@ from typing import TYPE_CHECKING import ansiwrap -from .color import colorize -from .color import highlight_tags_with_background_color +from jrnl.color import colorize +from jrnl.color import highlight_tags_with_background_color if TYPE_CHECKING: from .Journal import Journal diff --git a/jrnl/FolderJournal.py b/jrnl/journals/FolderJournal.py similarity index 97% rename from jrnl/FolderJournal.py rename to jrnl/journals/FolderJournal.py index 055d9408..316a10f2 100644 --- a/jrnl/FolderJournal.py +++ b/jrnl/journals/FolderJournal.py @@ -6,11 +6,12 @@ import fnmatch import os from typing import TYPE_CHECKING -from jrnl import Journal from jrnl import time +from .Journal import Journal + if TYPE_CHECKING: - from jrnl.Entry import Entry + from jrnl.journals import Entry def get_files(journal_config: str) -> list[str]: @@ -22,7 +23,7 @@ def get_files(journal_config: str) -> list[str]: return filenames -class Folder(Journal.Journal): +class Folder(Journal): """A Journal handling multiple files in a folder""" def __init__(self, name: str = "default", **kwargs): diff --git a/jrnl/Journal.py b/jrnl/journals/Journal.py similarity index 96% rename from jrnl/Journal.py rename to jrnl/journals/Journal.py index 1b444fc5..5a8b016e 100644 --- a/jrnl/Journal.py +++ b/jrnl/journals/Journal.py @@ -6,7 +6,6 @@ import logging import os import re -from jrnl import Entry from jrnl import time from jrnl.config import validate_journal_name from jrnl.encryption import determine_encryption_method @@ -17,6 +16,8 @@ from jrnl.output import print_msg from jrnl.path import expand_path from jrnl.prompt import yesno +from .Entry import Entry + class Tag: def __init__(self, name, count=0): @@ -184,11 +185,11 @@ class Journal: if entries: entries[-1].text = journal_txt[last_entry_pos : match.start()] last_entry_pos = match.end() - entries.append(Entry.Entry(self, date=new_date)) + entries.append(Entry(self, date=new_date)) # If no entries were found, treat all the existing text as an entry made now if not entries: - entries.append(Entry.Entry(self, date=time.parse("now"))) + entries.append(Entry(self, date=time.parse("now"))) # Fill in the text of the last entry entries[-1].text = journal_txt[last_entry_pos:] @@ -354,7 +355,7 @@ class Journal: ) if not date: # Still nothing? Meh, just live in the moment. date = time.parse("now") - entry = Entry.Entry(self, date, raw, starred=starred) + entry = Entry(self, date, raw, starred=starred) entry.modified = True self.entries.append(entry) if sort: @@ -410,7 +411,7 @@ class LegacyJournal(Journal): else: starred = False - current_entry = Entry.Entry( + current_entry = Entry( self, date=new_date, text=line[date_length + 1 :], starred=starred ) except ValueError: @@ -455,21 +456,21 @@ def open_journal(journal_name: str, config: dict, legacy: bool = False) -> Journ if config["journal"].strip("/").endswith(".dayone") or "entries" in os.listdir( config["journal"] ): - from jrnl import DayOneJournal + from jrnl.journals import DayOne - return DayOneJournal.DayOne(**config).open() + return DayOne(**config).open() else: - from jrnl import FolderJournal + from jrnl.journals import Folder - return FolderJournal.Folder(journal_name, **config).open() + return Folder(journal_name, **config).open() if not config["encrypt"]: if legacy: return LegacyJournal(journal_name, **config).open() if config["journal"].endswith(os.sep): - from jrnl import FolderJournal + from jrnl.journals import Folder - return FolderJournal.Folder(journal_name, **config).open() + return Folder(journal_name, **config).open() return Journal(journal_name, **config).open() if legacy: diff --git a/jrnl/journals/__init__.py b/jrnl/journals/__init__.py new file mode 100644 index 00000000..eb3dc44f --- /dev/null +++ b/jrnl/journals/__init__.py @@ -0,0 +1,5 @@ +from .DayOneJournal import DayOne +from .Entry import Entry +from .FolderJournal import Folder +from .Journal import Journal +from .Journal import open_journal diff --git a/jrnl/cli.py b/jrnl/main.py similarity index 94% rename from jrnl/cli.py rename to jrnl/main.py index a6b159db..770690c4 100644 --- a/jrnl/cli.py +++ b/jrnl/main.py @@ -7,9 +7,9 @@ import traceback from rich.logging import RichHandler +from jrnl import controller from jrnl.args import parse_args from jrnl.exception import JrnlException -from jrnl.jrnl import run from jrnl.messages import Message from jrnl.messages import MsgStyle from jrnl.messages import MsgText @@ -32,7 +32,7 @@ def configure_logger(debug: bool = False) -> None: logging.debug("Logging start") -def cli(manual_args: list[str] | None = None) -> int: +def run(manual_args: list[str] | None = None) -> int: try: if manual_args is None: manual_args = sys.argv[1:] @@ -41,7 +41,7 @@ def cli(manual_args: list[str] | None = None) -> int: configure_logger(args.debug) logging.debug("Parsed args:\n%s", args) - status_code = run(args) + status_code = controller.run(args) except JrnlException as e: status_code = 1 diff --git a/jrnl/plugins/dates_exporter.py b/jrnl/plugins/dates_exporter.py index 1e2ae0dc..38d101dd 100644 --- a/jrnl/plugins/dates_exporter.py +++ b/jrnl/plugins/dates_exporter.py @@ -7,8 +7,8 @@ from typing import TYPE_CHECKING from jrnl.plugins.text_exporter import TextExporter if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class DatesExporter(TextExporter): diff --git a/jrnl/plugins/fancy_exporter.py b/jrnl/plugins/fancy_exporter.py index 4b800754..447f1347 100644 --- a/jrnl/plugins/fancy_exporter.py +++ b/jrnl/plugins/fancy_exporter.py @@ -13,8 +13,8 @@ from jrnl.messages import MsgText from jrnl.plugins.text_exporter import TextExporter if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class FancyExporter(TextExporter): diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index b5bc0490..8c326182 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -11,7 +11,7 @@ from jrnl.messages import MsgText from jrnl.output import print_msg if TYPE_CHECKING: - from jrnl.Journal import Journal + from jrnl.journals import Journal class JRNLImporter: diff --git a/jrnl/plugins/json_exporter.py b/jrnl/plugins/json_exporter.py index 3a7c56ac..66d2bcc3 100644 --- a/jrnl/plugins/json_exporter.py +++ b/jrnl/plugins/json_exporter.py @@ -8,8 +8,8 @@ from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.util import get_tags_count if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class JSONExporter(TextExporter): diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index 9335847f..1512903d 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -12,8 +12,8 @@ from jrnl.output import print_msg from jrnl.plugins.text_exporter import TextExporter if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class MarkdownExporter(TextExporter): diff --git a/jrnl/plugins/tag_exporter.py b/jrnl/plugins/tag_exporter.py index 5702978d..b8b5eb79 100644 --- a/jrnl/plugins/tag_exporter.py +++ b/jrnl/plugins/tag_exporter.py @@ -7,8 +7,8 @@ from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.util import get_tags_count if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class TagExporter(TextExporter): diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index a839ee88..0a514da1 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -13,8 +13,8 @@ from jrnl.messages import MsgText from jrnl.output import print_msg if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class TextExporter: diff --git a/jrnl/plugins/util.py b/jrnl/plugins/util.py index 86eb5b17..ceaa0b04 100644 --- a/jrnl/plugins/util.py +++ b/jrnl/plugins/util.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from jrnl.Journal import Journal + from jrnl.journals import Journal def get_tags_count(journal: "Journal") -> set[tuple[int, str]]: diff --git a/jrnl/plugins/xml_exporter.py b/jrnl/plugins/xml_exporter.py index 3bda38e6..a0349af9 100644 --- a/jrnl/plugins/xml_exporter.py +++ b/jrnl/plugins/xml_exporter.py @@ -8,8 +8,8 @@ from jrnl.plugins.json_exporter import JSONExporter from jrnl.plugins.util import get_tags_count if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class XMLExporter(JSONExporter): diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index 2927243e..d960ef8a 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -13,8 +13,8 @@ from jrnl.output import print_msg from jrnl.plugins.text_exporter import TextExporter if TYPE_CHECKING: - from jrnl.Entry import Entry - from jrnl.Journal import Journal + from jrnl.journals import Entry + from jrnl.journals import Journal class YAMLExporter(TextExporter): @@ -34,7 +34,7 @@ class YAMLExporter(TextExporter): body = body_wrapper + entry.body tagsymbols = entry.journal.config["tagsymbols"] - # see also Entry.Entry.rag_regex + # see also Entry.rag_regex multi_tag_regex = re.compile(rf"(?u)^\s*([{tagsymbols}][-+*#/\w]+\s*)+$") """Increase heading levels in body text""" diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index d390844c..1b6e500d 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -4,12 +4,13 @@ import logging import os -from jrnl import Journal from jrnl import __version__ from jrnl.config import is_config_json from jrnl.config import load_config from jrnl.config import scope_config from jrnl.exception import JrnlException +from jrnl.journals import Journal +from jrnl.journals import open_journal from jrnl.messages import Message from jrnl.messages import MsgStyle from jrnl.messages import MsgText @@ -129,14 +130,14 @@ def upgrade_jrnl(config_path: str) -> None: ) backup(path, binary=True) - old_journal = Journal.open_journal( + old_journal = open_journal( journal_name, scope_config(config, journal_name), legacy=True ) logging.debug(f"Clearing encryption method for '{journal_name}' journal") # Update the encryption method - new_journal = Journal.Journal.from_journal(old_journal) + new_journal = Journal.from_journal(old_journal) new_journal.config["encrypt"] = "jrnlv2" new_journal._get_encryption_method() # Copy over password (jrnlv1 only supported password-based encryption) @@ -156,10 +157,10 @@ def upgrade_jrnl(config_path: str) -> None: ) backup(path) - old_journal = Journal.open_journal( + old_journal = open_journal( journal_name, scope_config(config, journal_name), legacy=True ) - all_journals.append(Journal.Journal.from_journal(old_journal)) + all_journals.append(Journal.from_journal(old_journal)) # loop through lists to validate failed_journals = [j for j in all_journals if not j.validate_parsing()] diff --git a/pyproject.toml b/pyproject.toml index 26dc4316..da931d0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ tox = "*" xmltodict = "*" [tool.poetry.scripts] -jrnl = 'jrnl.cli:cli' +jrnl = 'jrnl.main:run' [tool.poe.tasks] docs-check.default_item_type = "script" @@ -157,6 +157,8 @@ pycodestyle = [ "flake8-*" = ["+*"] flake8-black = ["-BLK901"] +[tool.flakeheaven.exceptions."jrnl/journals/__init__.py"] +pyflakes = ["-F401"] [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index 1dc09378..3f012e96 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -144,7 +144,7 @@ def mock_overrides(config_in_memory): return { "overrides": lambda: patch( - "jrnl.jrnl.apply_overrides", side_effect=my_overrides + "jrnl.controller.apply_overrides", side_effect=my_overrides ) } diff --git a/tests/lib/when_steps.py b/tests/lib/when_steps.py index d8ab8e72..38ea4ab1 100644 --- a/tests/lib/when_steps.py +++ b/tests/lib/when_steps.py @@ -8,7 +8,7 @@ from pytest_bdd import when from pytest_bdd.parsers import parse from pytest_bdd.parsers import re -from jrnl.cli import cli +from jrnl.main import run @when(parse('we change directory to "{directory_name}"')) @@ -44,7 +44,7 @@ def we_run_jrnl(cli_run, capsys, keyring): mocks[id] = stack.enter_context(factories[id]()) try: - cli_run["status"] = cli() or 0 + cli_run["status"] = run() or 0 except StopIteration: # This happens when input is expected, but don't have any input left pass diff --git a/tests/unit/test_jrnl.py b/tests/unit/test_controller.py similarity index 86% rename from tests/unit/test_jrnl.py rename to tests/unit/test_controller.py index 9e6d9b25..d60cd2d6 100644 --- a/tests/unit/test_jrnl.py +++ b/tests/unit/test_controller.py @@ -9,7 +9,7 @@ import pytest import jrnl from jrnl.args import parse_args -from jrnl.jrnl import _display_search_results +from jrnl.controller import _display_search_results @pytest.fixture @@ -19,10 +19,10 @@ def random_string(): @pytest.mark.parametrize("export_format", ["pretty", "short"]) @mock.patch("builtins.print") -@mock.patch("jrnl.Journal.Journal.pprint") +@mock.patch("jrnl.controller.Journal.pprint") def test_display_search_results_pretty_short(mock_pprint, mock_print, export_format): mock_args = parse_args(["--format", export_format]) - test_journal = mock.Mock(wraps=jrnl.Journal.Journal) + test_journal = mock.Mock(wraps=jrnl.journals.Journal) _display_search_results(mock_args, test_journal) @@ -40,7 +40,7 @@ def test_display_search_results_builtin_plugins( test_filename = random_string mock_args = parse_args(["--format", export_format, "--file", test_filename]) - test_journal = mock.Mock(wraps=jrnl.Journal.Journal) + test_journal = mock.Mock(wraps=jrnl.journals.Journal) mock_export = mock.Mock() mock_exporter.return_value.export = mock_export From 17c987c6051a9c9c45698f002b61a8e3c83b4195 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 14 Jan 2023 22:44:27 +0000 Subject: [PATCH 042/373] Update changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7767543..dff0f843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ **Implemented enhancements:** - Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521) +- Refactor flow for easier access to some files \(avoid things like `jrnl.Journal.Journal` and `jrnl.jrnl` co-existing\) [\#1662](https://github.com/jrnl-org/jrnl/pull/1662) ([wren](https://github.com/wren)) - Add more type hints [\#1642](https://github.com/jrnl-org/jrnl/pull/1642) ([outa](https://github.com/outa)) - Add `rich` handler to debug logging [\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren)) - Rework Encryption to enable future support of other encryption methods [\#1602](https://github.com/jrnl-org/jrnl/pull/1602) ([wren](https://github.com/wren)) From 7bd15d12adca5a36e6a56ecbd6a53dc06c5f7f4c Mon Sep 17 00:00:00 2001 From: Ciaran Concannon Date: Sat, 28 Jan 2023 19:45:01 +0000 Subject: [PATCH 043/373] Search for entries with no tags or stars with `-not -starred` and `-not -tagged` (#1663) * Allow for `-not -starred` to search for unstarred entries * Add `-tagged` and `-not -tagged` functionality --- jrnl/args.py | 59 ++++++++++++++++++++++++++++--- jrnl/controller.py | 9 +++++ jrnl/journals/Journal.py | 30 +++++++++------- tests/bdd/features/search.feature | 44 +++++++++++++++++++++++ tests/unit/test_parse_args.py | 3 ++ 5 files changed, 127 insertions(+), 18 deletions(-) diff --git a/jrnl/args.py b/jrnl/args.py index d23e1e53..b1055da3 100644 --- a/jrnl/args.py +++ b/jrnl/args.py @@ -28,6 +28,43 @@ class WrappingFormatter(argparse.RawTextHelpFormatter): return text +class IgnoreNoneAppendAction(argparse._AppendAction): + """ + Pass -not without a following string and avoid appending + a None value to the excluded list + """ + + def __call__(self, parser, namespace, values, option_string=None): + if values is not None: + super().__call__(parser, namespace, values, option_string) + + +def parse_not_arg( + args: list[str], parsed_args: argparse.Namespace, parser: argparse.ArgumentParser +) -> argparse.Namespace: + """ + It's possible to use -not as a precursor to -starred and -tagged + to reverse their behaviour, however this requires some extra logic + to parse, and to ensure we still do not allow passing an empty -not + """ + + parsed_args.exclude_starred = False + parsed_args.exclude_tagged = False + + if "-not-starred" in "".join(args): + parsed_args.starred = False + parsed_args.exclude_starred = True + if "-not-tagged" in "".join(args): + parsed_args.tagged = False + parsed_args.exclude_tagged = True + if "-not" in args and not any( + [parsed_args.exclude_starred, parsed_args.exclude_tagged, parsed_args.excluded] + ): + parser.error("argument -not: expected 1 argument") + + return parsed_args + + def parse_args(args: list[str] = []) -> argparse.Namespace: """ Argument parsing that is doable before the config is available. @@ -237,6 +274,12 @@ def parse_args(args: list[str] = []) -> argparse.Namespace: action="store_true", help="Show only starred entries (marked with *)", ) + reading.add_argument( + "-tagged", + dest="tagged", + action="store_true", + help="Show only entries that have at least one tag", + ) reading.add_argument( "-n", dest="limit", @@ -249,11 +292,15 @@ def parse_args(args: list[str] = []) -> argparse.Namespace: reading.add_argument( "-not", dest="excluded", - nargs=1, + nargs="?", default=[], - metavar="TAG", - action="extend", - help="Exclude entries with this tag", + metavar="TAG/FLAG", + action=IgnoreNoneAppendAction, + help=( + "If passed a string, will exclude entries with that tag. " + "Can be also used before -starred or -tagged flags, to exclude " + "starred or tagged entries respectively." + ), ) search_options_msg = """ These help you do various tasks with the selected entries from your search. @@ -388,5 +435,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace: # Handle '-123' as a shortcut for '-n 123' num = re.compile(r"^-(\d+)$") args = [num.sub(r"-n \1", arg) for arg in args] + parsed_args = parser.parse_intermixed_args(args) + parsed_args = parse_not_arg(args, parsed_args, parser) - return parser.parse_intermixed_args(args) + return parsed_args diff --git a/jrnl/controller.py b/jrnl/controller.py index 7d7c87ce..7a0a4965 100644 --- a/jrnl/controller.py +++ b/jrnl/controller.py @@ -89,6 +89,8 @@ def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool: args.edit, args.change_time, args.excluded, + args.exclude_starred, + args.exclude_tagged, args.export, args.end_date, args.today_in_history, @@ -101,6 +103,7 @@ def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool: args.starred, args.start_date, args.strict, + args.tagged, args.tags, ) ) @@ -270,7 +273,10 @@ def _has_search_args(args: "Namespace") -> bool: args.end_date, args.strict, args.starred, + args.tagged, args.excluded, + args.exclude_starred, + args.exclude_tagged, args.contains, args.limit, ) @@ -296,7 +302,10 @@ def _filter_journal_entries(args: "Namespace", journal: Journal, **kwargs) -> No end_date=args.end_date, strict=args.strict, starred=args.starred, + tagged=args.tagged, exclude=args.excluded, + exclude_starred=args.exclude_starred, + exclude_tagged=args.exclude_tagged, contains=args.contains, ) journal.limit(args.limit) diff --git a/jrnl/journals/Journal.py b/jrnl/journals/Journal.py index 5a8b016e..994e0098 100644 --- a/jrnl/journals/Journal.py +++ b/jrnl/journals/Journal.py @@ -229,16 +229,19 @@ class Journal: def filter( self, - tags: list = [], - month: str | int | None = None, - day: str | int | None = None, - year: str | None = None, - start_date: str | None = None, - end_date: str | None = None, - starred: bool = False, - strict: bool = False, - contains: bool = None, - exclude: list = [], + tags=[], + month=None, + day=None, + year=None, + start_date=None, + end_date=None, + starred=False, + tagged=False, + exclude_starred=False, + exclude_tagged=False, + strict=False, + contains=None, + exclude=[], ): """Removes all entries from the journal that don't match the filter. @@ -259,7 +262,7 @@ class Journal: start_date = time.parse(start_date) # If strict mode is on, all tags have to be present in entry - tagged = self.search_tags.issubset if strict else self.search_tags.intersection + has_tags = self.search_tags.issubset if strict else self.search_tags.intersection def excluded(tags): return 0 < len([tag for tag in tags if tag in excluded_tags]) @@ -275,8 +278,9 @@ class Journal: result = [ entry for entry in self.entries - if (not tags or tagged(entry.tags)) - and (not starred or entry.starred) + if (not tags or has_tags(entry.tags)) + and (not (starred or exclude_starred) or entry.starred == starred) + and (not (tagged or exclude_tagged) or bool(entry.tags) == tagged) and (not month or entry.date.month == compare_d.month) and (not day or entry.date.day == compare_d.day) and (not year or entry.date.year == compare_d.year) diff --git a/tests/bdd/features/search.feature b/tests/bdd/features/search.feature index d653a621..138c0285 100644 --- a/tests/bdd/features/search.feature +++ b/tests/bdd/features/search.feature @@ -124,6 +124,50 @@ Feature: Searching in a journal | basic_folder.yaml | | basic_dayone.yaml | + + Scenario: Searching for unstarred entries + Given we use the config "" + And we use the password "test" if prompted + When we run "jrnl -not -starred" + Then we should get no error + And the output should contain "2 entries found" + + Examples: configs + | config_file | + | basic_onefile.yaml | + | basic_folder.yaml | + | basic_dayone.yaml | + + Scenario: Searching for tagged entries + Given we use the config "" + And we use the password "test" if prompted + When we run "jrnl -tagged" + Then we should get no error + And the output should contain "3 entries found" + + Examples: configs + | config_file | + | basic_onefile.yaml | + | basic_folder.yaml | + | basic_dayone.yaml | + + Scenario: Searching for untagged entries + Given we use the config "empty_folder.yaml" + When we run "jrnl Tagged entry. This one has a @tag." + Then we should get no error + When we run "jrnl Untagged entry. This one has no tag." + Then we should get no error + When we run "jrnl -not -tagged" + Then we should get no error + And the output should contain "1 entry found" + And the output should contain "This one has no tag" + + Examples: configs + | config_file | + | basic_onefile.yaml | + | basic_folder.yaml | + | basic_dayone.yaml | + Scenario Outline: Searching for dates Given we use the config "" When we run "jrnl -on 2020-08-31 --short" diff --git a/tests/unit/test_parse_args.py b/tests/unit/test_parse_args.py index ccb7f5a2..0b266d23 100644 --- a/tests/unit/test_parse_args.py +++ b/tests/unit/test_parse_args.py @@ -23,6 +23,8 @@ def expected_args(**kwargs): "change_time": None, "edit": False, "end_date": None, + "exclude_starred": False, + "exclude_tagged": False, "today_in_history": False, "month": None, "day": None, @@ -38,6 +40,7 @@ def expected_args(**kwargs): "starred": False, "start_date": None, "strict": False, + "tagged": False, "tags": False, "text": [], "config_override": [], From d4ce2a84cfbea665e5249f06d6112dd9cc661637 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 28 Jan 2023 19:47:17 +0000 Subject: [PATCH 044/373] Update changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dff0f843..ad10e106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ **Implemented enhancements:** - Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521) +- Search for entries with no tags or stars with `-not -starred` and `-not -tagged` [\#1663](https://github.com/jrnl-org/jrnl/pull/1663) ([cjcon90](https://github.com/cjcon90)) - Refactor flow for easier access to some files \(avoid things like `jrnl.Journal.Journal` and `jrnl.jrnl` co-existing\) [\#1662](https://github.com/jrnl-org/jrnl/pull/1662) ([wren](https://github.com/wren)) - Add more type hints [\#1642](https://github.com/jrnl-org/jrnl/pull/1642) ([outa](https://github.com/outa)) - Add `rich` handler to debug logging [\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren)) From 2d1a52afe51b3c2dbac97d658dd63fe439010d66 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Andrea Date: Sat, 11 Feb 2023 21:10:18 +0100 Subject: [PATCH 045/373] Update documentation on temporary files naming (#1673) --- docs/privacy-and-security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/privacy-and-security.md b/docs/privacy-and-security.md index a55e6fa3..32cf8ac3 100644 --- a/docs/privacy-and-security.md +++ b/docs/privacy-and-security.md @@ -78,7 +78,7 @@ unencrypted temporary remains on your disk. If your computer were to shut off during this time, or the `jrnl` process were killed unexpectedly, then the unencrypted temporary file will remain on your disk. You can mitigate this issue by only saving with your editor right before closing it. You can also -manually delete these files (i.e. files named `jrnl_*.txt`) from your temporary +manually delete these files (i.e. files named `jrnl*.jrnl`) from your temporary folder. ## Plausible deniability From 6b179e673b0637fcc9ce1b6c9917aa34cd8f36f1 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 11 Feb 2023 20:12:27 +0000 Subject: [PATCH 046/373] Update changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad10e106..1abc2e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ **Documentation:** - Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651) +- Update documentation on temporary files naming [\#1673](https://github.com/jrnl-org/jrnl/pull/1673) ([giuseppedandrea](https://github.com/giuseppedandrea)) - Update docs to include time and title in arguments with `--edit` [\#1657](https://github.com/jrnl-org/jrnl/pull/1657) ([pconrad-fb](https://github.com/pconrad-fb)) - Fix markup in "Advanced Usage" doc [\#1655](https://github.com/jrnl-org/jrnl/pull/1655) ([multani](https://github.com/multani)) - Remove Windows 7 known issue since Windows 7 is no longer supported [\#1636](https://github.com/jrnl-org/jrnl/pull/1636) ([micahellison](https://github.com/micahellison)) From 6c6937c5078fa4a7a31d18e044cc067a74dd7013 Mon Sep 17 00:00:00 2001 From: David Isaksson Date: Sat, 11 Feb 2023 21:16:31 +0100 Subject: [PATCH 047/373] Add documentation about information leaks in Vim/Neovim (#1674) * Add documentation about using Vim/Neovim as editor * Add documentation about information leaks in editors * Spelling fix --------- Co-authored-by: Jonathan Wren --- docs/external-editors.md | 14 +++++++ docs/privacy-and-security.md | 78 ++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/docs/external-editors.md b/docs/external-editors.md index 977a9de2..b7219d8d 100644 --- a/docs/external-editors.md +++ b/docs/external-editors.md @@ -37,6 +37,9 @@ jrnl yesterday: All my troubles seemed so far away. --edit All editors must be [blocking processes](https://en.wikipedia.org/wiki/Blocking_(computing)) to work with jrnl. Some editors, such as [micro](https://micro-editor.github.io/), are blocking by default, though others can be made to block with additional arguments, such as many of those documented below. If jrnl opens your editor but finishes running immediately, then your editor is not a blocking process, and you may be able to correct that with one of the suggestions below. +Please see [this section](./privacy-and-security.md#editor-history) about how +your editor might leak sensitive information and how to mitigate that risk. + ## Sublime Text To use [Sublime Text](https://www.sublimetext.com/), install the command line @@ -71,6 +74,17 @@ back to journal. In the case of MacVim, this is `-f`: editor: "mvim -f" ``` +## Vim/Neovim + +To use any of the Vim derivatives as editor in Linux, simply set the `editor` +to the executable: + +```yaml +editor: "vim" +# or +editor: "nvim" +``` + ## iA Writer On OS X, you can use the fabulous [iA diff --git a/docs/privacy-and-security.md b/docs/privacy-and-security.md index 32cf8ac3..c767c5e0 100644 --- a/docs/privacy-and-security.md +++ b/docs/privacy-and-security.md @@ -67,6 +67,84 @@ Windows doesn't log history to disk, but it does keep it in your command prompt session. Close the command prompt or press `Alt`+`F7` to clear your history after journaling. +## Editor history + +Some editors keep usage history stored on disk for future use. This can be a +security risk in the sense that sensitive information can leak via recent +search patterns or editor commands. + +### Vim + +Vim stores progress data in a so called Viminfo file located at `~/.viminfo` +which contains all sorts of user data including command line history, search +string history, search/substitute patterns, contents of register etc. Also to +be able to recover opened files after an unexpected application close Vim uses +swap files. + +These options as well as other leaky features can be disabled by setting the +`editor` key in the Jrnl settings like this: + +``` yaml +editor: "vim -c 'set viminfo= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure'" +``` + +To disable all plugins and custom configurations and start Vim with the default +configuration `-u NONE` can be passed on the command line as well. This will +ensure that any rogue plugins or other difficult to catch information leaks are +eliminated. The downside to this is that the editor experience will decrease +quite a bit. + +To instead let Vim automatically detect when a Jrnl file is being edited an +autocommand can be used. Place this in your `~/.vimrc`: + +``` vim +autocmd BufNewFile,BufReadPre *.jrnl setlocal viminfo= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure +``` + +Please see `:h