mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
Merge branch 'develop' into rich-instead-of-ansiwrap-1191
poetry.lock retrieved from develop then re-locked with poetry lock --no-update
This commit is contained in:
commit
d777d41796
33 changed files with 533 additions and 712 deletions
2
.github/workflows/changelog.yaml
vendored
2
.github/workflows/changelog.yaml
vendored
|
@ -59,7 +59,7 @@ jobs:
|
|||
|
||||
if [[ "$(git rev-parse "origin/$BRANCH")" != "$GITHUB_SHA" ]]; then
|
||||
# Normal build on a branch (no tag)
|
||||
echo "::debug::BRANCH: $BRANCH $(git rev-parse origin/$BRANCH)"
|
||||
echo "::debug::BRANCH: $BRANCH $(git rev-parse "origin/$BRANCH")"
|
||||
echo "::debug::GITHUB_SHA: $GITHUB_SHA"
|
||||
echo "::error::$BRANCH has been updated since build started. Aborting changelog."
|
||||
exit 1
|
||||
|
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
@ -200,7 +200,7 @@ jobs:
|
|||
--force
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
path: ${{ env.BREW_TAP_DIRECTORY }}
|
||||
token: ${{ secrets.JRNL_BOT_TOKEN }}
|
||||
|
|
2
.github/workflows/testing_pipelines.yaml
vendored
2
.github/workflows/testing_pipelines.yaml
vendored
|
@ -14,6 +14,8 @@ on:
|
|||
paths:
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
schedule:
|
||||
- cron: '0 0 * * SAT'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -19,6 +19,7 @@ var/
|
|||
node_modules/
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
.flakeheaven_cache/
|
||||
|
||||
# Versioning
|
||||
.python-version
|
||||
|
@ -39,7 +40,7 @@ exp/
|
|||
objects.inv
|
||||
searchindex.js
|
||||
|
||||
# virtaulenv
|
||||
# virtualenv
|
||||
.venv*/
|
||||
env/
|
||||
env*/
|
||||
|
|
124
CHANGELOG.md
124
CHANGELOG.md
|
@ -1,45 +1,71 @@
|
|||
# Changelog
|
||||
|
||||
## [v4.0-beta3](https://pypi.org/project/jrnl/v4.0-beta3/) (2023-04-29)
|
||||
## [Unreleased](https://github.com/jrnl-org/jrnl/)
|
||||
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta2...v4.0-beta3)
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0.1...HEAD)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- jrnl reads extraneous text files when reading folder journal [\#1692](https://github.com/jrnl-org/jrnl/issues/1692)
|
||||
- jrnl crashes when adding tag argument after `--change-time` [\#1644](https://github.com/jrnl-org/jrnl/issues/1644)
|
||||
- Only read text files that look like entries when opening folder journal [\#1697](https://github.com/jrnl-org/jrnl/pull/1697) ([micahellison](https://github.com/micahellison))
|
||||
- Linting rules aren't enforced the same as format rules [\#1742](https://github.com/jrnl-org/jrnl/issues/1742)
|
||||
|
||||
**Build:**
|
||||
|
||||
- Replace flake8 and isort with ruff linter and add `black --check` to linting step [\#1763](https://github.com/jrnl-org/jrnl/pull/1763) ([micahellison](https://github.com/micahellison))
|
||||
|
||||
**Documentation:**
|
||||
|
||||
- Update contributing.md links in documentation [\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking))
|
||||
|
||||
## [v4.0-beta2](https://pypi.org/project/jrnl/v4.0-beta2/) (2023-04-22)
|
||||
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta...v4.0-beta2)
|
||||
|
||||
**Documentation:**
|
||||
|
||||
- Fix various typos [\#1718](https://github.com/jrnl-org/jrnl/pull/1718) ([hezhizhen](https://github.com/hezhizhen))
|
||||
- Add note about messages going to `stderr` and the implication for piping [\#1768](https://github.com/jrnl-org/jrnl/pull/1768) ([micahellison](https://github.com/micahellison))
|
||||
|
||||
**Packaging:**
|
||||
|
||||
- Update dependency cryptography to v40.0.2 [\#1723](https://github.com/jrnl-org/jrnl/pull/1723) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flakeheaven to v3.3.0 [\#1722](https://github.com/jrnl-org/jrnl/pull/1722) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest to v7.3.1 [\#1720](https://github.com/jrnl-org/jrnl/pull/1720) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency black to v23.3.0 [\#1715](https://github.com/jrnl-org/jrnl/pull/1715) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flake8-type-checking to v2.4.0 [\#1714](https://github.com/jrnl-org/jrnl/pull/1714) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13.3.4 [\#1713](https://github.com/jrnl-org/jrnl/pull/1713) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tox to v4.4.12 [\#1712](https://github.com/jrnl-org/jrnl/pull/1712) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency keyring to v24.2.0 [\#1760](https://github.com/jrnl-org/jrnl/pull/1760) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency keyring to v24 [\#1758](https://github.com/jrnl-org/jrnl/pull/1758) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest to v7.4.0 [\#1757](https://github.com/jrnl-org/jrnl/pull/1757) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13.4.2 [\#1754](https://github.com/jrnl-org/jrnl/pull/1754) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency cryptography to v41 [\#1753](https://github.com/jrnl-org/jrnl/pull/1753) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency ruamel.yaml to v0.17.32 [\#1752](https://github.com/jrnl-org/jrnl/pull/1752) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tox to v4.6.3 [\#1751](https://github.com/jrnl-org/jrnl/pull/1751) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
|
||||
## [v4.0-beta](https://pypi.org/project/jrnl/v4.0-beta/) (2023-03-25)
|
||||
## [v4.0.1](https://pypi.org/project/jrnl/v4.0.1/) (2023-06-20)
|
||||
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3...v4.0-beta)
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0.1-beta...v4.0.1)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- jrnl crashes when running `jrnl --list --format json` and `jrnl --list --format yaml` [\#1737](https://github.com/jrnl-org/jrnl/issues/1737)
|
||||
- Refactor --template code [\#1711](https://github.com/jrnl-org/jrnl/pull/1711) ([micahellison](https://github.com/micahellison))
|
||||
|
||||
**Build:**
|
||||
|
||||
- Fix linting issue in CI pipeline [\#1743](https://github.com/jrnl-org/jrnl/pull/1743) ([wren](https://github.com/wren))
|
||||
|
||||
**Packaging:**
|
||||
|
||||
- Update dependency ruamel.yaml to v0.17.28 [\#1749](https://github.com/jrnl-org/jrnl/pull/1749) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency requests to v2.31.0 [\#1748](https://github.com/jrnl-org/jrnl/pull/1748) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency ruamel.yaml to v0.17.26 [\#1746](https://github.com/jrnl-org/jrnl/pull/1746) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tzlocal to v5 [\#1741](https://github.com/jrnl-org/jrnl/pull/1741) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest-xdist to v3.3.1 [\#1740](https://github.com/jrnl-org/jrnl/pull/1740) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency poethepoet to v0.20.0 [\#1735](https://github.com/jrnl-org/jrnl/pull/1735) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency mkdocs to v1.4.3 [\#1733](https://github.com/jrnl-org/jrnl/pull/1733) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13.3.5 [\#1729](https://github.com/jrnl-org/jrnl/pull/1729) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency requests to v2.30.0 [\#1728](https://github.com/jrnl-org/jrnl/pull/1728) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tox to v4.5.1 [\#1727](https://github.com/jrnl-org/jrnl/pull/1727) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update peter-evans/create-pull-request action to v5 [\#1719](https://github.com/jrnl-org/jrnl/pull/1719) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flake8-simplify to v0.20.0 [\#1716](https://github.com/jrnl-org/jrnl/pull/1716) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
|
||||
## [v4.0](https://pypi.org/project/jrnl/v4.0/) (2023-05-20)
|
||||
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta3...v4.0)
|
||||
|
||||
🚨 **BREAKING CHANGES** 🚨
|
||||
|
||||
**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))
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Display locations of config file and documentation after initial install [\#1694](https://github.com/jrnl-org/jrnl/issues/1694)
|
||||
- Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521)
|
||||
- Add message with config location and docs location when installation is complete [\#1695](https://github.com/jrnl-org/jrnl/pull/1695) ([micahellison](https://github.com/micahellison))
|
||||
- Prompt to include colors in config when first running jrnl [\#1687](https://github.com/jrnl-org/jrnl/pull/1687) ([micahellison](https://github.com/micahellison))
|
||||
- Add ability to use template with `--template` [\#1667](https://github.com/jrnl-org/jrnl/pull/1667) ([alichtman](https://github.com/alichtman))
|
||||
|
@ -51,25 +77,15 @@
|
|||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Combinations of `--change-time`, `--delete`, and `--edit` don't work consistently [\#1696](https://github.com/jrnl-org/jrnl/issues/1696)
|
||||
- jrnl doesn't display count of entries deleted after deleting entries with `--delete` [\#1666](https://github.com/jrnl-org/jrnl/issues/1666)
|
||||
- Templated entries should not be saved if the raw text is identical to the original template [\#1652](https://github.com/jrnl-org/jrnl/issues/1652)
|
||||
- Adding an entry with a combination of flags causes a journal overwrite [\#1639](https://github.com/jrnl-org/jrnl/issues/1639)
|
||||
- jrnl does not update version key in config file [\#1638](https://github.com/jrnl-org/jrnl/issues/1638)
|
||||
- jrnl should not create 0-length "encrypted" file on startup [\#1493](https://github.com/jrnl-org/jrnl/issues/1493)
|
||||
- Only read text files that look like entries when opening folder journal [\#1697](https://github.com/jrnl-org/jrnl/pull/1697) ([micahellison](https://github.com/micahellison))
|
||||
- Save empty journal on install instead of just creating a zero-length file [\#1690](https://github.com/jrnl-org/jrnl/pull/1690) ([micahellison](https://github.com/micahellison))
|
||||
- Allow combinations of `--change-time`, `--delete`, and `--edit` while correctly counting the number of entries affected [\#1669](https://github.com/jrnl-org/jrnl/pull/1669) ([wren](https://github.com/wren))
|
||||
- Don't save templated journal entries if the received raw text is the same as the template itself [\#1653](https://github.com/jrnl-org/jrnl/pull/1653) ([Briscoooe](https://github.com/Briscoooe))
|
||||
- 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:**
|
||||
|
||||
- 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:**
|
||||
|
||||
- Support pytest-bdd 6 [\#1534](https://github.com/jrnl-org/jrnl/issues/1534)
|
||||
- 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))
|
||||
|
@ -77,11 +93,8 @@
|
|||
|
||||
**Documentation:**
|
||||
|
||||
- Document template extension behavior [\#1677](https://github.com/jrnl-org/jrnl/issues/1677)
|
||||
- Visual Studio Code may store unencrypted temporary files [\#1675](https://github.com/jrnl-org/jrnl/issues/1675)
|
||||
- Document `-tagged`, `-not -tagged`, and `-not -starred` [\#1668](https://github.com/jrnl-org/jrnl/issues/1668)
|
||||
- Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651)
|
||||
- Update console examples on jrnl.sh front page [\#1622](https://github.com/jrnl-org/jrnl/issues/1622)
|
||||
- Update contributing.md links in documentation [\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking))
|
||||
- Fix various typos [\#1718](https://github.com/jrnl-org/jrnl/pull/1718) ([hezhizhen](https://github.com/hezhizhen))
|
||||
- Update documentation front page text [\#1698](https://github.com/jrnl-org/jrnl/pull/1698) ([micahellison](https://github.com/micahellison))
|
||||
- Support mkdocs 1.4.2 and fix its missing breadcrumb [\#1691](https://github.com/jrnl-org/jrnl/pull/1691) ([micahellison](https://github.com/micahellison))
|
||||
- Document temporary file extension behavior when using template [\#1686](https://github.com/jrnl-org/jrnl/pull/1686) ([micahellison](https://github.com/micahellison))
|
||||
|
@ -94,17 +107,18 @@
|
|||
|
||||
**Packaging:**
|
||||
|
||||
- Update dependency cryptography to v40 [\#1710](https://github.com/jrnl-org/jrnl/pull/1710) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency poethepoet to v0.19.0 [\#1709](https://github.com/jrnl-org/jrnl/pull/1709) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tzlocal to v4.3 [\#1708](https://github.com/jrnl-org/jrnl/pull/1708) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tox to v4.4.7 [\#1707](https://github.com/jrnl-org/jrnl/pull/1707) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13.3.2 [\#1706](https://github.com/jrnl-org/jrnl/pull/1706) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest-xdist to v3.2.1 [\#1705](https://github.com/jrnl-org/jrnl/pull/1705) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest to v7.2.2 [\#1704](https://github.com/jrnl-org/jrnl/pull/1704) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Lock ruamel.yaml version to v0.17.21 until bug is fixed [\#1738](https://github.com/jrnl-org/jrnl/pull/1738) ([wren](https://github.com/wren))
|
||||
- Update dependency black to v23.3.0 [\#1715](https://github.com/jrnl-org/jrnl/pull/1715) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency cryptography to v40.0.2 [\#1723](https://github.com/jrnl-org/jrnl/pull/1723) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flake8-type-checking to v2.4.0 [\#1714](https://github.com/jrnl-org/jrnl/pull/1714) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flakeheaven to v3.3.0 [\#1722](https://github.com/jrnl-org/jrnl/pull/1722) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency ipdb to v0.13.13 [\#1703](https://github.com/jrnl-org/jrnl/pull/1703) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flake8-type-checking to v2.3.1 [\#1702](https://github.com/jrnl-org/jrnl/pull/1702) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency cryptography to v39.0.2 [\#1701](https://github.com/jrnl-org/jrnl/pull/1701) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13 [\#1654](https://github.com/jrnl-org/jrnl/pull/1654) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency poethepoet to v0.19.0 [\#1709](https://github.com/jrnl-org/jrnl/pull/1709) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest to v7.3.1 [\#1720](https://github.com/jrnl-org/jrnl/pull/1720) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest-xdist to v3.2.1 [\#1705](https://github.com/jrnl-org/jrnl/pull/1705) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13.3.4 [\#1713](https://github.com/jrnl-org/jrnl/pull/1713) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tox to v4.4.7 [\#1707](https://github.com/jrnl-org/jrnl/pull/1707) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tzlocal to v4.3 [\#1708](https://github.com/jrnl-org/jrnl/pull/1708) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
|
||||
## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29)
|
||||
|
||||
|
@ -203,6 +217,10 @@
|
|||
|
||||
🚨 **BREAKING CHANGES** 🚨
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
- Drop support for Python 3.7 and 3.8 [\#1412](https://github.com/jrnl-org/jrnl/pull/1412) ([micahellison](https://github.com/micahellison))
|
||||
|
||||
**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))
|
||||
|
@ -225,10 +243,6 @@
|
|||
- Display "No entry to save, because no text was received" after empty entry on cmdline [\#1459](https://github.com/jrnl-org/jrnl/pull/1459) ([apainintheneck](https://github.com/apainintheneck))
|
||||
- Yaml export errors now don't show stack trace [\#1449](https://github.com/jrnl-org/jrnl/pull/1449) ([apainintheneck](https://github.com/apainintheneck))
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
- Drop support for Python 3.7 and 3.8 [\#1412](https://github.com/jrnl-org/jrnl/pull/1412) ([micahellison](https://github.com/micahellison))
|
||||
|
||||
**Build:**
|
||||
|
||||
- Pin `pytest-bdd` to \<6.0 to temporarily avoid breaking changes [\#1536](https://github.com/jrnl-org/jrnl/pull/1536) ([wren](https://github.com/wren))
|
||||
|
|
|
@ -117,6 +117,11 @@ These formats are mainly intended for piping or exporting your journal to other
|
|||
programs. Even so, they can still be used in the same way as any other format (like
|
||||
written to a file, or displayed in your terminal, if you want).
|
||||
|
||||
!!! note
|
||||
You may see boxed messages like "2 entries found" when using these formats, but
|
||||
those messages are written to `stderr` instead of `stdout`, and won't be piped when
|
||||
using the `|` operator.
|
||||
|
||||
### JSON
|
||||
|
||||
``` sh
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "v4.0-beta3"
|
||||
__version__ = "v4.0.1"
|
||||
|
|
27
jrnl/args.py
27
jrnl/args.py
|
@ -78,7 +78,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
"""
|
||||
We gratefully thank all contributors!
|
||||
Come see the whole list of code and financial contributors at https://github.com/jrnl-org/jrnl
|
||||
And special thanks to Bad Lip Reading for the Yoda joke in the Writing section above :)"""
|
||||
And special thanks to Bad Lip Reading for the Yoda joke in the Writing section above :)""" # noqa: E501
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -214,7 +214,8 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
composing.add_argument(
|
||||
"--template",
|
||||
dest="template",
|
||||
help="Path to template file. Can be a local path, absolute path, or a path relative to $XDG_DATA_HOME/jrnl/templates/",
|
||||
help="Path to template file. Can be a local path, absolute path, or a path "
|
||||
"relative to $XDG_DATA_HOME/jrnl/templates/",
|
||||
)
|
||||
|
||||
read_msg = (
|
||||
|
@ -265,13 +266,15 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
"-contains",
|
||||
dest="contains",
|
||||
metavar="TEXT",
|
||||
help="Show entries containing specific text (put quotes around text with spaces)",
|
||||
help="Show entries containing specific text (put quotes around text with "
|
||||
"spaces)",
|
||||
)
|
||||
reading.add_argument(
|
||||
"-and",
|
||||
dest="strict",
|
||||
action="store_true",
|
||||
help='Show only entries that match all conditions, like saying "x AND y" (default: OR)',
|
||||
help='Show only entries that match all conditions, like saying "x AND y" '
|
||||
"(default: OR)",
|
||||
)
|
||||
reading.add_argument(
|
||||
"-starred",
|
||||
|
@ -290,7 +293,8 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
dest="limit",
|
||||
default=None,
|
||||
metavar="NUMBER",
|
||||
help="Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same effect)",
|
||||
help="Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same "
|
||||
"effect)",
|
||||
nargs="?",
|
||||
type=int,
|
||||
)
|
||||
|
@ -308,8 +312,12 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
),
|
||||
)
|
||||
|
||||
search_options_msg = """ These help you do various tasks with the selected entries from your search.
|
||||
search_options_msg = (
|
||||
" " # Preserves indentation
|
||||
"""
|
||||
These help you do various tasks with the selected entries from your search.
|
||||
If used on their own (with no search), they will act on your entire journal"""
|
||||
)
|
||||
exporting = parser.add_argument_group(
|
||||
"Searching Options", textwrap.dedent(search_options_msg)
|
||||
)
|
||||
|
@ -360,7 +368,8 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
"--tags",
|
||||
dest="tags",
|
||||
action="store_true",
|
||||
help="Alias for '--format tags'. Returns a list of all tags and number of occurrences",
|
||||
help="Alias for '--format tags'. Returns a list of all tags and number of "
|
||||
"occurrences",
|
||||
)
|
||||
exporting.add_argument(
|
||||
"--short",
|
||||
|
@ -400,7 +409,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
\t jrnl --config-override editor "nano" \n
|
||||
\t - Override color selections\n
|
||||
\t jrnl --config-override colors.body blue --config-override colors.title green
|
||||
""",
|
||||
""", # noqa: E501
|
||||
)
|
||||
config_overrides.add_argument(
|
||||
"--co",
|
||||
|
@ -430,7 +439,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
\t jrnl --config-file /home/user1/work_config.yaml
|
||||
\t - Use a personal config file stored on a thumb drive: \n
|
||||
\t jrnl --config-file /media/user1/my-thumb-drive/personal_config.yaml
|
||||
""",
|
||||
""", # noqa: E501
|
||||
)
|
||||
|
||||
alternate_config.add_argument(
|
||||
|
|
|
@ -142,7 +142,7 @@ def postconfig_encrypt(
|
|||
def postconfig_decrypt(
|
||||
args: argparse.Namespace, config: dict, original_config: dict
|
||||
) -> int:
|
||||
"""Decrypts into new file. If filename is not set, we encrypt the journal file itself."""
|
||||
"""Decrypts to 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.journals import open_journal
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
import argparse
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
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
|
||||
|
@ -21,13 +19,10 @@ from jrnl.messages import MsgStyle
|
|||
from jrnl.messages import MsgText
|
||||
from jrnl.output import list_journals
|
||||
from jrnl.output import print_msg
|
||||
from jrnl.path import home_dir
|
||||
from jrnl.path import get_config_path
|
||||
from jrnl.path import get_default_journal_path
|
||||
|
||||
# Constants
|
||||
DEFAULT_CONFIG_NAME = "jrnl.yaml"
|
||||
XDG_RESOURCE = "jrnl"
|
||||
|
||||
DEFAULT_JOURNAL_NAME = "journal.txt"
|
||||
DEFAULT_JOURNAL_KEY = "default"
|
||||
|
||||
YAML_SEPARATOR = ": "
|
||||
|
@ -42,9 +37,10 @@ def make_yaml_valid_dict(input: list) -> dict:
|
|||
The dict is created through the yaml loader, with the assumption that
|
||||
"input[0]: input[1]" is valid yaml.
|
||||
|
||||
:param input: list of configuration keys in dot-notation and their respective values.
|
||||
:param input: list of configuration keys in dot-notation and their respective values
|
||||
:type input: list
|
||||
:return: A single level dict of the configuration keys in dot-notation and their respective desired values
|
||||
:return: A single level dict of the configuration keys in dot-notation and their
|
||||
respective desired values
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
|
@ -73,31 +69,6 @@ def save_config(config: dict, alt_config_path: str | None = None) -> None:
|
|||
yaml.dump(config, f)
|
||||
|
||||
|
||||
def get_config_directory() -> str:
|
||||
try:
|
||||
return xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
|
||||
except FileExistsError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.ConfigDirectoryIsFile,
|
||||
MsgStyle.ERROR,
|
||||
{
|
||||
"config_directory_path": os.path.join(
|
||||
xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_config_path() -> Path:
|
||||
try:
|
||||
config_directory_path = get_config_directory()
|
||||
except JrnlException:
|
||||
return Path(home_dir(), DEFAULT_CONFIG_NAME)
|
||||
return Path(config_directory_path, DEFAULT_CONFIG_NAME)
|
||||
|
||||
|
||||
def get_default_config() -> dict[str, Any]:
|
||||
return {
|
||||
"version": __version__,
|
||||
|
@ -130,20 +101,6 @@ def get_default_colors() -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
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 get_templates_path() -> Path:
|
||||
# jrnl_xdg_resource_path is created by save_data_path if it does not exist
|
||||
jrnl_xdg_resource_path = Path(xdg.BaseDirectory.save_data_path(XDG_RESOURCE))
|
||||
jrnl_templates_path = jrnl_xdg_resource_path / "templates"
|
||||
# Create the directory if needed.
|
||||
jrnl_templates_path.mkdir(exist_ok=True)
|
||||
return jrnl_templates_path
|
||||
|
||||
|
||||
def scope_config(config: dict, journal_name: str) -> dict:
|
||||
if journal_name not in config["journals"]:
|
||||
return config
|
||||
|
|
|
@ -11,10 +11,10 @@ from jrnl import time
|
|||
from jrnl.config import DEFAULT_JOURNAL_KEY
|
||||
from jrnl.config import get_config_path
|
||||
from jrnl.config import get_journal_name
|
||||
from jrnl.config import get_templates_path
|
||||
from jrnl.config import scope_config
|
||||
from jrnl.editor import get_text_from_editor
|
||||
from jrnl.editor import get_text_from_stdin
|
||||
from jrnl.editor import read_template_file
|
||||
from jrnl.exception import JrnlException
|
||||
from jrnl.journals import open_journal
|
||||
from jrnl.messages import Message
|
||||
|
@ -23,7 +23,6 @@ from jrnl.messages import MsgText
|
|||
from jrnl.output import print_msg
|
||||
from jrnl.output import print_msgs
|
||||
from jrnl.override import apply_overrides
|
||||
from jrnl.path import absolute_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Namespace
|
||||
|
@ -35,9 +34,9 @@ if TYPE_CHECKING:
|
|||
def run(args: "Namespace"):
|
||||
"""
|
||||
Flow:
|
||||
1. Run standalone command if it doesn't require config (help, version, etc), then exit
|
||||
1. Run standalone command if it doesn't need config (help, version, etc), then exit
|
||||
2. Load config
|
||||
3. Run standalone command if it does require config (encrypt, decrypt, etc), then exit
|
||||
3. Run standalone command if it does need config (encrypt, decrypt, etc), then exit
|
||||
4. Load specified journal
|
||||
5. Start append mode, or search mode
|
||||
6. Perform actions with results from search mode (if needed)
|
||||
|
@ -130,74 +129,6 @@ def _is_append_mode(args: "Namespace", config: dict, **kwargs) -> bool:
|
|||
return append_mode
|
||||
|
||||
|
||||
def _read_template_file(template_arg: str, template_path_from_config: str) -> str:
|
||||
"""
|
||||
This function is called when either a template file is passed with --template, or config.template is set.
|
||||
|
||||
The processing logic is:
|
||||
If --template was not used: Load the global template file.
|
||||
If --template was used:
|
||||
* Check $XDG_DATA_HOME/jrnl/templates/template_arg.
|
||||
* Check template_arg as an absolute / relative path.
|
||||
|
||||
If a file is found, its contents are returned as a string.
|
||||
If not, a JrnlException is raised.
|
||||
"""
|
||||
logging.debug(
|
||||
"Append mode: Either a template arg was passed, or the global config is set."
|
||||
)
|
||||
|
||||
# If filename is unset, we are in this flow due to a global template being configured
|
||||
if not template_arg:
|
||||
logging.debug("Append mode: Global template configuration detected.")
|
||||
global_template_path = absolute_path(template_path_from_config)
|
||||
try:
|
||||
with open(global_template_path, encoding="utf-8") as f:
|
||||
template_data = f.read()
|
||||
return template_data
|
||||
except FileNotFoundError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.CantReadTemplateGlobalConfig,
|
||||
MsgStyle.ERROR,
|
||||
{
|
||||
"global_template_path": global_template_path,
|
||||
},
|
||||
)
|
||||
)
|
||||
else: # A template CLI arg was passed.
|
||||
logging.debug("Trying to load template from $XDG_DATA_HOME/jrnl/templates/")
|
||||
jrnl_template_dir = get_templates_path()
|
||||
logging.debug(f"Append mode: jrnl templates directory: {jrnl_template_dir}")
|
||||
template_path = jrnl_template_dir / template_arg
|
||||
try:
|
||||
with open(template_path, encoding="utf-8") as f:
|
||||
template_data = f.read()
|
||||
return template_data
|
||||
except FileNotFoundError:
|
||||
logging.debug(
|
||||
f"Couldn't open {template_path}. Treating --template argument like a local / abs path."
|
||||
)
|
||||
pass
|
||||
|
||||
normalized_template_arg_filepath = absolute_path(template_arg)
|
||||
try:
|
||||
with open(normalized_template_arg_filepath, encoding="utf-8") as f:
|
||||
template_data = f.read()
|
||||
return template_data
|
||||
except FileNotFoundError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.CantReadTemplateCLIArg,
|
||||
MsgStyle.ERROR,
|
||||
{
|
||||
"normalized_template_arg_filepath": normalized_template_arg_filepath,
|
||||
"jrnl_template_dir": template_path,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def append_mode(args: "Namespace", config: dict, journal: "Journal", **kwargs) -> None:
|
||||
"""
|
||||
Gets input from the user to write to the journal
|
||||
|
@ -210,26 +141,22 @@ def append_mode(args: "Namespace", config: dict, journal: "Journal", **kwargs) -
|
|||
"""
|
||||
logging.debug("Append mode: starting")
|
||||
|
||||
if args.template or config["template"]:
|
||||
logging.debug(f"Append mode: template CLI arg detected: {args.template}")
|
||||
# Read template file and pass as raw text into the composer
|
||||
template_data = _read_template_file(args.template, config["template"])
|
||||
raw = _write_in_editor(config, template_data)
|
||||
if raw == template_data:
|
||||
logging.error("Append mode: raw text was the same as the template")
|
||||
raise JrnlException(Message(MsgText.NoChangesToTemplate, MsgStyle.NORMAL))
|
||||
elif args.text:
|
||||
template_text = _get_template(args, config)
|
||||
|
||||
if args.text:
|
||||
logging.debug(f"Append mode: cli text detected: {args.text}")
|
||||
raw = " ".join(args.text).strip()
|
||||
if args.edit:
|
||||
raw = _write_in_editor(config, raw)
|
||||
|
||||
elif not sys.stdin.isatty():
|
||||
logging.debug("Append mode: receiving piped text")
|
||||
raw = sys.stdin.read()
|
||||
|
||||
else:
|
||||
raw = _write_in_editor(config)
|
||||
raw = _write_in_editor(config, template_text)
|
||||
|
||||
if template_text is not None and raw == template_text:
|
||||
logging.error("Append mode: raw text was the same as the template")
|
||||
raise JrnlException(Message(MsgText.NoChangesToTemplate, MsgStyle.NORMAL))
|
||||
|
||||
if not raw or raw.isspace():
|
||||
logging.error("Append mode: couldn't get raw text or entry was empty")
|
||||
|
@ -251,6 +178,23 @@ def append_mode(args: "Namespace", config: dict, journal: "Journal", **kwargs) -
|
|||
logging.debug("Append mode: completed journal.write()")
|
||||
|
||||
|
||||
def _get_template(args, config) -> str:
|
||||
# Read template file and pass as raw text into the composer
|
||||
logging.debug(
|
||||
"Get template:\n"
|
||||
f"--template: {args.template}\n"
|
||||
f"from config: {config.get('template')}"
|
||||
)
|
||||
template_path = args.template or config.get("template")
|
||||
|
||||
template_text = None
|
||||
|
||||
if template_path:
|
||||
template_text = read_template_file(template_path)
|
||||
|
||||
return template_text
|
||||
|
||||
|
||||
def search_mode(args: "Namespace", journal: "Journal", **kwargs) -> None:
|
||||
"""
|
||||
Search for entries in a journal, and return the
|
||||
|
|
|
@ -15,6 +15,8 @@ from jrnl.messages import MsgText
|
|||
from jrnl.os_compat import on_windows
|
||||
from jrnl.os_compat import split_args
|
||||
from jrnl.output import print_msg
|
||||
from jrnl.path import absolute_path
|
||||
from jrnl.path import get_templates_path
|
||||
|
||||
|
||||
def get_text_from_editor(config: dict, template: str = "") -> str:
|
||||
|
@ -73,3 +75,47 @@ def get_text_from_stdin() -> str:
|
|||
)
|
||||
|
||||
return raw
|
||||
|
||||
|
||||
def get_template_path(template_path: str, jrnl_template_dir: str) -> str:
|
||||
actual_template_path = os.path.join(jrnl_template_dir, template_path)
|
||||
if not os.path.exists(actual_template_path):
|
||||
logging.debug(
|
||||
f"Couldn't open {actual_template_path}. "
|
||||
"Treating template path like a local / abs path."
|
||||
)
|
||||
actual_template_path = absolute_path(template_path)
|
||||
|
||||
return actual_template_path
|
||||
|
||||
|
||||
def read_template_file(template_path: str) -> str:
|
||||
"""
|
||||
Reads the template file given a template path in this order:
|
||||
|
||||
* Check $XDG_DATA_HOME/jrnl/templates/template_path.
|
||||
* Check template_arg as an absolute / relative path.
|
||||
|
||||
If a file is found, its contents are returned as a string.
|
||||
If not, a JrnlException is raised.
|
||||
"""
|
||||
|
||||
jrnl_template_dir = get_templates_path()
|
||||
actual_template_path = get_template_path(template_path, jrnl_template_dir)
|
||||
|
||||
try:
|
||||
with open(actual_template_path, encoding="utf-8") as f:
|
||||
template_data = f.read()
|
||||
return template_data
|
||||
except FileNotFoundError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.CantReadTemplate,
|
||||
MsgStyle.ERROR,
|
||||
{
|
||||
"template_path": template_path,
|
||||
"actual_template_path": actual_template_path,
|
||||
"jrnl_template_dir": str(jrnl_template_dir) + os.sep,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
|
|
@ -31,11 +31,11 @@ from jrnl.upgrade import is_old_version
|
|||
|
||||
|
||||
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.
|
||||
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.
|
||||
"""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)
|
||||
|
@ -167,7 +167,7 @@ def install() -> dict:
|
|||
|
||||
|
||||
def _initialize_autocomplete() -> None:
|
||||
# readline is not included in Windows Active Python and perhaps some other distributions
|
||||
# readline is not included in Windows Active Python and perhaps some other distss
|
||||
if sys.modules.get("readline"):
|
||||
import readline
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ class Entry:
|
|||
}
|
||||
|
||||
def __str__(self):
|
||||
"""Returns a string representation of the entry to be written into a journal file."""
|
||||
"""Returns string representation of the entry to be written to journal file."""
|
||||
date_str = self.date.strftime(self.journal.config["timeformat"])
|
||||
title = "[{}] {}".format(date_str, self.title.rstrip("\n "))
|
||||
if self.starred:
|
||||
|
@ -214,7 +214,7 @@ SENTENCE_SPLITTER = re.compile(
|
|||
\s+ # AND a sequence of required spaces.
|
||||
)
|
||||
|[\uFF01\uFF0E\uFF1F\uFF61\u3002] # CJK full/half width terminals usually do not have following spaces.
|
||||
""",
|
||||
""", # noqa: E501
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
|
|
|
@ -122,7 +122,8 @@ class Folder(Journal):
|
|||
|
||||
@staticmethod
|
||||
def _get_files(journal_path: str) -> list[str]:
|
||||
"""Searches through sub directories starting with journal_path and find all text files that look like entries"""
|
||||
"""Searches through sub directories starting with journal_path and find all text
|
||||
files that look like entries"""
|
||||
for year_folder in Folder._get_year_folders(pathlib.Path(journal_path)):
|
||||
for month_folder in Folder._get_month_folders(year_folder):
|
||||
yield from Folder._get_day_files(month_folder)
|
||||
|
|
|
@ -102,7 +102,7 @@ class Journal:
|
|||
return self.encryption_method.encrypt(text)
|
||||
|
||||
def open(self, filename: str | None = None) -> "Journal":
|
||||
"""Opens the journal file defined in the config and parses it into a list of Entries.
|
||||
"""Opens the journal file 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)
|
||||
|
@ -144,7 +144,7 @@ class Journal:
|
|||
self._store(filename, text)
|
||||
|
||||
def validate_parsing(self) -> bool:
|
||||
"""Confirms that the jrnl is still parsed correctly after being dumped to text."""
|
||||
"""Confirms that the jrnl is still parsed correctly after conversion to text."""
|
||||
new_entries = self._parse(self._to_text())
|
||||
return all(entry == new_entries[i] for i, entry in enumerate(self.entries))
|
||||
|
||||
|
@ -225,8 +225,9 @@ class Journal:
|
|||
@property
|
||||
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.
|
||||
# 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.
|
||||
tags = [tag for entry in self.entries for tag in set(entry.tags)]
|
||||
# To be read: [for entry in journal.entries: for tag in set(entry.tags): tag]
|
||||
tag_counts = {(tags.count(tag), tag) for tag in tags}
|
||||
|
@ -343,7 +344,8 @@ class Journal:
|
|||
|
||||
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.
|
||||
If a date is given, it will parse and use this, otherwise scan for a date in
|
||||
the input first.
|
||||
"""
|
||||
|
||||
raw = raw.replace("\\n ", "\n").replace("\\n", "\n")
|
||||
|
|
|
@ -90,4 +90,4 @@ class MsgStyle(Enum):
|
|||
|
||||
@property
|
||||
def box_title(self) -> MsgText:
|
||||
return self.value.get("box_title", None)
|
||||
return self.value.get("box_title")
|
||||
|
|
|
@ -43,8 +43,8 @@ class MsgText(Enum):
|
|||
Do you want to encrypt your journal? (You can always change this later)
|
||||
"""
|
||||
UseColorsQuestion = """
|
||||
Do you want jrnl to use colors when displaying entries? (You can always change this later)
|
||||
"""
|
||||
Do you want jrnl to use colors to display entries? (You can always change this later)
|
||||
""" # noqa: E501 - the line is still under 88 when dedented
|
||||
YesOrNoPromptDefaultYes = "[Y/n]"
|
||||
YesOrNoPromptDefaultNo = "[y/N]"
|
||||
ContinueUpgrade = "Continue upgrading jrnl?"
|
||||
|
@ -105,16 +105,12 @@ class MsgText(Enum):
|
|||
|
||||
KeyboardInterruptMsg = "Aborted by user"
|
||||
|
||||
CantReadTemplateGlobalConfig = """
|
||||
Could not read template file defined in config:
|
||||
{global_template_path}
|
||||
"""
|
||||
CantReadTemplate = """
|
||||
Unable to find a template file {template_path}.
|
||||
|
||||
CantReadTemplateCLIArg = """
|
||||
Unable to find a template file based on the passed arg, and no global template was detected.
|
||||
The following filepaths were checked:
|
||||
jrnl XDG Template Directory : {jrnl_template_dir}
|
||||
Local Filepath : {normalized_template_arg_filepath}
|
||||
The following paths were checked:
|
||||
* {jrnl_template_dir}{template_path}
|
||||
* {actual_template_path}
|
||||
"""
|
||||
|
||||
NoNamedJournal = "No '{journal_name}' journal configured\n{journals}"
|
||||
|
|
|
@ -39,7 +39,10 @@ def journal_list_to_yaml(journal_list: dict) -> str:
|
|||
from ruamel.yaml import YAML
|
||||
|
||||
output = StringIO()
|
||||
YAML().dump(journal_list, output)
|
||||
dumper = YAML()
|
||||
dumper.width = 1000
|
||||
dumper.dump(journal_list, output)
|
||||
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ def apply_overrides(args: "Namespace", base_config: dict) -> dict:
|
|||
:return: Configuration to be used during runtime with the overrides applied
|
||||
:rtype: dict
|
||||
"""
|
||||
overrides = vars(args).get("config_override", None)
|
||||
overrides = vars(args).get("config_override")
|
||||
if not overrides:
|
||||
return base_config
|
||||
|
||||
|
@ -56,7 +56,8 @@ def _recursively_apply(tree: dict, nodes: list, override_value) -> dict:
|
|||
|
||||
Args:
|
||||
config (dict): Configuration to modify
|
||||
nodes (list): Vector of override keys; the length of the vector indicates tree depth
|
||||
nodes (list): Vector of override keys; the length of the vector indicates tree
|
||||
depth
|
||||
override_value (str): Runtime override passed from the command-line
|
||||
"""
|
||||
key = nodes[0]
|
||||
|
|
56
jrnl/path.py
56
jrnl/path.py
|
@ -2,6 +2,19 @@
|
|||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
|
||||
import xdg.BaseDirectory
|
||||
|
||||
from jrnl.exception import JrnlException
|
||||
from jrnl.messages import Message
|
||||
from jrnl.messages import MsgStyle
|
||||
from jrnl.messages import MsgText
|
||||
|
||||
# Constants
|
||||
XDG_RESOURCE = "jrnl"
|
||||
DEFAULT_CONFIG_NAME = "jrnl.yaml"
|
||||
DEFAULT_JOURNAL_NAME = "journal.txt"
|
||||
|
||||
|
||||
def home_dir() -> str:
|
||||
|
@ -14,3 +27,46 @@ def expand_path(path: str) -> str:
|
|||
|
||||
def absolute_path(path: str) -> str:
|
||||
return os.path.abspath(expand_path(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 get_templates_path() -> str:
|
||||
"""
|
||||
Get the path to the XDG templates directory. Creates the directory if it
|
||||
doesn't exist.
|
||||
"""
|
||||
# jrnl_xdg_resource_path is created by save_data_path if it does not exist
|
||||
jrnl_xdg_resource_path = Path(xdg.BaseDirectory.save_data_path(XDG_RESOURCE))
|
||||
jrnl_templates_path = jrnl_xdg_resource_path / "templates"
|
||||
# Create the directory if needed.
|
||||
jrnl_templates_path.mkdir(exist_ok=True)
|
||||
return str(jrnl_templates_path)
|
||||
|
||||
|
||||
def get_config_directory() -> str:
|
||||
try:
|
||||
return xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
|
||||
except FileExistsError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.ConfigDirectoryIsFile,
|
||||
MsgStyle.ERROR,
|
||||
{
|
||||
"config_directory_path": os.path.join(
|
||||
xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_config_path() -> str:
|
||||
try:
|
||||
config_directory_path = get_config_directory()
|
||||
except JrnlException:
|
||||
return os.path.join(home_dir(), DEFAULT_CONFIG_NAME)
|
||||
return os.path.join(config_directory_path, DEFAULT_CONFIG_NAME)
|
||||
|
|
|
@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class FancyExporter(TextExporter):
|
||||
"""This Exporter can convert entries and journals into text with unicode box drawing characters."""
|
||||
"""This Exporter converts entries and journals into text with unicode boxes."""
|
||||
|
||||
names = ["fancy", "boxed"]
|
||||
extension = "txt"
|
||||
|
|
|
@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class TagExporter(TextExporter):
|
||||
"""This Exporter can lists the tags for entries and journals, exported as a plain text file."""
|
||||
"""This Exporter lists the tags for entries and journals."""
|
||||
|
||||
names = ["tags"]
|
||||
extension = "tags"
|
||||
|
|
|
@ -10,7 +10,8 @@ if TYPE_CHECKING:
|
|||
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.
|
||||
# I came across this construction, worry not and embrace the ensuing moment of
|
||||
# enlightment.
|
||||
tags = [tag for entry in journal.entries for tag in set(entry.tags)]
|
||||
# To be read: [for entry in journal.entries: for tag in set(entry.tags): tag]
|
||||
tag_counts = {(tags.count(tag), tag) for tag in tags}
|
||||
|
|
|
@ -18,14 +18,15 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class YAMLExporter(TextExporter):
|
||||
"""This Exporter can convert entries and journals into Markdown formatted text with YAML front matter."""
|
||||
"""This Exporter converts entries and journals into Markdown formatted text with
|
||||
YAML front matter."""
|
||||
|
||||
names = ["yaml"]
|
||||
extension = "md"
|
||||
|
||||
@classmethod
|
||||
def export_entry(cls, entry: "Entry", to_multifile: bool = True) -> str:
|
||||
"""Returns a markdown representation of a single entry, with YAML front matter."""
|
||||
"""Returns a markdown representation of an entry, with YAML front matter."""
|
||||
if to_multifile is False:
|
||||
raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR))
|
||||
|
||||
|
@ -117,7 +118,14 @@ class YAMLExporter(TextExporter):
|
|||
# source directory is entry.journal.config['journal']
|
||||
# output directory is...?
|
||||
|
||||
return "{start}\ntitle: {title}\ndate: {date}\nstarred: {starred}\ntags: {tags}\n{dayone}body: |{body}{end}".format(
|
||||
return (
|
||||
"{start}\n"
|
||||
"title: {title}\n"
|
||||
"date: {date}\n"
|
||||
"starred: {starred}\n"
|
||||
"tags: {tags}\n"
|
||||
"{dayone}body: |{body}{end}"
|
||||
).format(
|
||||
start="---",
|
||||
date=date_str,
|
||||
title=entry.title,
|
||||
|
|
10
jrnl/time.py
10
jrnl/time.py
|
@ -34,8 +34,8 @@ def parse(
|
|||
elif isinstance(date_str, datetime.datetime):
|
||||
return date_str
|
||||
|
||||
# Don't try to parse anything with 6 or fewer characters and was parsed from the existing journal.
|
||||
# It's probably a markdown footnote
|
||||
# Don't try to parse anything with 6 or fewer characters and was parsed from the
|
||||
# existing journal. It's probably a markdown footnote
|
||||
if len(date_str) <= 6 and bracketed:
|
||||
return None
|
||||
|
||||
|
@ -82,9 +82,9 @@ def parse(
|
|||
else:
|
||||
date = datetime.datetime(*date[:6])
|
||||
|
||||
# Ugly heuristic: if the date is more than 4 weeks in the future, we got the year wrong.
|
||||
# Rather than this, we would like to see parsedatetime patched so we can tell it to prefer
|
||||
# past dates
|
||||
# Ugly heuristic: if the date is more than 4 weeks in the future, we got the year
|
||||
# wrong. Rather than this, we would like to see parsedatetime patched so we can
|
||||
# tell it to prefer past dates
|
||||
dt = datetime.datetime.now() - date
|
||||
if dt.days < -28 and not year_present:
|
||||
date = date.replace(date.year - 1)
|
||||
|
|
540
poetry.lock
generated
540
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "jrnl"
|
||||
version = "v4.0-beta3"
|
||||
version = "v4.0.1"
|
||||
description = "Collect your thoughts and notes without leaving the command line."
|
||||
authors = [
|
||||
"jrnl contributors <maintainers@jrnl.sh>",
|
||||
|
@ -35,7 +35,7 @@ keyring = ">=21.0" # https://github.com/jaraco/keyring#integration
|
|||
parsedatetime = ">=2.6"
|
||||
python-dateutil = "^2.8" # https://github.com/dateutil/dateutil/blob/master/RELEASING
|
||||
pyxdg = ">=0.27.0"
|
||||
"ruamel.yaml" = "^0.17.21"
|
||||
"ruamel.yaml" = ">=0.17.22"
|
||||
rich = ">=12.2.0, <14.0.0"
|
||||
|
||||
# dayone-only deps
|
||||
|
@ -43,13 +43,7 @@ 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"
|
||||
flake8-type-checking = ">=2.2.0"
|
||||
flake8-simplify = ">=0.19"
|
||||
ipdb = "*"
|
||||
isort = ">=5.10"
|
||||
mkdocs = ">=1.4"
|
||||
parse-type = ">=0.6.0"
|
||||
poethepoet = "*"
|
||||
|
@ -58,6 +52,7 @@ pytest-bdd = ">=6.0"
|
|||
pytest-clarity = "*"
|
||||
pytest-xdist = ">=2.5.0"
|
||||
requests = "*"
|
||||
ruff = ">=0.0.276"
|
||||
toml = ">=0.10"
|
||||
tox = "*"
|
||||
xmltodict = "*"
|
||||
|
@ -87,18 +82,18 @@ test-run = [
|
|||
# Groups of tasks
|
||||
format.default_item_type = "cmd"
|
||||
format.sequence = [
|
||||
"isort .",
|
||||
"ruff check . --select I --fix", # equivalent to "isort ."
|
||||
"black .",
|
||||
]
|
||||
|
||||
lint.env = { FLAKEHEAVEN_CACHE_TIMEOUT = "0" }
|
||||
lint.default_item_type = "cmd"
|
||||
lint.sequence = [
|
||||
"poetry --version",
|
||||
"poetry check",
|
||||
"flakeheaven --version",
|
||||
"flakeheaven plugins",
|
||||
"flakeheaven lint",
|
||||
"ruff --version",
|
||||
"ruff .",
|
||||
"black --version",
|
||||
"black --check ."
|
||||
]
|
||||
|
||||
test = [
|
||||
|
@ -106,11 +101,6 @@ test = [
|
|||
"test-run",
|
||||
]
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
force_single_line = true
|
||||
known_first_party = ["jrnl", "tests"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "6.0"
|
||||
required_plugins = [
|
||||
|
@ -136,29 +126,35 @@ filterwarnings = [
|
|||
"ignore:[WinError 5].*"
|
||||
]
|
||||
|
||||
[tool.flakeheaven]
|
||||
max_line_length = 88
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
# https://beta.ruff.rs/docs/rules/
|
||||
select = [
|
||||
'F', # Pyflakes
|
||||
'E', # pycodestyle errors
|
||||
'W', # pycodestyle warnings
|
||||
'I', # isort
|
||||
'ASYNC', # flake8-async
|
||||
'S110', # try-except-pass
|
||||
'S112', # try-except-continue
|
||||
'EM', # flake8-errmsg
|
||||
'ISC', # flake8-implicit-str-concat
|
||||
'Q', # flake8-quotes
|
||||
'RSE', # flake8-raise
|
||||
'TID', # flake8-tidy-imports
|
||||
'TCH', # flake8-type-checking
|
||||
'T100', # debugger, don't allow break points
|
||||
'ICN' # flake8-import-conventions
|
||||
]
|
||||
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*",
|
||||
]
|
||||
"flake8-*" = ["+*"]
|
||||
flake8-black = ["-BLK901"]
|
||||
[tool.ruff.isort]
|
||||
force-single-line = true
|
||||
known-first-party = ["jrnl", "tests"]
|
||||
|
||||
[tool.flakeheaven.exceptions."jrnl/journals/__init__.py"]
|
||||
pyflakes = ["-F401"]
|
||||
[tool.ruff.per-file-ignores]
|
||||
"__init__.py" = ["F401"] # unused imports
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
|
|
@ -38,7 +38,7 @@ Feature: Using templates
|
|||
And we use the password "test" if prompted
|
||||
When we run "jrnl --template this_template_does_not_exist.template"
|
||||
Then we should get an error
|
||||
Then the error output should contain "Unable to find a template file based on the passed arg"
|
||||
Then the error output should contain "Unable to find a template file"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
|
|
@ -183,10 +183,10 @@ def mock_default_journal_path(temp_dir):
|
|||
|
||||
@fixture
|
||||
def mock_default_templates_path(temp_dir):
|
||||
templates_path = Path(temp_dir.name, "templates")
|
||||
templates_path = os.path.join(temp_dir.name, "templates")
|
||||
return {
|
||||
"get_templates_path": lambda: patch(
|
||||
"jrnl.controller.get_templates_path", return_value=templates_path
|
||||
"jrnl.editor.get_templates_path", return_value=templates_path
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
@ -38,38 +38,41 @@ def output_should_match(regex, cli_run):
|
|||
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}"
|
||||
|
||||
|
||||
@then(parse("the output {it_should:Should} contain\n{expected_output}", SHOULD_DICT))
|
||||
@then(parse('the output {it_should:Should} contain "{expected_output}"', SHOULD_DICT))
|
||||
@then(parse("the output {it_should:Should} contain\n{expected}", SHOULD_DICT))
|
||||
@then(parse('the output {it_should:Should} contain "{expected}"', SHOULD_DICT))
|
||||
@then(
|
||||
parse(
|
||||
"the {which_output_stream} output {it_should:Should} contain\n{expected_output}",
|
||||
"the {which_output_stream} output {it_should:Should} contain\n{expected}",
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(
|
||||
parse(
|
||||
'the {which_output_stream} output {it_should:Should} contain "{expected_output}"',
|
||||
'the {which_output_stream} output {it_should:Should} contain "{expected}"',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
def output_should_contain(expected_output, which_output_stream, cli_run, it_should):
|
||||
output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}"
|
||||
assert expected_output
|
||||
def output_should_contain(expected, which_output_stream, cli_run, it_should):
|
||||
output_str = (
|
||||
f"\nEXPECTED:\n{expected}\n\n"
|
||||
f"ACTUAL STDOUT:\n{cli_run['stdout']}\n\n"
|
||||
f"ACTUAL STDERR:\n{cli_run['stderr']}"
|
||||
)
|
||||
|
||||
assert expected
|
||||
if which_output_stream is None:
|
||||
assert ((expected_output in cli_run["stdout"]) == it_should) or (
|
||||
(expected_output in cli_run["stderr"]) == it_should
|
||||
assert ((expected in cli_run["stdout"]) == it_should) or (
|
||||
(expected in cli_run["stderr"]) == it_should
|
||||
), output_str
|
||||
|
||||
elif which_output_stream == "standard":
|
||||
assert (expected_output in cli_run["stdout"]) == it_should, output_str
|
||||
assert (expected in cli_run["stdout"]) == it_should, output_str
|
||||
|
||||
elif which_output_stream == "error":
|
||||
assert (expected_output in cli_run["stderr"]) == it_should, output_str
|
||||
assert (expected in cli_run["stderr"]) == it_should, output_str
|
||||
|
||||
else:
|
||||
assert (
|
||||
expected_output in cli_run[which_output_stream]
|
||||
) == it_should, output_str
|
||||
assert (expected in cli_run[which_output_stream]) == it_should, output_str
|
||||
|
||||
|
||||
@then(parse("the output should not contain\n{expected_output}"))
|
||||
|
@ -119,7 +122,8 @@ def output_should_be_columns_wide(cli_run, width):
|
|||
|
||||
@then(
|
||||
parse(
|
||||
'the default journal "{journal_file}" should be in the "{journal_dir}" directory'
|
||||
'the default journal "{journal_file}" '
|
||||
'should be in the "{journal_dir}" directory'
|
||||
)
|
||||
)
|
||||
def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir):
|
||||
|
@ -135,13 +139,15 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir
|
|||
|
||||
@then(
|
||||
parse(
|
||||
'the config for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
|
||||
'the config for journal "{journal_name}" '
|
||||
'{it_should:Should} contain "{some_yaml}"',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(
|
||||
parse(
|
||||
'the config for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
|
||||
'the config for journal "{journal_name}" '
|
||||
"{it_should:Should} contain\n{some_yaml}",
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
|
@ -157,20 +163,22 @@ def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
|
|||
actual_slice = actual
|
||||
if type(actual) is dict:
|
||||
# `expected` objects formatted in yaml only compare one level deep
|
||||
actual_slice = {key: actual.get(key, None) for key in expected.keys()}
|
||||
actual_slice = {key: actual.get(key) for key in expected.keys()}
|
||||
|
||||
assert (expected == actual_slice) == it_should
|
||||
|
||||
|
||||
@then(
|
||||
parse(
|
||||
'the config in memory for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
|
||||
'the config in memory for journal "{journal_name}" '
|
||||
'{it_should:Should} contain "{some_yaml}"',
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
@then(
|
||||
parse(
|
||||
'the config in memory for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
|
||||
'the config in memory for journal "{journal_name}" '
|
||||
"{it_should:Should} contain\n{some_yaml}",
|
||||
SHOULD_DICT,
|
||||
)
|
||||
)
|
||||
|
|
45
tests/unit/test_editor.py
Normal file
45
tests/unit/test_editor.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Copyright © 2012-2023 jrnl contributors
|
||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
import os
|
||||
from unittest.mock import mock_open
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from jrnl.editor import get_template_path
|
||||
from jrnl.editor import read_template_file
|
||||
from jrnl.exception import JrnlException
|
||||
|
||||
|
||||
@patch(
|
||||
"os.getcwd", side_effect="/"
|
||||
) # prevent failures in CI if current directory has been deleted
|
||||
@patch("builtins.open", side_effect=FileNotFoundError())
|
||||
def test_read_template_file_with_no_file_raises_exception(mock_open, mock_getcwd):
|
||||
with pytest.raises(JrnlException) as ex:
|
||||
read_template_file("invalid_file.txt")
|
||||
assert isinstance(ex.value, JrnlException)
|
||||
|
||||
|
||||
@patch(
|
||||
"os.getcwd", side_effect="/"
|
||||
) # prevent failures in CI if current directory has been deleted
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="template text")
|
||||
def test_read_template_file_with_valid_file_returns_text(mock_file, mock_getcwd):
|
||||
assert read_template_file("valid_file.txt") == "template text"
|
||||
|
||||
|
||||
def test_get_template_path_when_exists_returns_correct_path():
|
||||
with patch("os.path.exists", return_value=True):
|
||||
output = get_template_path("template", "templatepath")
|
||||
|
||||
assert output == os.path.join("templatepath", "template")
|
||||
|
||||
|
||||
@patch("jrnl.editor.absolute_path")
|
||||
def test_get_template_path_when_doesnt_exist_returns_correct_path(mock_absolute_paths):
|
||||
with patch("os.path.exists", return_value=False):
|
||||
output = get_template_path("template", "templatepath")
|
||||
|
||||
assert output == mock_absolute_paths.return_value
|
|
@ -242,7 +242,9 @@ def test_color_override():
|
|||
|
||||
def test_multiple_overrides():
|
||||
parsed_args = cli_as_dict(
|
||||
'--config-override colors.title green --config-override editor "nano" --config-override journal.scratchpad "/tmp/scratchpad"'
|
||||
"--config-override colors.title green "
|
||||
'--config-override editor "nano" '
|
||||
'--config-override journal.scratchpad "/tmp/scratchpad"'
|
||||
)
|
||||
assert parsed_args == expected_args(
|
||||
config_override=[
|
||||
|
|
Loading…
Add table
Reference in a new issue