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
|
if [[ "$(git rev-parse "origin/$BRANCH")" != "$GITHUB_SHA" ]]; then
|
||||||
# Normal build on a branch (no tag)
|
# 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 "::debug::GITHUB_SHA: $GITHUB_SHA"
|
||||||
echo "::error::$BRANCH has been updated since build started. Aborting changelog."
|
echo "::error::$BRANCH has been updated since build started. Aborting changelog."
|
||||||
exit 1
|
exit 1
|
||||||
|
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
@ -200,7 +200,7 @@ jobs:
|
||||||
--force
|
--force
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v4
|
uses: peter-evans/create-pull-request@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.BREW_TAP_DIRECTORY }}
|
path: ${{ env.BREW_TAP_DIRECTORY }}
|
||||||
token: ${{ secrets.JRNL_BOT_TOKEN }}
|
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:
|
paths:
|
||||||
- '.github/workflows/**'
|
- '.github/workflows/**'
|
||||||
- '.github/actions/**'
|
- '.github/actions/**'
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * SAT'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -19,6 +19,7 @@ var/
|
||||||
node_modules/
|
node_modules/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
.flakeheaven_cache/
|
||||||
|
|
||||||
# Versioning
|
# Versioning
|
||||||
.python-version
|
.python-version
|
||||||
|
@ -39,7 +40,7 @@ exp/
|
||||||
objects.inv
|
objects.inv
|
||||||
searchindex.js
|
searchindex.js
|
||||||
|
|
||||||
# virtaulenv
|
# virtualenv
|
||||||
.venv*/
|
.venv*/
|
||||||
env/
|
env/
|
||||||
env*/
|
env*/
|
||||||
|
|
124
CHANGELOG.md
124
CHANGELOG.md
|
@ -1,45 +1,71 @@
|
||||||
# Changelog
|
# 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:**
|
**Fixed bugs:**
|
||||||
|
|
||||||
- jrnl reads extraneous text files when reading folder journal [\#1692](https://github.com/jrnl-org/jrnl/issues/1692)
|
- Linting rules aren't enforced the same as format rules [\#1742](https://github.com/jrnl-org/jrnl/issues/1742)
|
||||||
- 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))
|
**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:**
|
**Documentation:**
|
||||||
|
|
||||||
- Update contributing.md links in documentation [\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking))
|
- 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))
|
||||||
|
|
||||||
## [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))
|
|
||||||
|
|
||||||
**Packaging:**
|
**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 keyring to v24.2.0 [\#1760](https://github.com/jrnl-org/jrnl/pull/1760) ([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 keyring to v24 [\#1758](https://github.com/jrnl-org/jrnl/pull/1758) ([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 to v7.4.0 [\#1757](https://github.com/jrnl-org/jrnl/pull/1757) ([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 rich to v13.4.2 [\#1754](https://github.com/jrnl-org/jrnl/pull/1754) ([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 cryptography to v41 [\#1753](https://github.com/jrnl-org/jrnl/pull/1753) ([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 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.4.12 [\#1712](https://github.com/jrnl-org/jrnl/pull/1712) ([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:**
|
**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))
|
- 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))
|
- 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))
|
- 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:**
|
**Fixed bugs:**
|
||||||
|
|
||||||
- Combinations of `--change-time`, `--delete`, and `--edit` don't work consistently [\#1696](https://github.com/jrnl-org/jrnl/issues/1696)
|
- 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))
|
||||||
- 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)
|
|
||||||
- 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))
|
- 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))
|
- 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))
|
- 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))
|
- 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))
|
- 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:**
|
**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))
|
- 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))
|
- 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 `simplify` plugin to linting checks [\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren))
|
||||||
|
@ -77,11 +93,8 @@
|
||||||
|
|
||||||
**Documentation:**
|
**Documentation:**
|
||||||
|
|
||||||
- Document template extension behavior [\#1677](https://github.com/jrnl-org/jrnl/issues/1677)
|
- Update contributing.md links in documentation [\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking))
|
||||||
- Visual Studio Code may store unencrypted temporary files [\#1675](https://github.com/jrnl-org/jrnl/issues/1675)
|
- Fix various typos [\#1718](https://github.com/jrnl-org/jrnl/pull/1718) ([hezhizhen](https://github.com/hezhizhen))
|
||||||
- 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 documentation front page text [\#1698](https://github.com/jrnl-org/jrnl/pull/1698) ([micahellison](https://github.com/micahellison))
|
- 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))
|
- 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))
|
- 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:**
|
**Packaging:**
|
||||||
|
|
||||||
- Update dependency cryptography to v40 [\#1710](https://github.com/jrnl-org/jrnl/pull/1710) ([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 poethepoet to v0.19.0 [\#1709](https://github.com/jrnl-org/jrnl/pull/1709) ([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 tzlocal to v4.3 [\#1708](https://github.com/jrnl-org/jrnl/pull/1708) ([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 tox to v4.4.7 [\#1707](https://github.com/jrnl-org/jrnl/pull/1707) ([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.2 [\#1706](https://github.com/jrnl-org/jrnl/pull/1706) ([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-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))
|
|
||||||
- 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 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 poethepoet to v0.19.0 [\#1709](https://github.com/jrnl-org/jrnl/pull/1709) ([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 pytest to v7.3.1 [\#1720](https://github.com/jrnl-org/jrnl/pull/1720) ([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 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)
|
## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29)
|
||||||
|
|
||||||
|
@ -203,6 +217,10 @@
|
||||||
|
|
||||||
🚨 **BREAKING CHANGES** 🚨
|
🚨 **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:**
|
**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))
|
- 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))
|
- 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))
|
- 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:**
|
**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))
|
- 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
|
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).
|
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
|
### JSON
|
||||||
|
|
||||||
``` sh
|
``` 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!
|
We gratefully thank all contributors!
|
||||||
Come see the whole list of code and financial contributors at https://github.com/jrnl-org/jrnl
|
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(
|
composing.add_argument(
|
||||||
"--template",
|
"--template",
|
||||||
dest="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 = (
|
read_msg = (
|
||||||
|
@ -265,13 +266,15 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
||||||
"-contains",
|
"-contains",
|
||||||
dest="contains",
|
dest="contains",
|
||||||
metavar="TEXT",
|
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(
|
reading.add_argument(
|
||||||
"-and",
|
"-and",
|
||||||
dest="strict",
|
dest="strict",
|
||||||
action="store_true",
|
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(
|
reading.add_argument(
|
||||||
"-starred",
|
"-starred",
|
||||||
|
@ -290,7 +293,8 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
||||||
dest="limit",
|
dest="limit",
|
||||||
default=None,
|
default=None,
|
||||||
metavar="NUMBER",
|
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="?",
|
nargs="?",
|
||||||
type=int,
|
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"""
|
If used on their own (with no search), they will act on your entire journal"""
|
||||||
|
)
|
||||||
exporting = parser.add_argument_group(
|
exporting = parser.add_argument_group(
|
||||||
"Searching Options", textwrap.dedent(search_options_msg)
|
"Searching Options", textwrap.dedent(search_options_msg)
|
||||||
)
|
)
|
||||||
|
@ -360,7 +368,8 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
||||||
"--tags",
|
"--tags",
|
||||||
dest="tags",
|
dest="tags",
|
||||||
action="store_true",
|
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(
|
exporting.add_argument(
|
||||||
"--short",
|
"--short",
|
||||||
|
@ -400,7 +409,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
||||||
\t jrnl --config-override editor "nano" \n
|
\t jrnl --config-override editor "nano" \n
|
||||||
\t - Override color selections\n
|
\t - Override color selections\n
|
||||||
\t jrnl --config-override colors.body blue --config-override colors.title green
|
\t jrnl --config-override colors.body blue --config-override colors.title green
|
||||||
""",
|
""", # noqa: E501
|
||||||
)
|
)
|
||||||
config_overrides.add_argument(
|
config_overrides.add_argument(
|
||||||
"--co",
|
"--co",
|
||||||
|
@ -430,7 +439,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
||||||
\t jrnl --config-file /home/user1/work_config.yaml
|
\t jrnl --config-file /home/user1/work_config.yaml
|
||||||
\t - Use a personal config file stored on a thumb drive: \n
|
\t - Use a personal config file stored on a thumb drive: \n
|
||||||
\t jrnl --config-file /media/user1/my-thumb-drive/personal_config.yaml
|
\t jrnl --config-file /media/user1/my-thumb-drive/personal_config.yaml
|
||||||
""",
|
""", # noqa: E501
|
||||||
)
|
)
|
||||||
|
|
||||||
alternate_config.add_argument(
|
alternate_config.add_argument(
|
||||||
|
|
|
@ -142,7 +142,7 @@ def postconfig_encrypt(
|
||||||
def postconfig_decrypt(
|
def postconfig_decrypt(
|
||||||
args: argparse.Namespace, config: dict, original_config: dict
|
args: argparse.Namespace, config: dict, original_config: dict
|
||||||
) -> int:
|
) -> 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.config import update_config
|
||||||
from jrnl.install import save_config
|
from jrnl.install import save_config
|
||||||
from jrnl.journals import open_journal
|
from jrnl.journals import open_journal
|
||||||
|
|
|
@ -4,12 +4,10 @@
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
import colorama
|
import colorama
|
||||||
import xdg.BaseDirectory
|
|
||||||
from rich.pretty import pretty_repr
|
from rich.pretty import pretty_repr
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
from ruamel.yaml import constructor
|
from ruamel.yaml import constructor
|
||||||
|
@ -21,13 +19,10 @@ from jrnl.messages import MsgStyle
|
||||||
from jrnl.messages import MsgText
|
from jrnl.messages import MsgText
|
||||||
from jrnl.output import list_journals
|
from jrnl.output import list_journals
|
||||||
from jrnl.output import print_msg
|
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
|
# Constants
|
||||||
DEFAULT_CONFIG_NAME = "jrnl.yaml"
|
|
||||||
XDG_RESOURCE = "jrnl"
|
|
||||||
|
|
||||||
DEFAULT_JOURNAL_NAME = "journal.txt"
|
|
||||||
DEFAULT_JOURNAL_KEY = "default"
|
DEFAULT_JOURNAL_KEY = "default"
|
||||||
|
|
||||||
YAML_SEPARATOR = ": "
|
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
|
The dict is created through the yaml loader, with the assumption that
|
||||||
"input[0]: input[1]" is valid yaml.
|
"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
|
: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
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -73,31 +69,6 @@ def save_config(config: dict, alt_config_path: str | None = None) -> None:
|
||||||
yaml.dump(config, f)
|
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]:
|
def get_default_config() -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"version": __version__,
|
"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:
|
def scope_config(config: dict, journal_name: str) -> dict:
|
||||||
if journal_name not in config["journals"]:
|
if journal_name not in config["journals"]:
|
||||||
return config
|
return config
|
||||||
|
|
|
@ -11,10 +11,10 @@ from jrnl import time
|
||||||
from jrnl.config import DEFAULT_JOURNAL_KEY
|
from jrnl.config import DEFAULT_JOURNAL_KEY
|
||||||
from jrnl.config import get_config_path
|
from jrnl.config import get_config_path
|
||||||
from jrnl.config import get_journal_name
|
from jrnl.config import get_journal_name
|
||||||
from jrnl.config import get_templates_path
|
|
||||||
from jrnl.config import scope_config
|
from jrnl.config import scope_config
|
||||||
from jrnl.editor import get_text_from_editor
|
from jrnl.editor import get_text_from_editor
|
||||||
from jrnl.editor import get_text_from_stdin
|
from jrnl.editor import get_text_from_stdin
|
||||||
|
from jrnl.editor import read_template_file
|
||||||
from jrnl.exception import JrnlException
|
from jrnl.exception import JrnlException
|
||||||
from jrnl.journals import open_journal
|
from jrnl.journals import open_journal
|
||||||
from jrnl.messages import Message
|
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_msg
|
||||||
from jrnl.output import print_msgs
|
from jrnl.output import print_msgs
|
||||||
from jrnl.override import apply_overrides
|
from jrnl.override import apply_overrides
|
||||||
from jrnl.path import absolute_path
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
|
@ -35,9 +34,9 @@ if TYPE_CHECKING:
|
||||||
def run(args: "Namespace"):
|
def run(args: "Namespace"):
|
||||||
"""
|
"""
|
||||||
Flow:
|
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
|
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
|
4. Load specified journal
|
||||||
5. Start append mode, or search mode
|
5. Start append mode, or search mode
|
||||||
6. Perform actions with results from search mode (if needed)
|
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
|
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:
|
def append_mode(args: "Namespace", config: dict, journal: "Journal", **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Gets input from the user to write to the journal
|
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")
|
logging.debug("Append mode: starting")
|
||||||
|
|
||||||
if args.template or config["template"]:
|
template_text = _get_template(args, config)
|
||||||
logging.debug(f"Append mode: template CLI arg detected: {args.template}")
|
|
||||||
# Read template file and pass as raw text into the composer
|
if args.text:
|
||||||
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:
|
|
||||||
logging.debug(f"Append mode: cli text detected: {args.text}")
|
logging.debug(f"Append mode: cli text detected: {args.text}")
|
||||||
raw = " ".join(args.text).strip()
|
raw = " ".join(args.text).strip()
|
||||||
if args.edit:
|
if args.edit:
|
||||||
raw = _write_in_editor(config, raw)
|
raw = _write_in_editor(config, raw)
|
||||||
|
|
||||||
elif not sys.stdin.isatty():
|
elif not sys.stdin.isatty():
|
||||||
logging.debug("Append mode: receiving piped text")
|
logging.debug("Append mode: receiving piped text")
|
||||||
raw = sys.stdin.read()
|
raw = sys.stdin.read()
|
||||||
|
|
||||||
else:
|
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():
|
if not raw or raw.isspace():
|
||||||
logging.error("Append mode: couldn't get raw text or entry was empty")
|
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()")
|
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:
|
def search_mode(args: "Namespace", journal: "Journal", **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Search for entries in a journal, and return the
|
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 on_windows
|
||||||
from jrnl.os_compat import split_args
|
from jrnl.os_compat import split_args
|
||||||
from jrnl.output import print_msg
|
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:
|
def get_text_from_editor(config: dict, template: str = "") -> str:
|
||||||
|
@ -73,3 +75,47 @@ def get_text_from_stdin() -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
return raw
|
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:
|
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.
|
"""Checks if there are keys missing in a given config dict, and if so, updates the
|
||||||
This essentially automatically ports jrnl installations if new config parameters are introduced in later
|
config file accordingly. This essentially automatically ports jrnl installations
|
||||||
versions.
|
if new config parameters are introduced in later versions. Also checks for
|
||||||
Also checks for existence of and difference in version number between config dict and current jrnl version,
|
existence of and difference in version number between config dict
|
||||||
and if so, update the config file accordingly.
|
and current jrnl version, and if so, update the config file accordingly.
|
||||||
Supply alt_config_path if using an alternate config through --config-file."""
|
Supply alt_config_path if using an alternate config through --config-file."""
|
||||||
default_config = get_default_config()
|
default_config = get_default_config()
|
||||||
missing_keys = set(default_config).difference(config_data)
|
missing_keys = set(default_config).difference(config_data)
|
||||||
|
@ -167,7 +167,7 @@ def install() -> dict:
|
||||||
|
|
||||||
|
|
||||||
def _initialize_autocomplete() -> None:
|
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"):
|
if sys.modules.get("readline"):
|
||||||
import readline
|
import readline
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ class Entry:
|
||||||
}
|
}
|
||||||
|
|
||||||
def __str__(self):
|
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"])
|
date_str = self.date.strftime(self.journal.config["timeformat"])
|
||||||
title = "[{}] {}".format(date_str, self.title.rstrip("\n "))
|
title = "[{}] {}".format(date_str, self.title.rstrip("\n "))
|
||||||
if self.starred:
|
if self.starred:
|
||||||
|
@ -214,7 +214,7 @@ SENTENCE_SPLITTER = re.compile(
|
||||||
\s+ # AND a sequence of required spaces.
|
\s+ # AND a sequence of required spaces.
|
||||||
)
|
)
|
||||||
|[\uFF01\uFF0E\uFF1F\uFF61\u3002] # CJK full/half width terminals usually do not have following spaces.
|
|[\uFF01\uFF0E\uFF1F\uFF61\u3002] # CJK full/half width terminals usually do not have following spaces.
|
||||||
""",
|
""", # noqa: E501
|
||||||
re.VERBOSE,
|
re.VERBOSE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,8 @@ class Folder(Journal):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_files(journal_path: str) -> list[str]:
|
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 year_folder in Folder._get_year_folders(pathlib.Path(journal_path)):
|
||||||
for month_folder in Folder._get_month_folders(year_folder):
|
for month_folder in Folder._get_month_folders(year_folder):
|
||||||
yield from Folder._get_day_files(month_folder)
|
yield from Folder._get_day_files(month_folder)
|
||||||
|
|
|
@ -102,7 +102,7 @@ class Journal:
|
||||||
return self.encryption_method.encrypt(text)
|
return self.encryption_method.encrypt(text)
|
||||||
|
|
||||||
def open(self, filename: str | None = None) -> "Journal":
|
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)."""
|
Entries have the form (date, title, body)."""
|
||||||
filename = filename or self.config["journal"]
|
filename = filename or self.config["journal"]
|
||||||
dirname = os.path.dirname(filename)
|
dirname = os.path.dirname(filename)
|
||||||
|
@ -144,7 +144,7 @@ class Journal:
|
||||||
self._store(filename, text)
|
self._store(filename, text)
|
||||||
|
|
||||||
def validate_parsing(self) -> bool:
|
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())
|
new_entries = self._parse(self._to_text())
|
||||||
return all(entry == new_entries[i] for i, entry in enumerate(self.entries))
|
return all(entry == new_entries[i] for i, entry in enumerate(self.entries))
|
||||||
|
|
||||||
|
@ -225,8 +225,9 @@ class Journal:
|
||||||
@property
|
@property
|
||||||
def tags(self) -> list[Tag]:
|
def tags(self) -> list[Tag]:
|
||||||
"""Returns a set of tuples (count, tag) for all tags present in the journal."""
|
"""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
|
# Astute reader: should the following line leave you as puzzled as me the first
|
||||||
# I came across this construction, worry not and embrace the ensuing moment of enlightment.
|
# 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)]
|
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]
|
# 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}
|
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:
|
def new_entry(self, raw: str, date=None, sort: bool = True) -> Entry:
|
||||||
"""Constructs a new entry from some raw text input.
|
"""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")
|
raw = raw.replace("\\n ", "\n").replace("\\n", "\n")
|
||||||
|
|
|
@ -90,4 +90,4 @@ class MsgStyle(Enum):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def box_title(self) -> MsgText:
|
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)
|
Do you want to encrypt your journal? (You can always change this later)
|
||||||
"""
|
"""
|
||||||
UseColorsQuestion = """
|
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]"
|
YesOrNoPromptDefaultYes = "[Y/n]"
|
||||||
YesOrNoPromptDefaultNo = "[y/N]"
|
YesOrNoPromptDefaultNo = "[y/N]"
|
||||||
ContinueUpgrade = "Continue upgrading jrnl?"
|
ContinueUpgrade = "Continue upgrading jrnl?"
|
||||||
|
@ -105,16 +105,12 @@ class MsgText(Enum):
|
||||||
|
|
||||||
KeyboardInterruptMsg = "Aborted by user"
|
KeyboardInterruptMsg = "Aborted by user"
|
||||||
|
|
||||||
CantReadTemplateGlobalConfig = """
|
CantReadTemplate = """
|
||||||
Could not read template file defined in config:
|
Unable to find a template file {template_path}.
|
||||||
{global_template_path}
|
|
||||||
"""
|
|
||||||
|
|
||||||
CantReadTemplateCLIArg = """
|
The following paths were checked:
|
||||||
Unable to find a template file based on the passed arg, and no global template was detected.
|
* {jrnl_template_dir}{template_path}
|
||||||
The following filepaths were checked:
|
* {actual_template_path}
|
||||||
jrnl XDG Template Directory : {jrnl_template_dir}
|
|
||||||
Local Filepath : {normalized_template_arg_filepath}
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
NoNamedJournal = "No '{journal_name}' journal configured\n{journals}"
|
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
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
output = StringIO()
|
output = StringIO()
|
||||||
YAML().dump(journal_list, output)
|
dumper = YAML()
|
||||||
|
dumper.width = 1000
|
||||||
|
dumper.dump(journal_list, output)
|
||||||
|
|
||||||
return output.getvalue()
|
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
|
:return: Configuration to be used during runtime with the overrides applied
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
overrides = vars(args).get("config_override", None)
|
overrides = vars(args).get("config_override")
|
||||||
if not overrides:
|
if not overrides:
|
||||||
return base_config
|
return base_config
|
||||||
|
|
||||||
|
@ -56,7 +56,8 @@ def _recursively_apply(tree: dict, nodes: list, override_value) -> dict:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config (dict): Configuration to modify
|
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
|
override_value (str): Runtime override passed from the command-line
|
||||||
"""
|
"""
|
||||||
key = nodes[0]
|
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
|
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
import os.path
|
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:
|
def home_dir() -> str:
|
||||||
|
@ -14,3 +27,46 @@ def expand_path(path: str) -> str:
|
||||||
|
|
||||||
def absolute_path(path: str) -> str:
|
def absolute_path(path: str) -> str:
|
||||||
return os.path.abspath(expand_path(path))
|
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):
|
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"]
|
names = ["fancy", "boxed"]
|
||||||
extension = "txt"
|
extension = "txt"
|
||||||
|
|
|
@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class TagExporter(TextExporter):
|
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"]
|
names = ["tags"]
|
||||||
extension = "tags"
|
extension = "tags"
|
||||||
|
|
|
@ -10,7 +10,8 @@ if TYPE_CHECKING:
|
||||||
def get_tags_count(journal: "Journal") -> set[tuple[int, str]]:
|
def get_tags_count(journal: "Journal") -> set[tuple[int, str]]:
|
||||||
"""Returns a set of tuples (count, tag) for all tags present in the journal."""
|
"""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
|
# 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)]
|
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]
|
# 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}
|
tag_counts = {(tags.count(tag), tag) for tag in tags}
|
||||||
|
|
|
@ -18,14 +18,15 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class YAMLExporter(TextExporter):
|
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"]
|
names = ["yaml"]
|
||||||
extension = "md"
|
extension = "md"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def export_entry(cls, entry: "Entry", to_multifile: bool = True) -> str:
|
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:
|
if to_multifile is False:
|
||||||
raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR))
|
raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR))
|
||||||
|
|
||||||
|
@ -117,7 +118,14 @@ class YAMLExporter(TextExporter):
|
||||||
# source directory is entry.journal.config['journal']
|
# source directory is entry.journal.config['journal']
|
||||||
# output directory is...?
|
# 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="---",
|
start="---",
|
||||||
date=date_str,
|
date=date_str,
|
||||||
title=entry.title,
|
title=entry.title,
|
||||||
|
|
10
jrnl/time.py
10
jrnl/time.py
|
@ -34,8 +34,8 @@ def parse(
|
||||||
elif isinstance(date_str, datetime.datetime):
|
elif isinstance(date_str, datetime.datetime):
|
||||||
return date_str
|
return date_str
|
||||||
|
|
||||||
# Don't try to parse anything with 6 or fewer characters and was parsed from the existing journal.
|
# Don't try to parse anything with 6 or fewer characters and was parsed from the
|
||||||
# It's probably a markdown footnote
|
# existing journal. It's probably a markdown footnote
|
||||||
if len(date_str) <= 6 and bracketed:
|
if len(date_str) <= 6 and bracketed:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -82,9 +82,9 @@ def parse(
|
||||||
else:
|
else:
|
||||||
date = datetime.datetime(*date[:6])
|
date = datetime.datetime(*date[:6])
|
||||||
|
|
||||||
# Ugly heuristic: if the date is more than 4 weeks in the future, we got the year wrong.
|
# Ugly heuristic: if the date is more than 4 weeks in the future, we got the year
|
||||||
# Rather than this, we would like to see parsedatetime patched so we can tell it to prefer
|
# wrong. Rather than this, we would like to see parsedatetime patched so we can
|
||||||
# past dates
|
# tell it to prefer past dates
|
||||||
dt = datetime.datetime.now() - date
|
dt = datetime.datetime.now() - date
|
||||||
if dt.days < -28 and not year_present:
|
if dt.days < -28 and not year_present:
|
||||||
date = date.replace(date.year - 1)
|
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]
|
[tool.poetry]
|
||||||
name = "jrnl"
|
name = "jrnl"
|
||||||
version = "v4.0-beta3"
|
version = "v4.0.1"
|
||||||
description = "Collect your thoughts and notes without leaving the command line."
|
description = "Collect your thoughts and notes without leaving the command line."
|
||||||
authors = [
|
authors = [
|
||||||
"jrnl contributors <maintainers@jrnl.sh>",
|
"jrnl contributors <maintainers@jrnl.sh>",
|
||||||
|
@ -35,7 +35,7 @@ keyring = ">=21.0" # https://github.com/jaraco/keyring#integration
|
||||||
parsedatetime = ">=2.6"
|
parsedatetime = ">=2.6"
|
||||||
python-dateutil = "^2.8" # https://github.com/dateutil/dateutil/blob/master/RELEASING
|
python-dateutil = "^2.8" # https://github.com/dateutil/dateutil/blob/master/RELEASING
|
||||||
pyxdg = ">=0.27.0"
|
pyxdg = ">=0.27.0"
|
||||||
"ruamel.yaml" = "^0.17.21"
|
"ruamel.yaml" = ">=0.17.22"
|
||||||
rich = ">=12.2.0, <14.0.0"
|
rich = ">=12.2.0, <14.0.0"
|
||||||
|
|
||||||
# dayone-only deps
|
# dayone-only deps
|
||||||
|
@ -43,13 +43,7 @@ tzlocal = ">=4.0" # https://github.com/regebro/tzlocal/blob/master/CHANGES.txt
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = { version = ">=21.5b2", allow-prereleases = true }
|
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 = "*"
|
ipdb = "*"
|
||||||
isort = ">=5.10"
|
|
||||||
mkdocs = ">=1.4"
|
mkdocs = ">=1.4"
|
||||||
parse-type = ">=0.6.0"
|
parse-type = ">=0.6.0"
|
||||||
poethepoet = "*"
|
poethepoet = "*"
|
||||||
|
@ -58,6 +52,7 @@ pytest-bdd = ">=6.0"
|
||||||
pytest-clarity = "*"
|
pytest-clarity = "*"
|
||||||
pytest-xdist = ">=2.5.0"
|
pytest-xdist = ">=2.5.0"
|
||||||
requests = "*"
|
requests = "*"
|
||||||
|
ruff = ">=0.0.276"
|
||||||
toml = ">=0.10"
|
toml = ">=0.10"
|
||||||
tox = "*"
|
tox = "*"
|
||||||
xmltodict = "*"
|
xmltodict = "*"
|
||||||
|
@ -87,18 +82,18 @@ test-run = [
|
||||||
# Groups of tasks
|
# Groups of tasks
|
||||||
format.default_item_type = "cmd"
|
format.default_item_type = "cmd"
|
||||||
format.sequence = [
|
format.sequence = [
|
||||||
"isort .",
|
"ruff check . --select I --fix", # equivalent to "isort ."
|
||||||
"black .",
|
"black .",
|
||||||
]
|
]
|
||||||
|
|
||||||
lint.env = { FLAKEHEAVEN_CACHE_TIMEOUT = "0" }
|
|
||||||
lint.default_item_type = "cmd"
|
lint.default_item_type = "cmd"
|
||||||
lint.sequence = [
|
lint.sequence = [
|
||||||
"poetry --version",
|
"poetry --version",
|
||||||
"poetry check",
|
"poetry check",
|
||||||
"flakeheaven --version",
|
"ruff --version",
|
||||||
"flakeheaven plugins",
|
"ruff .",
|
||||||
"flakeheaven lint",
|
"black --version",
|
||||||
|
"black --check ."
|
||||||
]
|
]
|
||||||
|
|
||||||
test = [
|
test = [
|
||||||
|
@ -106,11 +101,6 @@ test = [
|
||||||
"test-run",
|
"test-run",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
||||||
force_single_line = true
|
|
||||||
known_first_party = ["jrnl", "tests"]
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = "6.0"
|
minversion = "6.0"
|
||||||
required_plugins = [
|
required_plugins = [
|
||||||
|
@ -136,29 +126,35 @@ filterwarnings = [
|
||||||
"ignore:[WinError 5].*"
|
"ignore:[WinError 5].*"
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.flakeheaven]
|
[tool.ruff]
|
||||||
max_line_length = 88
|
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"]
|
exclude = [".git", ".tox", ".venv", "node_modules"]
|
||||||
|
|
||||||
[tool.flakeheaven.plugins]
|
[tool.ruff.isort]
|
||||||
"py*" = ["+*"]
|
force-single-line = true
|
||||||
pycodestyle = [
|
known-first-party = ["jrnl", "tests"]
|
||||||
"-E101",
|
|
||||||
"-E111", "-E114", "-E115", "-E116", "-E117",
|
|
||||||
"-E12*",
|
|
||||||
"-E13*",
|
|
||||||
"-E2*",
|
|
||||||
"-E3*",
|
|
||||||
"-E401",
|
|
||||||
"-E5*",
|
|
||||||
"-E70",
|
|
||||||
"-W1*", "-W2*", "-W3*", "-W5*",
|
|
||||||
]
|
|
||||||
"flake8-*" = ["+*"]
|
|
||||||
flake8-black = ["-BLK901"]
|
|
||||||
|
|
||||||
[tool.flakeheaven.exceptions."jrnl/journals/__init__.py"]
|
[tool.ruff.per-file-ignores]
|
||||||
pyflakes = ["-F401"]
|
"__init__.py" = ["F401"] # unused imports
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
|
|
@ -38,7 +38,7 @@ Feature: Using templates
|
||||||
And we use the password "test" if prompted
|
And we use the password "test" if prompted
|
||||||
When we run "jrnl --template this_template_does_not_exist.template"
|
When we run "jrnl --template this_template_does_not_exist.template"
|
||||||
Then we should get an error
|
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
|
Examples: configs
|
||||||
| config_file |
|
| config_file |
|
||||||
|
|
|
@ -183,10 +183,10 @@ def mock_default_journal_path(temp_dir):
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def mock_default_templates_path(temp_dir):
|
def mock_default_templates_path(temp_dir):
|
||||||
templates_path = Path(temp_dir.name, "templates")
|
templates_path = os.path.join(temp_dir.name, "templates")
|
||||||
return {
|
return {
|
||||||
"get_templates_path": lambda: patch(
|
"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)}"
|
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\n{expected}", SHOULD_DICT))
|
||||||
@then(parse('the output {it_should:Should} contain "{expected_output}"', SHOULD_DICT))
|
@then(parse('the output {it_should:Should} contain "{expected}"', SHOULD_DICT))
|
||||||
@then(
|
@then(
|
||||||
parse(
|
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,
|
SHOULD_DICT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@then(
|
@then(
|
||||||
parse(
|
parse(
|
||||||
'the {which_output_stream} output {it_should:Should} contain "{expected_output}"',
|
'the {which_output_stream} output {it_should:Should} contain "{expected}"',
|
||||||
SHOULD_DICT,
|
SHOULD_DICT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
def output_should_contain(expected_output, which_output_stream, cli_run, it_should):
|
def output_should_contain(expected, 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']}"
|
output_str = (
|
||||||
assert expected_output
|
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:
|
if which_output_stream is None:
|
||||||
assert ((expected_output in cli_run["stdout"]) == it_should) or (
|
assert ((expected in cli_run["stdout"]) == it_should) or (
|
||||||
(expected_output in cli_run["stderr"]) == it_should
|
(expected in cli_run["stderr"]) == it_should
|
||||||
), output_str
|
), output_str
|
||||||
|
|
||||||
elif which_output_stream == "standard":
|
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":
|
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:
|
else:
|
||||||
assert (
|
assert (expected in cli_run[which_output_stream]) == it_should, output_str
|
||||||
expected_output in cli_run[which_output_stream]
|
|
||||||
) == it_should, output_str
|
|
||||||
|
|
||||||
|
|
||||||
@then(parse("the output should not contain\n{expected_output}"))
|
@then(parse("the output should not contain\n{expected_output}"))
|
||||||
|
@ -119,7 +122,8 @@ def output_should_be_columns_wide(cli_run, width):
|
||||||
|
|
||||||
@then(
|
@then(
|
||||||
parse(
|
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):
|
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(
|
@then(
|
||||||
parse(
|
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,
|
SHOULD_DICT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@then(
|
@then(
|
||||||
parse(
|
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,
|
SHOULD_DICT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -157,20 +163,22 @@ def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
|
||||||
actual_slice = actual
|
actual_slice = actual
|
||||||
if type(actual) is dict:
|
if type(actual) is dict:
|
||||||
# `expected` objects formatted in yaml only compare one level deep
|
# `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
|
assert (expected == actual_slice) == it_should
|
||||||
|
|
||||||
|
|
||||||
@then(
|
@then(
|
||||||
parse(
|
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,
|
SHOULD_DICT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@then(
|
@then(
|
||||||
parse(
|
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,
|
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():
|
def test_multiple_overrides():
|
||||||
parsed_args = cli_as_dict(
|
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(
|
assert parsed_args == expected_args(
|
||||||
config_override=[
|
config_override=[
|
||||||
|
|
Loading…
Add table
Reference in a new issue