Compare commits

..

No commits in common. "develop" and "v4.0-beta3" have entirely different histories.

70 changed files with 1733 additions and 2329 deletions

View file

@ -11,17 +11,16 @@ runs:
shell: bash shell: bash
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Capture full Python version in env - name: Capture full Python version in env
run: echo "PYTHON_FULL_VERSION=$(python --version)" >> $GITHUB_ENV run: echo "PYTHON_FULL_VERSION=$(python --version)" >> $GITHUB_ENV
shell: bash shell: bash
- name: poetry cache # Change CACHE_STRING secret to bust the cache - name: poetry cache # Change CACHE_STRING secret to bust the cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: .venv path: .venv
key: ${{ runner.os }}-${{ hashFiles('poetry.lock') }}-${{ env.PYTHON_FULL_VERSION }}-${{ inputs.cache-string }} key: ${{ runner.os }}-${{ hashFiles('poetry.lock') }}-${{ env.PYTHON_FULL_VERSION }}-${{ inputs.cache-string }}
@ -34,7 +33,7 @@ runs:
echo '::endgroup::' echo '::endgroup::'
echo '::group::Other dependencies' echo '::group::Other dependencies'
poetry sync poetry install --remove-untracked
echo '::endgroup::' echo '::endgroup::'
echo 'DEPS_INSTALLED=true' >> $GITHUB_ENV echo 'DEPS_INSTALLED=true' >> $GITHUB_ENV

View file

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
token: ${{ secrets.JRNL_BOT_TOKEN }} token: ${{ secrets.JRNL_BOT_TOKEN }}
@ -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

View file

@ -36,10 +36,10 @@ jobs:
os: [ ubuntu-latest ] os: [ ubuntu-latest ]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
@ -50,13 +50,13 @@ jobs:
run: echo "PYTHON_FULL_VERSION=$(python --version)" >> "$GITHUB_ENV" run: echo "PYTHON_FULL_VERSION=$(python --version)" >> "$GITHUB_ENV"
- name: poetry cache - name: poetry cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: .venv path: .venv
key: ${{ runner.os }}-${{ hashFiles('poetry.lock') }}-${{ env.PYTHON_FULL_VERSION }}-${{ secrets.CACHE_STRING }} key: ${{ runner.os }}-${{ hashFiles('poetry.lock') }}-${{ env.PYTHON_FULL_VERSION }}-${{ secrets.CACHE_STRING }}
- name: npm cache - name: npm cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: node_modules path: node_modules
key: ${{ runner.os }}-pa11y-v3 key: ${{ runner.os }}-pa11y-v3
@ -65,7 +65,7 @@ jobs:
run: | run: |
pip install poetry pip install poetry
poetry config --local virtualenvs.in-project true poetry config --local virtualenvs.in-project true
poetry sync --no-root poetry install --no-root --remove-untracked
npm install npm install
echo "node_modules/.bin" >> "$GITHUB_PATH" echo "node_modules/.bin" >> "$GITHUB_PATH"

View file

@ -19,6 +19,11 @@ on:
type: boolean type: boolean
required: true required: true
default: true default: true
include_brew:
description: 'Publish to Homebrew?'
type: boolean
required: true
default: true
jobs: jobs:
validate: validate:
@ -61,12 +66,12 @@ jobs:
echo "JRNL_VERSION=$JRNL_VERSION" >> "$GITHUB_ENV" echo "JRNL_VERSION=$JRNL_VERSION" >> "$GITHUB_ENV"
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: '3.13' python-version: '3.11'
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
token: ${{ secrets.JRNL_BOT_TOKEN }} token: ${{ secrets.JRNL_BOT_TOKEN }}
@ -107,3 +112,111 @@ jobs:
run: | run: |
pypi_version="$(find dist/jrnl-*.tar.gz | sed -r 's!dist/jrnl-(.*)\.tar\.gz!\1!')" pypi_version="$(find dist/jrnl-*.tar.gz | sed -r 's!dist/jrnl-(.*)\.tar\.gz!\1!')"
echo "pypi_version=$pypi_version" >> "$GITHUB_OUTPUT" echo "pypi_version=$pypi_version" >> "$GITHUB_OUTPUT"
release_homebrew:
if: ${{ github.event.inputs.include_brew == 'true' }}
needs: release_pypi
name: "Release to Homebrew"
runs-on: macos-latest
env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
HOME_REPO: ${{ secrets.HOME_REPO }}
steps:
- name: Get version
run: |
JRNL_VERSION="${{ github.event.inputs.version }}"
PYPI_VERSION="${{ needs.release_pypi.outputs.pypi_version }}"
echo "::debug::jrnl version: $JRNL_VERSION"
echo "::debug::pypi version: $PYPI_VERSION"
echo "JRNL_VERSION=$JRNL_VERSION" >> "$GITHUB_ENV"
echo "PYPI_VERSION=$PYPI_VERSION" >> "$GITHUB_ENV"
- name: Set env variables
env:
REPO_OWNER: ${{ github.repository_owner }}
run: |
if [[ $JRNL_VERSION =~ (alpha|beta) ]]; then
echo '::debug::Prerelease (not a full release)'
{
echo "RELEASE_TYPE=pre"
echo "FORMULA_REPO=${REPO_OWNER}/homebrew-prerelease"
echo "BOT_REPO=jrnl-bot/homebrew-prerelease"
echo "FORMULA_NAME=jrnl-beta"
} >> "$GITHUB_ENV"
else
echo '::debug::Full release (not a prerelease)'
if [[ "${{ github.repository }}" == "${HOME_REPO}" ]]; then
REPO_OWNER="homebrew"
fi
{
echo "RELEASE_TYPE=full"
echo "FORMULA_REPO=${REPO_OWNER}/homebrew-core"
echo "BOT_REPO=jrnl-bot/homebrew-core"
echo "FORMULA_NAME=jrnl"
} >> "$GITHUB_ENV"
fi
- name: Tap formula
run: |
brew tap "${FORMULA_REPO}"
echo '::debug::Set tap directory'
echo "BREW_TAP_DIRECTORY=$(brew --repo "${FORMULA_REPO}")" >> "$GITHUB_ENV"
- name: Install dependencies
run: brew install pipgrip
- name: Query PyPI API
uses: nick-invision/retry@v2
with:
timeout_seconds: 10
max_attempts: 30
retry_wait_seconds: 10
command: |
curl -Ls https://pypi.org/pypi/jrnl/json > api_response.json
# if query doesn't have our version yet, give it some time before trying again
if [[ "null" == "$(jq ".releases[\"${PYPI_VERSION}\"][1].url" -r api_response.json)" ]]; then
echo "::debug::PYPI_VERSION: $PYPI_VERSION"
echo "::debug::JQ VALUE: $(jq ".releases[\"${PYPI_VERSION}\"][1].url" -r api_response.json)"
echo "::group::cat api_response.json"
cat api_response.json
echo "::endgroup::"
exit 1
fi
- name: Update Homebrew Formula
uses: nick-invision/retry@v2
with:
timeout_minutes: 8
max_attempts: 6
retry_wait_seconds: 30
command: >
brew bump-formula-pr "${FORMULA_NAME}"
--url $(jq ".releases[\"${PYPI_VERSION}\"][1].url" -r api_response.json)
--sha256 $(jq ".releases[\"${PYPI_VERSION}\"][1].digests.sha256" -r api_response.json)
--no-audit
--write-only
--force
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
path: ${{ env.BREW_TAP_DIRECTORY }}
token: ${{ secrets.JRNL_BOT_TOKEN }}
push-to-fork: ${{ env.BOT_REPO }}
committer: ${{ secrets.JRNL_BOT_NAME }} <${{ secrets.JRNL_BOT_EMAIL }}>
author: ${{ secrets.JRNL_BOT_NAME }} <${{ secrets.JRNL_BOT_EMAIL }}>
title: jrnl ${{ env.JRNL_VERSION }}
body: Created with `brew bump-formula-pr`
branch: jrnl-${{ env.JRNL_VERSION }}--
branch-suffix: random
commit-message: |
jrnl ${{ env.JRNL_VERSION }}
Update jrnl to ${{ env.JRNL_VERSION }}
${{ secrets.RELEASE_COAUTHORS }}

View file

@ -14,8 +14,6 @@ on:
paths: paths:
- '.github/workflows/**' - '.github/workflows/**'
- '.github/actions/**' - '.github/actions/**'
schedule:
- cron: '0 0 * * SAT'
jobs: jobs:
test: test:
@ -28,7 +26,7 @@ jobs:
os: [ ubuntu-latest ] os: [ ubuntu-latest ]
steps: steps:
- run: git config --global core.autocrlf false - run: git config --global core.autocrlf false
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Check workflow files - name: Check workflow files
uses: docker://rhysd/actionlint:latest uses: docker://rhysd/actionlint:latest
with: with:

View file

@ -37,11 +37,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: [ '3.10', '3.11', '3.12', '3.13' ] python-version: [ '3.10', '3.11' ]
os: [ ubuntu-latest, macos-latest, windows-latest ] os: [ ubuntu-latest, macos-latest, windows-latest ]
steps: steps:
- run: git config --global core.autocrlf false - run: git config --global core.autocrlf false
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run tests - name: Run tests
uses: ./.github/actions/run_tests uses: ./.github/actions/run_tests
with: with:

View file

@ -17,11 +17,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: [ '3.10', '3.11', '3.12', '3.13' ] python-version: [ '3.10', '3.11' ]
os: [ ubuntu-latest, macos-latest, windows-latest ] os: [ ubuntu-latest, macos-latest, windows-latest ]
steps: steps:
- run: git config --global core.autocrlf false - run: git config --global core.autocrlf false
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run tests - name: Run tests
uses: ./.github/actions/run_tests uses: ./.github/actions/run_tests
with: with:

3
.gitignore vendored
View file

@ -19,7 +19,6 @@ var/
node_modules/ node_modules/
__pycache__/ __pycache__/
.pytest_cache/ .pytest_cache/
.flakeheaven_cache/
# Versioning # Versioning
.python-version .python-version
@ -40,7 +39,7 @@ exp/
objects.inv objects.inv
searchindex.js searchindex.js
# virtualenv # virtaulenv
.venv*/ .venv*/
env/ env/
env*/ env*/

View file

@ -2,180 +2,42 @@
## [Unreleased](https://github.com/jrnl-org/jrnl/) ## [Unreleased](https://github.com/jrnl-org/jrnl/)
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.2.1...HEAD) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta2...HEAD)
**Fixed bugs:**
- poetry warning - "poetry.dev-dependencies" section is deprecated [\#1975](https://github.com/jrnl-org/jrnl/issues/1975)
- Homebrew autobump error on jrnl release [\#1961](https://github.com/jrnl-org/jrnl/issues/1961)
**Build:**
- Remove release step to publish to Homebrew [\#1994](https://github.com/jrnl-org/jrnl/pull/1994) ([micahellison](https://github.com/micahellison))
**Packaging:**
- Update dependency rich to v14 [\#1989](https://github.com/jrnl-org/jrnl/pull/1989) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency python to 3.13 [\#1988](https://github.com/jrnl-org/jrnl/pull/1988) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency tox to v4.25.0 [\#1986](https://github.com/jrnl-org/jrnl/pull/1986) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency tzlocal to v5.3.1 [\#1984](https://github.com/jrnl-org/jrnl/pull/1984) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency jinja2 to v3.1.6 [\#1983](https://github.com/jrnl-org/jrnl/pull/1983) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency poethepoet to v0.33.1 [\#1982](https://github.com/jrnl-org/jrnl/pull/1982) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pytest to v8.3.5 [\#1981](https://github.com/jrnl-org/jrnl/pull/1981) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency cryptography to v44.0.2 [\#1980](https://github.com/jrnl-org/jrnl/pull/1980) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency ruff to v0.11.3 [\#1978](https://github.com/jrnl-org/jrnl/pull/1978) ([renovate[bot]](https://github.com/apps/renovate))
## [v4.2.1](https://pypi.org/project/jrnl/v4.2.1/) (2025-02-25)
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.2...v4.2.1)
**Documentation:** **Documentation:**
- Typing animation in landing page is broken [\#1969](https://github.com/jrnl-org/jrnl/issues/1969) - Update contributing.md links in documentation [\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking))
**Packaging:** **Packaging:**
- Update dependency pytest to \>=8.1.1 [\#1974](https://github.com/jrnl-org/jrnl/pull/1974) ([wren](https://github.com/wren)) - 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 black to v25 [\#1973](https://github.com/jrnl-org/jrnl/pull/1973) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency tzlocal to v5.3 [\#1972](https://github.com/jrnl-org/jrnl/pull/1972) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency ruamel.yaml to v0.18.10 [\#1967](https://github.com/jrnl-org/jrnl/pull/1967) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency jinja2 to v3.1.5 [\#1966](https://github.com/jrnl-org/jrnl/pull/1966) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency cryptography to v44 [\#1962](https://github.com/jrnl-org/jrnl/pull/1962) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pytest-bdd to v8.1.0 [\#1952](https://github.com/jrnl-org/jrnl/pull/1952) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency poethepoet to v0.32.2 [\#1951](https://github.com/jrnl-org/jrnl/pull/1951) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency keyring to v25.6.0 [\#1948](https://github.com/jrnl-org/jrnl/pull/1948) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency ruff to v0.9.7 [\#1947](https://github.com/jrnl-org/jrnl/pull/1947) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency rich to v13.9.4 [\#1946](https://github.com/jrnl-org/jrnl/pull/1946) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency tox to v4.24.1 [\#1945](https://github.com/jrnl-org/jrnl/pull/1945) ([renovate[bot]](https://github.com/apps/renovate))
## [v4.2](https://pypi.org/project/jrnl/v4.2/) (2024-11-17) ## [v4.0-beta2](https://pypi.org/project/jrnl/v4.0-beta2/) (2023-04-22)
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.2-beta...v4.2) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta...v4.0-beta2)
**Implemented enhancements:** **Documentation:**
- Add Python 3.13 support [\#1893](https://github.com/jrnl-org/jrnl/issues/1893) - Fix various typos [\#1718](https://github.com/jrnl-org/jrnl/pull/1718) ([hezhizhen](https://github.com/hezhizhen))
- Add calendar heatmap display format [\#1759](https://github.com/jrnl-org/jrnl/pull/1759) ([alichtman](https://github.com/alichtman))
**Packaging:**
**Fixed bugs:**
- Update dependency cryptography to v40.0.2 [\#1723](https://github.com/jrnl-org/jrnl/pull/1723) ([renovate[bot]](https://github.com/apps/renovate))
- -contains doesn't accept multiple search terms, doesn't work with -and [\#1877](https://github.com/jrnl-org/jrnl/issues/1877) - Update dependency flakeheaven to v3.3.0 [\#1722](https://github.com/jrnl-org/jrnl/pull/1722) ([renovate[bot]](https://github.com/apps/renovate))
- Tests failing on develop branch starting with pytest-bdd 7.1.2 [\#1875](https://github.com/jrnl-org/jrnl/issues/1875) - Update dependency pytest to v7.3.1 [\#1720](https://github.com/jrnl-org/jrnl/pull/1720) ([renovate[bot]](https://github.com/apps/renovate))
- Ignore color when used in a pipeline [\#1839](https://github.com/jrnl-org/jrnl/issues/1839) - Update dependency black to v23.3.0 [\#1715](https://github.com/jrnl-org/jrnl/pull/1715) ([renovate[bot]](https://github.com/apps/renovate))
- Fix -contains to allow multiple terms with "OR" logic unless -and is added [\#1890](https://github.com/jrnl-org/jrnl/pull/1890) ([eigenric](https://github.com/eigenric)) - 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))
**Documentation:** - Update dependency tox to v4.4.12 [\#1712](https://github.com/jrnl-org/jrnl/pull/1712) ([renovate[bot]](https://github.com/apps/renovate))
- Recommend pipx as default installation method [\#1888](https://github.com/jrnl-org/jrnl/issues/1888) ## [v4.0-beta](https://pypi.org/project/jrnl/v4.0-beta/) (2023-03-25)
- Remove documentation recommendation to install pipx through brew or pip [\#1886](https://github.com/jrnl-org/jrnl/issues/1886)
- Document security risks of using a computer that someone else has admin access to [\#1793](https://github.com/jrnl-org/jrnl/issues/1793) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3...v4.0-beta)
- Recommend pipx as easiest installation method for all OSes and remove warning about apt [\#1889](https://github.com/jrnl-org/jrnl/pull/1889) ([micahellison](https://github.com/micahellison))
- Docs accessibility checker failure - contrast ratio [\#1934](https://github.com/jrnl-org/jrnl/issues/1934)
- Docs accessibility test runner failing [\#1932](https://github.com/jrnl-org/jrnl/issues/1932)
**Packaging:**
- Update actions/cache action to v4 [\#1847](https://github.com/jrnl-org/jrnl/pull/1847) ([renovate[bot]](https://github.com/apps/renovate))
- Update actions/setup-python action to v5 [\#1848](https://github.com/jrnl-org/jrnl/pull/1848) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency black to v24.8.0 [\#1923](https://github.com/jrnl-org/jrnl/pull/1923) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency cryptography to v43.0.3 [\#1942](https://github.com/jrnl-org/jrnl/pull/1942) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency jinja2 to v3.1.4 [\#1892](https://github.com/jrnl-org/jrnl/pull/1892) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency keyring to v25.4.1 [\#1924](https://github.com/jrnl-org/jrnl/pull/1924) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency mkdocs to v1.6.1 [\#1895](https://github.com/jrnl-org/jrnl/pull/1895) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pa11y-ci to v3.1.0 [\#1831](https://github.com/jrnl-org/jrnl/pull/1831) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency parse-type to v0.6.4 [\#1936](https://github.com/jrnl-org/jrnl/pull/1936) ([renovate[bot]](https://github.com/apps/renovate))
- Update peter-evans/create-pull-request action to v7 [\#1929](https://github.com/jrnl-org/jrnl/pull/1929) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency poethepoet to v0.29.0 [\#1925](https://github.com/jrnl-org/jrnl/pull/1925) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pytest to v7.4.4 [\#1845](https://github.com/jrnl-org/jrnl/pull/1845) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pytest-bdd to v7.3.0 [\#1896](https://github.com/jrnl-org/jrnl/pull/1896) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pytest-xdist to v3.6.1 [\#1897](https://github.com/jrnl-org/jrnl/pull/1897) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency python-dateutil to v2.9.0 [\#1898](https://github.com/jrnl-org/jrnl/pull/1898) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency requests to v2.32.3 [\#1899](https://github.com/jrnl-org/jrnl/pull/1899) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency rich to v13.9.2 [\#1937](https://github.com/jrnl-org/jrnl/pull/1937) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency ruamel.yaml to v0.18.6 [\#1855](https://github.com/jrnl-org/jrnl/pull/1855) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency ruff to v0.7.0 [\#1938](https://github.com/jrnl-org/jrnl/pull/1938) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency tox to v4.23.0 [\#1935](https://github.com/jrnl-org/jrnl/pull/1935) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency typed.js to v2.1.0 [\#1861](https://github.com/jrnl-org/jrnl/pull/1861) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency xmltodict to v0.14.2 [\#1940](https://github.com/jrnl-org/jrnl/pull/1940) ([renovate[bot]](https://github.com/apps/renovate))
- Update nick-invision/retry action to v3 [\#1851](https://github.com/jrnl-org/jrnl/pull/1851) ([renovate[bot]](https://github.com/apps/renovate))
- Update peter-evans/create-pull-request action to v6 [\#1852](https://github.com/jrnl-org/jrnl/pull/1852) ([renovate[bot]](https://github.com/apps/renovate))
## [v4.1](https://pypi.org/project/jrnl/v4.1/) (2023-11-04)
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.1-beta2...v4.1)
**Build:**
- Add Python 3.12 support [\#1761](https://github.com/jrnl-org/jrnl/pull/1761) ([micahellison](https://github.com/micahellison))
- Set new required build fields in the ReadTheDocs config file [\#1803](https://github.com/jrnl-org/jrnl/pull/1803) ([micahellison](https://github.com/micahellison))
- 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:**
- 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:**
- Drop/replace ansiwrap dependency [\#1191](https://github.com/jrnl-org/jrnl/issues/1191)
- Use rich instead of ansiwrap to wrap text [\#1693](https://github.com/jrnl-org/jrnl/pull/1693) ([micahellison](https://github.com/micahellison))
- Update actions/checkout action to v4 [\#1788](https://github.com/jrnl-org/jrnl/pull/1788) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency black to v23.10.1 [\#1811](https://github.com/jrnl-org/jrnl/pull/1811) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency cryptography to v41.0.5 [\#1815](https://github.com/jrnl-org/jrnl/pull/1815) ([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 mkdocs to v1.5.3 [\#1795](https://github.com/jrnl-org/jrnl/pull/1795) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency parse-type to v0.6.2 [\#1762](https://github.com/jrnl-org/jrnl/pull/1762) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency poethepoet to v0.24.1 [\#1806](https://github.com/jrnl-org/jrnl/pull/1806) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pytest to v7.4.3 [\#1816](https://github.com/jrnl-org/jrnl/pull/1816) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency pytest-bdd to v7 [\#1807](https://github.com/jrnl-org/jrnl/pull/1807) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency rich to v13.6.0 [\#1794](https://github.com/jrnl-org/jrnl/pull/1794) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency ruamel.yaml to v0.18.3 [\#1813](https://github.com/jrnl-org/jrnl/pull/1813) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency ruff to v0.1.3 [\#1810](https://github.com/jrnl-org/jrnl/pull/1810) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency tox to v4.11.3 [\#1782](https://github.com/jrnl-org/jrnl/pull/1782) ([renovate[bot]](https://github.com/apps/renovate))
- Update dependency tzlocal to v5.2 [\#1814](https://github.com/jrnl-org/jrnl/pull/1814) ([renovate[bot]](https://github.com/apps/renovate))
**Special thanks:**
- jrnl uses UTC instead of local time for entries in WSL/Ubuntu [\#1607](https://github.com/jrnl-org/jrnl/issues/1607) investigated and reported upstream by [giuseppedandrea](https://github.com/giuseppedandrea)
## [v4.0.1](https://pypi.org/project/jrnl/v4.0.1/) (2023-06-20)
[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))
@ -187,15 +49,25 @@
**Fixed bugs:** **Fixed bugs:**
- 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)) - 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)
- 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))
@ -203,8 +75,11 @@
**Documentation:** **Documentation:**
- Update contributing.md links in documentation [\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking)) - Document template extension behavior [\#1677](https://github.com/jrnl-org/jrnl/issues/1677)
- Fix various typos [\#1718](https://github.com/jrnl-org/jrnl/pull/1718) ([hezhizhen](https://github.com/hezhizhen)) - 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 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))
@ -217,18 +92,17 @@
**Packaging:** **Packaging:**
- 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 cryptography to v40 [\#1710](https://github.com/jrnl-org/jrnl/pull/1710) ([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 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 poethepoet to v0.19.0 [\#1709](https://github.com/jrnl-org/jrnl/pull/1709) ([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)) - 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))
- 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))
## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29) ## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29)
@ -327,10 +201,6 @@
🚨 **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))
@ -353,6 +223,10 @@
- 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))

View file

@ -117,11 +117,6 @@ 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

View file

@ -7,14 +7,24 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
## Installation ## Installation
The easiest way to install `jrnl` is using On Mac and Linux, the easiest way to install `jrnl` is using
[pipx](https://pipx.pypa.io/stable/installation/) [Homebrew](http://brew.sh/):
with [Python](https://www.python.org/) 3.10+:
``` sh
brew install jrnl
```
On other platforms, install `jrnl` using [Python](https://www.python.org/) 3.10+ and [pipx](https://pipxproject.github.io/pipx/):
``` sh ``` sh
pipx install jrnl pipx install jrnl
``` ```
!!! note
`pipx` should be installed through either `brew` or `pip`. Missing dependencies and other issues
may occur when installing `pipx` through `apt` or another package manager. Further installation
instructions can be found in [pipx's documentation](https://pipxproject.github.io/pipx/installation/).
!!! tip !!! tip
Do not use `sudo` while installing `jrnl`. This may lead to path issues. Do not use `sudo` while installing `jrnl`. This may lead to path issues.

View file

@ -14,35 +14,6 @@ program there are some limitations to be aware of.
passwords can be easily circumvented by someone with basic security skills passwords can be easily circumvented by someone with basic security skills
to access to your encrypted `jrnl` file. to access to your encrypted `jrnl` file.
## Plausible deniability
You may be able to hide the contents of your journal behind a layer of encryption,
but if someone has access to your configuration file, then they can figure out that
you have a journal, where that journal file is, and when you last edited it.
With a sufficient power imbalance, someone may be able to force you to unencrypt
it through non-technical means.
## Spying
While `jrnl` can protect against unauthorized access to your journal entries while
it isn't open, it cannot protect you against an unsafe computer/location.
For example:
- Someone installs a keylogger, tracking what you type into your journal.
- Someone watches your screen while you write your entry.
- Someone installs a backdoor into `jrnl` or poisons your journal into revealing your entries.
## Saved Passwords
When creating an encrypted journal, you'll be prompted as to whether or not you
want to "store the password in your keychain." This keychain is accessed using
the [Python keyring library](https://pypi.org/project/keyring/), which has different
behavior depending on your operating system.
In Windows, the keychain is the Windows Credential Manager (WCM), which can't be locked
and can be accessed by any other application running under your username. If this is
a concern for you, you may not want to store your password.
## Shell history ## Shell history
Since you can enter entries from the command line, any tool that logs command Since you can enter entries from the command line, any tool that logs command
@ -227,6 +198,25 @@ vim.api.nvim_create_autocmd( {"BufNewFile","BufReadPre" }, {
Please see `:h <option>` in Neovim for more information about the options mentioned. Please see `:h <option>` in Neovim for more information about the options mentioned.
## Plausible deniability
You may be able to hide the contents of your journal behind a layer of encryption,
but if someone has access to your configuration file, then they can figure out that
you have a journal, where that journal file is, and when you last edited it.
With a sufficient power imbalance, someone may be able to force you to unencrypt
it through non-technical means.
## Saved Passwords
When creating an encrypted journal, you'll be prompted as to whether or not you
want to "store the password in your keychain." This keychain is accessed using
the [Python keyring library](https://pypi.org/project/keyring/), which has different
behavior depending on your operating system.
In Windows, the keychain is the Windows Credential Manager (WCM), which can't be locked
and can be accessed by any other application running under your username. If this is
a concern for you, you may not want to store your password.
## Notice any other risks? ## Notice any other risks?
Please let the maintainers know by [filing an issue on GitHub](https://github.com/jrnl-org/jrnl/issues). Please let the maintainers know by [filing an issue on GitHub](https://github.com/jrnl-org/jrnl/issues).

View file

@ -139,9 +139,3 @@ Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
.rst-content .tip .admonition { .rst-content .tip .admonition {
background: var(--light-blue); background: var(--light-blue);
} }
/* hack to bypass a11y issue with conflicting highlight.css files */
code.language-xml span.hljs-meta span.hljs-string {
color: var(--green) !important;
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -65,7 +65,7 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
<a href="overview">Documentation</a> <a href="overview">Documentation</a>
<a href="installation" class="cta">Get Started</a> <a href="installation" class="cta">Get Started</a>
<a href="http://github.com/jrnl-org/jrnl" title="View on Github">Fork on GitHub</a> <a href="http://github.com/jrnl-org/jrnl" title="View on Github">Fork on GitHub</a>
<a id="twitter-nav" href="https://twitter.com/intent/tweet?text=Collect+your+thoughts+and+notes+without+leaving+the+command+line.+https%3A%2F%2Fjrnl.sh+via+@JrnlSh">Tell your friends on X</a> <a id="twitter-nav" href="https://twitter.com/intent/tweet?text=Collect+your+thoughts+and+notes+without+leaving+the+command+line.+https%3A%2F%2Fjrnl.sh+via+@JrnlSh"><i class="icon twitter"></i>Tell your friends on Twitter</a>
</nav> </nav>
<div class="flex"> <div class="flex">
<section> <section>
@ -103,7 +103,7 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
<footer> <footer>
jrnl is made with love by <a href="https://github.com/jrnl-org/jrnl/graphs/contributors" title="Contributors">many fabulous people</a>. If you need help, <a href="https://github.com/jrnl-org/jrnl/issues/new/choose" title="Open a new issue on Github">submit an issue</a> on Github. jrnl is made with love by <a href="https://github.com/jrnl-org/jrnl/graphs/contributors" title="Contributors">many fabulous people</a>. If you need help, <a href="https://github.com/jrnl-org/jrnl/issues/new/choose" title="Open a new issue on Github">submit an issue</a> on Github.
</footer> </footer>
<script src="https://unpkg.com/typed.js@2.1.0/dist/typed.umd.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.12/typed.min.js"></script>
<script> <script>
new Typed("#typed", { new Typed("#typed", {
strings: [ strings: [

View file

@ -1,2 +1,2 @@
mkdocs>=1.4 mkdocs>=1.4
jinja2==3.1.6 jinja2==3.1.2

View file

@ -1 +1 @@
__version__ = "v4.2.1" __version__ = "v4.0-beta3"

View file

@ -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 :)""" # noqa: E501 And special thanks to Bad Lip Reading for the Yoda joke in the Writing section above :)"""
), ),
) )
@ -214,8 +214,7 @@ 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 " help="Path to template file. Can be a local path, absolute path, or a path relative to $XDG_DATA_HOME/jrnl/templates/",
"relative to $XDG_DATA_HOME/jrnl/templates/",
) )
read_msg = ( read_msg = (
@ -265,17 +264,14 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
reading.add_argument( reading.add_argument(
"-contains", "-contains",
dest="contains", dest="contains",
action="append",
metavar="TEXT", metavar="TEXT",
help="Show entries containing specific text (put quotes around text with " help="Show entries containing specific text (put quotes around text with spaces)",
"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" ' help='Show only entries that match all conditions, like saying "x AND y" (default: OR)',
"(default: OR)",
) )
reading.add_argument( reading.add_argument(
"-starred", "-starred",
@ -294,8 +290,7 @@ 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 " help="Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same effect)",
"effect)",
nargs="?", nargs="?",
type=int, type=int,
) )
@ -313,12 +308,8 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
), ),
) )
search_options_msg = ( search_options_msg = """ These help you do various tasks with the selected entries from your search.
" " # Preserves indentation If used on their own (with no search), they will act on your entire journal"""
"""
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( exporting = parser.add_argument_group(
"Searching Options", textwrap.dedent(search_options_msg) "Searching Options", textwrap.dedent(search_options_msg)
) )
@ -369,8 +360,7 @@ 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 " help="Alias for '--format tags'. Returns a list of all tags and number of occurrences",
"occurrences",
) )
exporting.add_argument( exporting.add_argument(
"--short", "--short",
@ -410,7 +400,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",
@ -440,7 +430,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(

View file

@ -76,17 +76,7 @@ def postconfig_import(args: argparse.Namespace, config: dict, **_) -> int:
journal = open_journal(args.journal_name, config) journal = open_journal(args.journal_name, config)
format = args.export if args.export else "jrnl" format = args.export if args.export else "jrnl"
get_importer(format).import_(journal, args.filename)
if (importer := get_importer(format)) is None:
raise JrnlException(
Message(
MsgText.ImporterNotFound,
MsgStyle.ERROR,
{"format": format},
)
)
importer.import_(journal, args.filename)
return 0 return 0
@ -152,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 to file. If filename is not set, we encrypt the journal file itself.""" """Decrypts into new 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

View file

@ -4,10 +4,12 @@
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
@ -19,10 +21,13 @@ 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 get_config_path from jrnl.path import home_dir
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 = ": "
@ -37,10 +42,9 @@ 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 :return: A single level dict of the configuration keys in dot-notation and their respective desired values
respective desired values
:rtype: dict :rtype: dict
""" """
@ -69,6 +73,31 @@ 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__,
@ -101,12 +130,26 @@ 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
config = config.copy() config = config.copy()
journal_conf = config["journals"].get(journal_name) journal_conf = config["journals"].get(journal_name)
if isinstance(journal_conf, dict): if type(journal_conf) is dict:
# We can override the default config on a by-journal basis # We can override the default config on a by-journal basis
logging.debug( logging.debug(
"Updating configuration with specific journal overrides:\n%s", "Updating configuration with specific journal overrides:\n%s",
@ -181,7 +224,7 @@ def update_config(
"""Updates a config dict with new values - either global if scope is None """Updates a config dict with new values - either global if scope is None
or config['journals'][scope] is just a string pointing to a journal file, or config['journals'][scope] is just a string pointing to a journal file,
or within the scope""" or within the scope"""
if scope and isinstance(config["journals"][scope], dict): if scope and type(config["journals"][scope]) is dict: # Update to journal specific
config["journals"][scope].update(new_config) config["journals"][scope].update(new_config)
elif scope and force_local: # Convert to dict elif scope and force_local: # Convert to dict
config["journals"][scope] = {"journal": config["journals"][scope]} config["journals"][scope] = {"journal": config["journals"][scope]}

View file

@ -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,6 +23,7 @@ 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
@ -34,9 +35,9 @@ if TYPE_CHECKING:
def run(args: "Namespace"): def run(args: "Namespace"):
""" """
Flow: Flow:
1. Run standalone command if it doesn't need config (help, version, etc), then exit 1. Run standalone command if it doesn't require config (help, version, etc), then exit
2. Load config 2. Load config
3. Run standalone command if it does need config (encrypt, decrypt, etc), then exit 3. Run standalone command if it does require 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)
@ -129,6 +130,74 @@ 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
@ -141,22 +210,26 @@ def append_mode(args: "Namespace", config: dict, journal: "Journal", **kwargs) -
""" """
logging.debug("Append mode: starting") logging.debug("Append mode: starting")
template_text = _get_template(args, config) if args.template or config["template"]:
logging.debug(f"Append mode: template CLI arg detected: {args.template}")
if args.text: # 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:
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:
raw = _write_in_editor(config, template_text)
if template_text is not None and raw == template_text: else:
logging.error("Append mode: raw text was the same as the template") raw = _write_in_editor(config)
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")
@ -178,23 +251,6 @@ 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

View file

@ -15,8 +15,6 @@ 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:
@ -58,9 +56,9 @@ def get_text_from_stdin() -> str:
MsgText.WritingEntryStart, MsgText.WritingEntryStart,
MsgStyle.TITLE, MsgStyle.TITLE,
{ {
"how_to_quit": ( "how_to_quit": MsgText.HowToQuitWindows
MsgText.HowToQuitWindows if on_windows() else MsgText.HowToQuitLinux if on_windows()
) else MsgText.HowToQuitLinux
}, },
) )
) )
@ -75,47 +73,3 @@ 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,
},
)
)

View file

@ -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 """Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly.
config file accordingly. This essentially automatically ports jrnl installations This essentially automatically ports jrnl installations if new config parameters are introduced in later
if new config parameters are introduced in later versions. Also checks for versions.
existence of and difference in version number between config dict Also checks for existence of and difference in version number between config dict and current jrnl version,
and current jrnl version, and if so, update the config file accordingly. 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 distss # readline is not included in Windows Active Python and perhaps some other distributions
if sys.modules.get("readline"): if sys.modules.get("readline"):
import readline import readline

View file

@ -7,9 +7,10 @@ import os
import re import re
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import ansiwrap
from jrnl.color import colorize from jrnl.color import colorize
from jrnl.color import highlight_tags_with_background_color from jrnl.color import highlight_tags_with_background_color
from jrnl.output import wrap_with_ansi_colors
if TYPE_CHECKING: if TYPE_CHECKING:
from .Journal import Journal from .Journal import Journal
@ -88,7 +89,7 @@ class Entry:
} }
def __str__(self): def __str__(self):
"""Returns string representation of the entry to be written to journal file.""" """Returns a string representation of the entry to be written into a 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:
@ -128,7 +129,7 @@ class Entry:
columns = 79 columns = 79
# Color date / title and bold title # Color date / title and bold title
title = wrap_with_ansi_colors( title = ansiwrap.fill(
date_str date_str
+ " " + " "
+ highlight_tags_with_background_color( + highlight_tags_with_background_color(
@ -142,17 +143,35 @@ class Entry:
body = highlight_tags_with_background_color( body = highlight_tags_with_background_color(
self, self.body.rstrip(" \n"), self.journal.config["colors"]["body"] self, self.body.rstrip(" \n"), self.journal.config["colors"]["body"]
) )
body_text = [
body = wrap_with_ansi_colors(body, columns - len(indent)) colorize(
if indent: ansiwrap.fill(
# Without explicitly colorizing the indent character, it will lose its line,
# color after a tag appears. columns,
body = "\n".join( initial_indent=indent,
colorize(indent, self.journal.config["colors"]["body"]) + line subsequent_indent=indent,
for line in body.splitlines() drop_whitespace=True,
),
self.journal.config["colors"]["body"],
) )
or indent
for line in body.rstrip(" \n").splitlines()
]
body = colorize(body, self.journal.config["colors"]["body"]) # ansiwrap doesn't handle lines with only the "\n" character and some
# ANSI escapes properly, so we have this hack here to make sure the
# beginning of each line has the indent character and it's colored
# properly. textwrap doesn't have this issue, however, it doesn't wrap
# the strings properly as it counts ANSI escapes as literal characters.
# TL;DR: I'm sorry.
body = "\n".join(
[
colorize(indent, self.journal.config["colors"]["body"]) + line
if not ansiwrap.strip_color(line).startswith(indent)
else line
for line in body_text
]
)
else: else:
title = ( title = (
date_str date_str
@ -214,7 +233,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,
) )

View file

@ -122,8 +122,7 @@ 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 """Searches through sub directories starting with journal_path and find all text files that look like entries"""
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)

View file

@ -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 and parses it into a list of Entries """Opens the journal file defined in the config 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 conversion to text.""" """Confirms that the jrnl is still parsed correctly after being dumped 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,9 +225,8 @@ 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 # Astute reader: should the following line leave you as puzzled as me the first time
# time I came across this construction, worry not and embrace the ensuing moment # I came across this construction, worry not and embrace the ensuing moment of enlightment.
# 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}
@ -246,7 +245,7 @@ class Journal:
exclude_starred=False, exclude_starred=False,
exclude_tagged=False, exclude_tagged=False,
strict=False, strict=False,
contains=[], contains=None,
exclude=[], exclude=[],
): ):
"""Removes all entries from the journal that don't match the filter. """Removes all entries from the journal that don't match the filter.
@ -276,7 +275,7 @@ class Journal:
return 0 < len([tag for tag in tags if tag in excluded_tags]) return 0 < len([tag for tag in tags if tag in excluded_tags])
if contains: if contains:
contains_lower = [substring.casefold() for substring in contains] contains_lower = contains.casefold()
# Create datetime object for comparison below # Create datetime object for comparison below
# this approach allows various formats # this approach allows various formats
@ -298,20 +297,8 @@ class Journal:
and ( and (
not contains not contains
or ( or (
strict contains_lower in entry.title.casefold()
and all( or contains_lower in entry.body.casefold()
substring in entry.title.casefold()
or substring in entry.body.casefold()
for substring in contains_lower
)
)
or (
not strict
and any(
substring in entry.title.casefold()
or substring in entry.body.casefold()
for substring in contains_lower
)
) )
) )
] ]
@ -356,8 +343,7 @@ 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 If a date is given, it will parse and use this, otherwise scan for a date in the input first.
the input first.
""" """
raw = raw.replace("\\n ", "\n").replace("\\n", "\n") raw = raw.replace("\\n ", "\n").replace("\\n", "\n")

View file

@ -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") return self.value.get("box_title", None)

View file

@ -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 to display entries? (You can always change this later) Do you want jrnl to use colors when displaying 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,12 +105,16 @@ class MsgText(Enum):
KeyboardInterruptMsg = "Aborted by user" KeyboardInterruptMsg = "Aborted by user"
CantReadTemplate = """ CantReadTemplateGlobalConfig = """
Unable to find a template file {template_path}. Could not read template file defined in config:
{global_template_path}
"""
The following paths were checked: CantReadTemplateCLIArg = """
* {jrnl_template_dir}{template_path} Unable to find a template file based on the passed arg, and no global template was detected.
* {actual_template_path} The following filepaths were checked:
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}"
@ -266,11 +270,6 @@ class MsgText(Enum):
{count} imported to {journal_name} journal {count} imported to {journal_name} journal
""" """
ImporterNotFound = """
No importer found for file type '{format}'.
'{format}' is likely to be an export-only format.
"""
# --- Color --- # # --- Color --- #
InvalidColor = "{key} set to invalid color: {color}" InvalidColor = "{key} set to invalid color: {color}"

View file

@ -39,10 +39,7 @@ def journal_list_to_yaml(journal_list: dict) -> str:
from ruamel.yaml import YAML from ruamel.yaml import YAML
output = StringIO() output = StringIO()
dumper = YAML() YAML().dump(journal_list, output)
dumper.width = 1000
dumper.dump(journal_list, output)
return output.getvalue() return output.getvalue()
@ -131,12 +128,3 @@ def format_msg_text(msg: Message) -> Text:
text = textwrap.dedent(text) text = textwrap.dedent(text)
text = text.strip() text = text.strip()
return Text(text) return Text(text)
def wrap_with_ansi_colors(text: str, width: int) -> str:
richtext = Text.from_ansi(text, no_wrap=False, tab_size=None)
console = Console(width=width, force_terminal=True)
with console.capture() as capture:
console.print(richtext, sep="", end="")
return capture.get()

View file

@ -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") overrides = vars(args).get("config_override", None)
if not overrides: if not overrides:
return base_config return base_config
@ -56,8 +56,7 @@ 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 nodes (list): Vector of override keys; the length of the vector indicates tree depth
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]

View file

@ -2,19 +2,6 @@
# 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:
@ -27,46 +14,3 @@ 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)

View file

@ -3,7 +3,6 @@
from typing import Type from typing import Type
from jrnl.plugins.calendar_heatmap_exporter import CalendarHeatmapExporter
from jrnl.plugins.dates_exporter import DatesExporter from jrnl.plugins.dates_exporter import DatesExporter
from jrnl.plugins.fancy_exporter import FancyExporter from jrnl.plugins.fancy_exporter import FancyExporter
from jrnl.plugins.jrnl_importer import JRNLImporter from jrnl.plugins.jrnl_importer import JRNLImporter
@ -15,15 +14,14 @@ from jrnl.plugins.xml_exporter import XMLExporter
from jrnl.plugins.yaml_exporter import YAMLExporter from jrnl.plugins.yaml_exporter import YAMLExporter
__exporters = [ __exporters = [
CalendarHeatmapExporter,
DatesExporter,
FancyExporter,
JSONExporter, JSONExporter,
MarkdownExporter, MarkdownExporter,
TagExporter, TagExporter,
DatesExporter,
TextExporter, TextExporter,
XMLExporter, XMLExporter,
YAMLExporter, YAMLExporter,
FancyExporter,
] ]
__importers = [JRNLImporter] __importers = [JRNLImporter]

View file

@ -1,117 +0,0 @@
# Copyright © 2012-2023 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
import calendar
from datetime import datetime
from typing import TYPE_CHECKING
from rich import box
from rich.align import Align
from rich.columns import Columns
from rich.console import Console
from rich.table import Table
from rich.text import Text
from jrnl.plugins.text_exporter import TextExporter
from jrnl.plugins.util import get_journal_frequency_nested
if TYPE_CHECKING:
from jrnl.journals import Entry
from jrnl.journals import Journal
from jrnl.plugins.util import NestedDict
class CalendarHeatmapExporter(TextExporter):
"""This Exporter displays a calendar heatmap of the journaling frequency."""
names = ["calendar", "heatmap"]
extension = "cal"
@classmethod
def export_entry(cls, entry: "Entry"):
raise NotImplementedError
@classmethod
def print_calendar_heatmap(cls, journal_frequency: "NestedDict") -> str:
"""Returns a string representation of the calendar heatmap."""
console = Console()
cal = calendar.Calendar()
curr_year = datetime.now().year
curr_month = datetime.now().month
curr_day = datetime.now().day
hit_first_entry = False
with console.capture() as capture:
for year, month_journaling_freq in journal_frequency.items():
year_calendar = []
for month in range(1, 13):
if month > curr_month and year == curr_year:
break
entries_this_month = sum(month_journaling_freq[month].values())
if not hit_first_entry and entries_this_month > 0:
hit_first_entry = True
if entries_this_month == 0 and not hit_first_entry:
continue
elif entries_this_month == 0:
entry_msg = "No entries"
elif entries_this_month == 1:
entry_msg = "1 entry"
else:
entry_msg = f"{entries_this_month} entries"
table = Table(
title=f"{calendar.month_name[month]} {year} ({entry_msg})",
title_style="bold green",
box=box.SIMPLE_HEAVY,
padding=0,
)
for week_day in cal.iterweekdays():
table.add_column(
"{:.3}".format(calendar.day_name[week_day]), justify="right"
)
month_days = cal.monthdayscalendar(year, month)
for weekdays in month_days:
days = []
for _, day in enumerate(weekdays):
if day == 0: # Not a part of this month, just filler.
day_label = Text(str(day or ""), style="white")
elif (
day > curr_day
and month == curr_month
and year == curr_year
):
break
else:
journal_frequency_for_day = (
month_journaling_freq[month][day] or 0
)
day = str(day)
# TODO: Make colors configurable?
if journal_frequency_for_day == 0:
day_label = Text(day, style="red on black")
elif journal_frequency_for_day == 1:
day_label = Text(day, style="black on yellow")
elif journal_frequency_for_day == 2:
day_label = Text(day, style="black on green")
else:
day_label = Text(day, style="black on white")
days.append(day_label)
table.add_row(*days)
year_calendar.append(Align.center(table))
# Print year header line
console.rule(str(year))
console.print()
# Print calendar
console.print(Columns(year_calendar, padding=1, expand=True))
return capture.get()
@classmethod
def export_journal(cls, journal: "Journal"):
"""Returns dates and their frequencies for an entire journal."""
journal_entry_date_frequency = get_journal_frequency_nested(journal)
return cls.print_calendar_heatmap(journal_entry_date_frequency)

View file

@ -1,10 +1,10 @@
# Copyright © 2012-2023 jrnl contributors # Copyright © 2012-2023 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from collections import Counter
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from jrnl.plugins.text_exporter import TextExporter from jrnl.plugins.text_exporter import TextExporter
from jrnl.plugins.util import get_journal_frequency_one_level
if TYPE_CHECKING: if TYPE_CHECKING:
from jrnl.journals import Entry from jrnl.journals import Entry
@ -24,6 +24,10 @@ class DatesExporter(TextExporter):
@classmethod @classmethod
def export_journal(cls, journal: "Journal") -> str: def export_journal(cls, journal: "Journal") -> str:
"""Returns dates and their frequencies for an entire journal.""" """Returns dates and their frequencies for an entire journal."""
date_counts = get_journal_frequency_one_level(journal) date_counts = Counter()
for entry in journal.entries:
# entry.date.date() gets date without time
date = str(entry.date.date())
date_counts[date] += 1
result = "\n".join(f"{date}, {count}" for date, count in date_counts.items()) result = "\n".join(f"{date}, {count}" for date, count in date_counts.items())
return result return result

View file

@ -18,7 +18,7 @@ if TYPE_CHECKING:
class FancyExporter(TextExporter): class FancyExporter(TextExporter):
"""This Exporter converts entries and journals into text with unicode boxes.""" """This Exporter can convert entries and journals into text with unicode box drawing characters."""
names = ["fancy", "boxed"] names = ["fancy", "boxed"]
extension = "txt" extension = "txt"

View file

@ -12,7 +12,7 @@ if TYPE_CHECKING:
class TagExporter(TextExporter): class TagExporter(TextExporter):
"""This Exporter lists the tags for entries and journals.""" """This Exporter can lists the tags for entries and journals, exported as a plain text file."""
names = ["tags"] names = ["tags"]
extension = "tags" extension = "tags"

View file

@ -1,26 +1,16 @@
# Copyright © 2012-2023 jrnl contributors # Copyright © 2012-2023 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from collections import Counter
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from jrnl.journals import Journal from jrnl.journals import Journal
class NestedDict(dict):
"""https://stackoverflow.com/a/74873621/8740440"""
def __missing__(self, x):
self[x] = NestedDict()
return self[x]
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 # I came across this construction, worry not and embrace the ensuing moment of enlightment.
# 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}
@ -38,26 +28,3 @@ def oxford_list(lst: list) -> str:
return lst[0] + " or " + lst[1] return lst[0] + " or " + lst[1]
else: else:
return ", ".join(lst[:-1]) + ", or " + lst[-1] return ", ".join(lst[:-1]) + ", or " + lst[-1]
def get_journal_frequency_nested(journal: "Journal") -> NestedDict:
"""Returns a NestedDict of the form {year: {month: {day: count}}}"""
journal_frequency = NestedDict()
for entry in journal.entries:
date = entry.date.date()
if date.day in journal_frequency[date.year][date.month]:
journal_frequency[date.year][date.month][date.day] += 1
else:
journal_frequency[date.year][date.month][date.day] = 1
return journal_frequency
def get_journal_frequency_one_level(journal: "Journal") -> Counter:
"""Returns a Counter of the form {date (YYYY-MM-DD): count}"""
date_counts = Counter()
for entry in journal.entries:
# entry.date.date() gets date without time
date = str(entry.date.date())
date_counts[date] += 1
return date_counts

View file

@ -18,15 +18,14 @@ if TYPE_CHECKING:
class YAMLExporter(TextExporter): class YAMLExporter(TextExporter):
"""This Exporter converts entries and journals into Markdown formatted text with """This Exporter can convert entries and journals into Markdown formatted text with YAML front matter."""
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 an entry, with YAML front matter.""" """Returns a markdown representation of a single 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))
@ -118,14 +117,7 @@ class YAMLExporter(TextExporter):
# source directory is entry.journal.config['journal'] # source directory is entry.journal.config['journal']
# output directory is...? # output directory is...?
return ( return "{start}\ntitle: {title}\ndate: {date}\nstarred: {starred}\ntags: {tags}\n{dayone}body: |{body}{end}".format(
"{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,

View file

@ -9,11 +9,14 @@ DEFAULT_PAST = datetime.datetime(FAKE_YEAR, 1, 1, 0, 0)
def __get_pdt_calendar(): def __get_pdt_calendar():
import parsedatetime as pdt try:
import parsedatetime.parsedatetime_consts as pdt
except ImportError:
import parsedatetime as pdt
consts = pdt.Constants(usePyICU=False) consts = pdt.Constants(usePyICU=False)
consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday
calendar = pdt.Calendar(consts, version=pdt.VERSION_CONTEXT_STYLE) calendar = pdt.Calendar(consts)
return calendar return calendar
@ -31,18 +34,14 @@ 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 # Don't try to parse anything with 6 or fewer characters and was parsed from the existing journal.
# existing journal. It's probably a markdown footnote # It's probably a markdown footnote
if len(date_str) <= 6 and bracketed: if len(date_str) <= 6 and bracketed:
return None return None
default_date = DEFAULT_FUTURE if inclusive else DEFAULT_PAST default_date = DEFAULT_FUTURE if inclusive else DEFAULT_PAST
date = None date = None
year_present = False year_present = False
hasTime = False
hasDate = False
while not date: while not date:
try: try:
from dateutil.parser import parse as dateparse from dateutil.parser import parse as dateparse
@ -54,8 +53,7 @@ def parse(
) )
else: else:
year_present = True year_present = True
hasTime = not (date.hour == date.minute == 0) flag = 1 if date.hour == date.minute == 0 else 2
hasDate = True
date = date.timetuple() date = date.timetuple()
except Exception as e: except Exception as e:
if e.args[0] == "day is out of range for month": if e.args[0] == "day is out of range for month":
@ -63,11 +61,9 @@ def parse(
default_date = datetime.datetime(y, m, d - 1, H, M, S) default_date = datetime.datetime(y, m, d - 1, H, M, S)
else: else:
calendar = __get_pdt_calendar() calendar = __get_pdt_calendar()
date, parse_context = calendar.parse(date_str) date, flag = calendar.parse(date_str)
hasTime = parse_context.hasTime
hasDate = parse_context.hasDate
if not hasDate and not hasTime: if not flag: # Oops, unparsable.
try: # Try and parse this as a single year try: # Try and parse this as a single year
year = int(date_str) year = int(date_str)
return datetime.datetime(year, 1, 1) return datetime.datetime(year, 1, 1)
@ -76,8 +72,8 @@ def parse(
except TypeError: except TypeError:
return None return None
if hasDate and not hasTime: if flag == 1: # Date found, but no time. Use the default time.
date = datetime.datetime( # Use the default time date = datetime.datetime(
*date[:3], *date[:3],
hour=23 if inclusive else default_hour or 0, hour=23 if inclusive else default_hour or 0,
minute=59 if inclusive else default_minute or 0, minute=59 if inclusive else default_minute or 0,
@ -86,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 # Ugly heuristic: if the date is more than 4 weeks in the future, we got the year wrong.
# wrong. Rather than this, we would like to see parsedatetime patched so we can # Rather than this, we would like to see parsedatetime patched so we can tell it to prefer
# tell it to prefer past dates # 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)

150
package-lock.json generated
View file

@ -5,7 +5,7 @@
"packages": { "packages": {
"": { "": {
"devDependencies": { "devDependencies": {
"pa11y-ci": "3.1.0" "pa11y-ci": "3.0.1"
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
@ -25,6 +25,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@ -68,9 +74,9 @@
} }
}, },
"node_modules/axe-core": { "node_modules/axe-core": {
"version": "4.2.4", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.2.4.tgz", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz",
"integrity": "sha512-9AiDKFKUCWEQm1Kj4lcq7KFavLqSXdf2m/zJo+NVh4VXlW5iwXRJ6alkKmipCyYorsRnqsICH9XLubP1jBF+Og==", "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@ -519,6 +525,19 @@
"node": ">= 0.4.0" "node": ">= 0.4.0"
} }
}, },
"node_modules/hogan.js": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
"integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
"dev": true,
"dependencies": {
"mkdirp": "0.3.0",
"nopt": "1.0.10"
},
"bin": {
"hulk": "bin/hulk"
}
},
"node_modules/hoopy": { "node_modules/hoopy": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@ -665,6 +684,16 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/mkdirp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
"integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/mkdirp-classic": { "node_modules/mkdirp-classic": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@ -677,15 +706,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"node_modules/mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"dev": true,
"bin": {
"mustache": "bin/mustache"
}
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.7", "version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@ -719,6 +739,21 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dev": true,
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "*"
}
},
"node_modules/nth-check": { "node_modules/nth-check": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@ -795,18 +830,18 @@
} }
}, },
"node_modules/pa11y": { "node_modules/pa11y": {
"version": "6.2.3", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/pa11y/-/pa11y-6.2.3.tgz", "resolved": "https://registry.npmjs.org/pa11y/-/pa11y-6.1.1.tgz",
"integrity": "sha512-69JoUlfW2QVmrgQAm+17XBxIvmd1u0ImFBYIHPyjC61CzAkmxO3kkbqDVxIcl0OKLvAMYSMbvfCH8kMFE9xsbg==", "integrity": "sha512-2NzqA3D9CUlDWj8WuOI4fM2P0qM1d/IUxsRRpzCOfDT5eMR1oEgmUwW2TAk+f90ff/GVck0BewdYT4et4BANew==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"axe-core": "~4.2.1", "axe-core": "^4.0.2",
"bfj": "~7.0.2", "bfj": "~7.0.2",
"commander": "~8.0.0", "commander": "~8.0.0",
"envinfo": "~7.8.1", "envinfo": "~7.8.1",
"html_codesniffer": "~2.5.1", "hogan.js": "^3.0.2",
"html_codesniffer": "^2.5.1",
"kleur": "~4.1.4", "kleur": "~4.1.4",
"mustache": "~4.2.0",
"node.extend": "~2.0.2", "node.extend": "~2.0.2",
"p-timeout": "~4.1.0", "p-timeout": "~4.1.0",
"puppeteer": "~9.1.1", "puppeteer": "~9.1.1",
@ -820,19 +855,19 @@
} }
}, },
"node_modules/pa11y-ci": { "node_modules/pa11y-ci": {
"version": "3.1.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/pa11y-ci/-/pa11y-ci-3.1.0.tgz", "resolved": "https://registry.npmjs.org/pa11y-ci/-/pa11y-ci-3.0.1.tgz",
"integrity": "sha512-1WBGBMq0dYtZ+N/SH/AcnFSsT6sZ2w27d8Z/5XHJWSELeX8Qhh4yX5f0drb7crwjt7ugKSo4A7eEF9RbMB0LYg==", "integrity": "sha512-DUtEIhEG3Ofds7qRuplq0DdCb9doILRlzcRctFNzo4QUNmVy4iZfM3u51A9cqoPo2irCJZoo5BzfiFrcriY2IQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"async": "~2.6.4", "async": "~2.6.3",
"cheerio": "~1.0.0-rc.10", "cheerio": "~1.0.0-rc.10",
"commander": "~6.2.1", "commander": "~6.2.1",
"globby": "~6.1.0", "globby": "~6.1.0",
"kleur": "~4.1.4", "kleur": "~4.1.4",
"lodash": "~4.17.21", "lodash": "~4.17.21",
"node-fetch": "~2.6.1", "node-fetch": "~2.6.1",
"pa11y": "^6.2.3", "pa11y": "~6.1.0",
"protocolify": "~3.0.0", "protocolify": "~3.0.0",
"puppeteer": "~9.1.1", "puppeteer": "~9.1.1",
"wordwrap": "~1.0.0" "wordwrap": "~1.0.0"
@ -841,7 +876,7 @@
"pa11y-ci": "bin/pa11y-ci.js" "pa11y-ci": "bin/pa11y-ci.js"
}, },
"engines": { "engines": {
"node": ">= 12" "node": ">=12"
} }
}, },
"node_modules/pa11y/node_modules/commander": { "node_modules/pa11y/node_modules/commander": {
@ -1235,6 +1270,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"agent-base": { "agent-base": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@ -1269,9 +1310,9 @@
} }
}, },
"axe-core": { "axe-core": {
"version": "4.2.4", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.2.4.tgz", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz",
"integrity": "sha512-9AiDKFKUCWEQm1Kj4lcq7KFavLqSXdf2m/zJo+NVh4VXlW5iwXRJ6alkKmipCyYorsRnqsICH9XLubP1jBF+Og==", "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==",
"dev": true "dev": true
}, },
"balanced-match": { "balanced-match": {
@ -1592,6 +1633,16 @@
"function-bind": "^1.1.1" "function-bind": "^1.1.1"
} }
}, },
"hogan.js": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
"integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
"dev": true,
"requires": {
"mkdirp": "0.3.0",
"nopt": "1.0.10"
}
},
"hoopy": { "hoopy": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@ -1693,6 +1744,12 @@
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"mkdirp": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
"integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
"dev": true
},
"mkdirp-classic": { "mkdirp-classic": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@ -1705,12 +1762,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"dev": true
},
"node-fetch": { "node-fetch": {
"version": "2.6.7", "version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@ -1730,6 +1781,15 @@
"is": "^3.2.1" "is": "^3.2.1"
} }
}, },
"nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dev": true,
"requires": {
"abbrev": "1"
}
},
"nth-check": { "nth-check": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@ -1785,18 +1845,18 @@
"dev": true "dev": true
}, },
"pa11y": { "pa11y": {
"version": "6.2.3", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/pa11y/-/pa11y-6.2.3.tgz", "resolved": "https://registry.npmjs.org/pa11y/-/pa11y-6.1.1.tgz",
"integrity": "sha512-69JoUlfW2QVmrgQAm+17XBxIvmd1u0ImFBYIHPyjC61CzAkmxO3kkbqDVxIcl0OKLvAMYSMbvfCH8kMFE9xsbg==", "integrity": "sha512-2NzqA3D9CUlDWj8WuOI4fM2P0qM1d/IUxsRRpzCOfDT5eMR1oEgmUwW2TAk+f90ff/GVck0BewdYT4et4BANew==",
"dev": true, "dev": true,
"requires": { "requires": {
"axe-core": "~4.2.1", "axe-core": "^4.0.2",
"bfj": "~7.0.2", "bfj": "~7.0.2",
"commander": "~8.0.0", "commander": "~8.0.0",
"envinfo": "~7.8.1", "envinfo": "~7.8.1",
"html_codesniffer": "~2.5.1", "hogan.js": "^3.0.2",
"html_codesniffer": "^2.5.1",
"kleur": "~4.1.4", "kleur": "~4.1.4",
"mustache": "~4.2.0",
"node.extend": "~2.0.2", "node.extend": "~2.0.2",
"p-timeout": "~4.1.0", "p-timeout": "~4.1.0",
"puppeteer": "~9.1.1", "puppeteer": "~9.1.1",
@ -1812,19 +1872,19 @@
} }
}, },
"pa11y-ci": { "pa11y-ci": {
"version": "3.1.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/pa11y-ci/-/pa11y-ci-3.1.0.tgz", "resolved": "https://registry.npmjs.org/pa11y-ci/-/pa11y-ci-3.0.1.tgz",
"integrity": "sha512-1WBGBMq0dYtZ+N/SH/AcnFSsT6sZ2w27d8Z/5XHJWSELeX8Qhh4yX5f0drb7crwjt7ugKSo4A7eEF9RbMB0LYg==", "integrity": "sha512-DUtEIhEG3Ofds7qRuplq0DdCb9doILRlzcRctFNzo4QUNmVy4iZfM3u51A9cqoPo2irCJZoo5BzfiFrcriY2IQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"async": "~2.6.4", "async": "~2.6.3",
"cheerio": "~1.0.0-rc.10", "cheerio": "~1.0.0-rc.10",
"commander": "~6.2.1", "commander": "~6.2.1",
"globby": "~6.1.0", "globby": "~6.1.0",
"kleur": "~4.1.4", "kleur": "~4.1.4",
"lodash": "~4.17.21", "lodash": "~4.17.21",
"node-fetch": "~2.6.1", "node-fetch": "~2.6.1",
"pa11y": "^6.2.3", "pa11y": "~6.1.0",
"protocolify": "~3.0.0", "protocolify": "~3.0.0",
"puppeteer": "~9.1.1", "puppeteer": "~9.1.1",
"wordwrap": "~1.0.0" "wordwrap": "~1.0.0"

View file

@ -1,5 +1,5 @@
{ {
"devDependencies": { "devDependencies": {
"pa11y-ci": "3.1.0" "pa11y-ci": "3.0.1"
} }
} }

2015
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "jrnl" name = "jrnl"
version = "v4.2.1" version = "v4.0-beta3"
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>",
@ -27,32 +27,38 @@ classifiers = [
"Funding" = "https://opencollective.com/jrnl" "Funding" = "https://opencollective.com/jrnl"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10.0, <3.14" python = ">=3.10.0, <3.13"
ansiwrap = "^0.8.4"
colorama = ">=0.4" # https://github.com/tartley/colorama/blob/master/CHANGELOG.rst colorama = ">=0.4" # https://github.com/tartley/colorama/blob/master/CHANGELOG.rst
cryptography = ">=3.0" # https://cryptography.io/en/latest/api-stability.html cryptography = ">=3.0" # https://cryptography.io/en/latest/api-stability.html
keyring = ">=21.0" # https://github.com/jaraco/keyring#integration 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.22" "ruamel.yaml" = "^0.17.21"
rich = ">=14.0.0, <14.1.0" rich = ">=12.2.0, <14.0.0"
# dayone-only deps # dayone-only deps
tzlocal = ">=4.0" # https://github.com/regebro/tzlocal/blob/master/CHANGES.txt tzlocal = ">=4.0" # https://github.com/regebro/tzlocal/blob/master/CHANGES.txt
[tool.poetry.group.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 = "*"
pytest = ">=8.1" pytest = ">=6.2"
pytest-bdd = ">=8.0" 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 = "*"
@ -82,18 +88,18 @@ test-run = [
# Groups of tasks # Groups of tasks
format.default_item_type = "cmd" format.default_item_type = "cmd"
format.sequence = [ format.sequence = [
"ruff check . --select I --fix", # equivalent to "isort ." "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",
"ruff --version", "flakeheaven --version",
"ruff check .", "flakeheaven plugins",
"black --version", "flakeheaven lint",
"black --check ."
] ]
test = [ test = [
@ -101,6 +107,11 @@ 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 = [
@ -121,40 +132,34 @@ addopts = [
filterwarnings = [ filterwarnings = [
"ignore::DeprecationWarning", "ignore::DeprecationWarning",
"ignore:Flag style will be deprecated in.*",
"ignore:[WinError 32].*", "ignore:[WinError 32].*",
"ignore:[WinError 5].*" "ignore:[WinError 5].*"
] ]
[tool.ruff] [tool.flakeheaven]
line-length = 88 max_line_length = 88
target-version = "py310"
# https://beta.ruff.rs/docs/rules/
lint.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.ruff.lint.isort] [tool.flakeheaven.plugins]
force-single-line = true "py*" = ["+*"]
known-first-party = ["jrnl", "tests"] pycodestyle = [
"-E101",
"-E111", "-E114", "-E115", "-E116", "-E117",
"-E12*",
"-E13*",
"-E2*",
"-E3*",
"-E401",
"-E5*",
"-E70",
"-W1*", "-W2*", "-W3*", "-W5*",
]
"flake8-*" = ["+*"]
flake8-black = ["-BLK901"]
[tool.ruff.lint.per-file-ignores] [tool.flakeheaven.exceptions."jrnl/journals/__init__.py"]
"__init__.py" = ["F401"] # unused imports pyflakes = ["-F401"]
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
@ -169,8 +174,8 @@ isolated_build = True
[testenv] [testenv]
deps = deps =
pytest >=8.1 pytest >= 6.2
pytest-bdd >=8.0 pytest-bdd >=6.0
pytest-xdist >=2.5.0 pytest-xdist >=2.5.0
parse-type >=0.6.0 parse-type >=0.6.0
toml >=0.10 toml >=0.10

View file

@ -5,13 +5,6 @@
# Required # Required
version: 2 version: 2
# Set the OS
build:
os: ubuntu-22.04
tools:
python: "3"
# Build documentation in the docs/ directory # Build documentation in the docs/ directory
mkdocs: mkdocs:
configuration: mkdocs.yml configuration: mkdocs.yml

View file

@ -41,14 +41,7 @@ def generate_pa11y_config_from_sitemap():
urls += [url["loc"] for url in xml_sitemap["urlset"]["url"]] urls += [url["loc"] for url in xml_sitemap["urlset"]["url"]]
with open(CONFIG_FILENAME, "w") as f: with open(CONFIG_FILENAME, "w") as f:
f.write( f.write(json.dumps({"urls": urls}))
json.dumps(
{
"defaults": {"chromeLaunchConfig": {"args": ["--no-sandbox"]}},
"urls": urls,
}
)
)
def output_file(file): def output_file(file):

View file

@ -8,20 +8,16 @@ Feature: Test combinations of edit, change-time, and delete
And we write nothing to the editor if opened And we write nothing to the editor if opened
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' --edit" and enter When we run "jrnl --change-time '2022-04-23 10:30' --edit" and enter
"""
Y Y
N N
Y Y
"""
Then the error output should contain "No text received from editor. Were you trying to delete all the entries?" Then the error output should contain "No text received from editor. Were you trying to delete all the entries?"
And the editor should have been called And the editor should have been called
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -33,16 +29,12 @@ Feature: Test combinations of edit, change-time, and delete
Scenario Outline: --delete with --edit deletes selected entries Scenario Outline: --delete with --edit deletes selected entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we append to the editor if opened And we append to the editor if opened
"""
[2023-02-21 10:32] Here is a new entry [2023-02-21 10:32] Here is a new entry
"""
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --delete --edit" and enter When we run "jrnl --delete --edit" and enter
"""
Y Y
N N
Y Y
"""
Then the editor should have been called Then the editor should have been called
And the error output should contain "3 entries found" And the error output should contain "3 entries found"
And the error output should contain "2 entries deleted" And the error output should contain "2 entries deleted"
@ -50,10 +42,8 @@ Feature: Test combinations of edit, change-time, and delete
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the error output should contain "2 entries found" Then the error output should contain "2 entries found"
And the output should be And the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2023-02-21 10:32 Here is a new entry 2023-02-21 10:32 Here is a new entry
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -67,23 +57,19 @@ Feature: Test combinations of edit, change-time, and delete
And we use the password "test" if prompted And we use the password "test" if prompted
# --change-time is asked first, then --delete # --change-time is asked first, then --delete
When we run "jrnl --change-time '2022-04-23 10:30' --delete" and enter When we run "jrnl --change-time '2022-04-23 10:30' --delete" and enter
"""
N N
N N
Y Y
Y Y
N N
N N
"""
Then the error output should contain "3 entries found" Then the error output should contain "3 entries found"
And the error output should contain "1 entry deleted" And the error output should contain "1 entry deleted"
And the error output should contain "1 entry modified" And the error output should contain "1 entry modified"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -95,30 +81,24 @@ Feature: Test combinations of edit, change-time, and delete
Scenario Outline: Combining --change-time and --delete and --edit affects appropriate entries Scenario Outline: Combining --change-time and --delete and --edit affects appropriate entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we append to the editor if opened And we append to the editor if opened
""" [2023-02-21 10:32] Here is a new entry
[2023-02-21 10:32] Here is a new entry
"""
And we use the password "test" if prompted And we use the password "test" if prompted
# --change-time is asked first, then --delete, then --edit # --change-time is asked first, then --delete, then --edit
When we run "jrnl --change-time '2022-04-23 10:30' --delete --edit" and enter When we run "jrnl --change-time '2022-04-23 10:30' --delete --edit" and enter
"""
N N
Y Y
Y Y
Y Y
Y Y
N N
"""
Then the error output should contain "3 entries found" Then the error output should contain "3 entries found"
And the error output should contain "2 entries deleted" And the error output should contain "2 entries deleted"
And the error output should contain "1 entry modified" And the error output should contain "1 entry modified" # only 1, because the other was deleted
And the error output should contain "1 entry added" And the error output should contain "1 entry added" # by edit
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
2023-02-21 10:32 Here is a new entry 2023-02-21 10:32 Here is a new entry
"""
Examples: Configs Examples: Configs
| config_file | | config_file |

View file

@ -8,18 +8,14 @@ Feature: Change entry times in journal
When we run "jrnl -1" When we run "jrnl -1"
Then the output should contain "2020-09-24 09:14 The third entry finally" Then the output should contain "2020-09-24 09:14 The third entry finally"
When we run "jrnl -1 --change-time '2022-04-23 10:30'" and enter When we run "jrnl -1 --change-time '2022-04-23 10:30'" and enter
"""
Y Y
"""
Then the error output should contain "1 entry modified" Then the error output should contain "1 entry modified"
And the error output should not contain "deleted" And the error output should not contain "deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -33,26 +29,20 @@ Feature: Change entry times in journal
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --short" When we run "jrnl --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
When we run "jrnl --change-time '2022-04-23 10:30'" and enter When we run "jrnl --change-time '2022-04-23 10:30'" and enter
"""
Y Y
N N
Y Y
"""
Then the error output should contain "3 entries found" Then the error output should contain "3 entries found"
And the error output should contain "2 entries modified" And the error output should contain "2 entries modified"
When we run "jrnl --short" When we run "jrnl --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -68,18 +58,14 @@ Feature: Change entry times in journal
When we run "jrnl -1" When we run "jrnl -1"
Then the output should contain "2020-09-24 09:14 The third entry finally" Then the output should contain "2020-09-24 09:14 The third entry finally"
When we run "jrnl -1 --change-time '2023-02-21 10:30'" and enter When we run "jrnl -1 --change-time '2023-02-21 10:30'" and enter
"""
N N
"""
Then the error output should not contain "modified" Then the error output should not contain "modified"
And the error output should not contain "deleted" And the error output should not contain "deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -97,11 +83,9 @@ Feature: Change entry times in journal
And the error output should not contain "entries deleted" And the error output should not contain "entries deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -115,18 +99,14 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' @ipsum" and enter When we run "jrnl --change-time '2022-04-23 10:30' @ipsum" and enter
"""
Y Y
"""
Then the error output should contain "1 entry found" Then the error output should contain "1 entry found"
And the error output should contain "1 entry modified" And the error output should contain "1 entry modified"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -140,17 +120,13 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' @ipsum @tagthree" and enter When we run "jrnl --change-time '2022-04-23 10:30' @ipsum @tagthree" and enter
"""
Y Y
Y Y
"""
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -164,16 +140,12 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' -and @tagone @tagtwo" and enter When we run "jrnl --change-time '2022-04-23 10:30' -and @tagone @tagtwo" and enter
"""
Y Y
"""
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -187,16 +159,12 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' @tagone -not @ipsum" and enter When we run "jrnl --change-time '2022-04-23 10:30' @tagone -not @ipsum" and enter
"""
Y Y
"""
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -210,16 +178,12 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' -from 2020-09-01" and enter When we run "jrnl --change-time '2022-04-23 10:30' -from 2020-09-01" and enter
"""
Y Y
"""
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 The third entry finally after weeks without writing. 2022-04-23 10:30 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -233,17 +197,13 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' -to 2020-08-31" and enter When we run "jrnl --change-time '2022-04-23 10:30' -to 2020-08-31" and enter
"""
Y Y
Y Y
"""
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.
2022-04-23 10:30 A second entry in what I hope to be a long series. 2022-04-23 10:30 A second entry in what I hope to be a long series.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -257,16 +217,12 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' -starred" and enter When we run "jrnl --change-time '2022-04-23 10:30' -starred" and enter
"""
Y Y
"""
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
2022-04-23 10:30 A second entry in what I hope to be a long series. 2022-04-23 10:30 A second entry in what I hope to be a long series.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -280,17 +236,13 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time '2022-04-23 10:30' -contains dignissim" and enter When we run "jrnl --change-time '2022-04-23 10:30' -contains dignissim" and enter
"""
Y Y
"""
Then the error output should contain "1 entry modified" Then the error output should contain "1 entry modified"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -304,18 +256,14 @@ Feature: Change entry times in journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --change-time" and enter When we run "jrnl --change-time" and enter
"""
N N
N N
N N
"""
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |

View file

@ -7,8 +7,8 @@ Feature: Multiple journals
Given the config "basic_onefile.yaml" exists Given the config "basic_onefile.yaml" exists
And we use the config "multiple.yaml" And we use the config "multiple.yaml"
When we run "jrnl --cf basic_onefile.yaml -999" When we run "jrnl --cf basic_onefile.yaml -999"
Then the output should not contain "My first entry" Then the output should not contain "My first entry" # from multiple.yaml
And the output should contain "Lorem ipsum" And the output should contain "Lorem ipsum" # from basic_onefile.yaml
Scenario: Write to default journal by default using an alternate config Scenario: Write to default journal by default using an alternate config
Given the config "multiple.yaml" exists Given the config "multiple.yaml" exists
@ -73,22 +73,18 @@ Feature: Multiple journals
Given the config "multiple.yaml" exists Given the config "multiple.yaml" exists
And we use the config "basic_onefile.yaml" And we use the config "basic_onefile.yaml"
When we run "jrnl new_encrypted --cf multiple.yaml Adding first entry" and enter When we run "jrnl new_encrypted --cf multiple.yaml Adding first entry" and enter
"""
these three eyes these three eyes
these three eyes these three eyes
n n
"""
Then the output should contain "Journal 'new_encrypted' created at " Then the output should contain "Journal 'new_encrypted' created at "
Scenario: Don't overwrite main config when encrypting a journal in an alternate config Scenario: Don't overwrite main config when encrypting a journal in an alternate config
Given the config "basic_onefile.yaml" exists Given the config "basic_onefile.yaml" exists
And we use the config "multiple.yaml" And we use the config "multiple.yaml"
When we run "jrnl --cf basic_onefile.yaml --encrypt" and enter When we run "jrnl --cf basic_onefile.yaml --encrypt" and enter
"""
these three eyes these three eyes
these three eyes these three eyes
n n
"""
Then the output should contain "Journal encrypted to features/journals/basic_onefile.journal" Then the output should contain "Journal encrypted to features/journals/basic_onefile.journal"
And the config should contain "encrypt: false" And the config should contain "encrypt: false"
@ -126,7 +122,7 @@ Feature: Multiple journals
And we use the config "duplicate_keys.yaml" And we use the config "duplicate_keys.yaml"
When we run "jrnl -1" When we run "jrnl -1"
Then the output should contain "There is at least one duplicate key in your configuration file" Then the output should contain "There is at least one duplicate key in your configuration file"
Scenario: Show a warning message when using --config-file with duplicate keys Scenario: Show a warning message when using --config-file with duplicate keys
Given the config "duplicate_keys.yaml" exists Given the config "duplicate_keys.yaml" exists
And we use the config "multiple.yaml" And we use the config "multiple.yaml"
@ -142,4 +138,4 @@ Feature: Multiple journals
Given we use the config "format_md.yaml" Given we use the config "format_md.yaml"
When we run "jrnl -1" When we run "jrnl -1"
Then the output should contain "Configuration updated to newest version at" Then the output should contain "Configuration updated to newest version at"
And the version in the config file should be up-to-date And the version in the config file should be up-to-date

View file

@ -27,13 +27,11 @@ Feature: Reading and writing to journal with custom date formats
Then we should get no error Then we should get no error
When we run "jrnl -n 999" When we run "jrnl -n 999"
Then the output should be Then the output should be
"""
09.06.2013 15:39 My first entry. 09.06.2013 15:39 My first entry.
| Everything is alright | Everything is alright
10.07.2013 15:40 Life is good. 10.07.2013 15:40 Life is good.
| But I'm better. | But I'm better.
"""
Scenario Outline: Writing an entry from command line with custom date Scenario Outline: Writing an entry from command line with custom date
@ -144,11 +142,9 @@ Feature: Reading and writing to journal with custom date formats
Given we use the config "mostlyreadabledates.yaml" Given we use the config "mostlyreadabledates.yaml"
When we run "jrnl --short" When we run "jrnl --short"
Then the output should be Then the output should be
"""
2019-07-01 14:23 The third entry 2019-07-01 14:23 The third entry
2019-07-18 14:23 The first entry 2019-07-18 14:23 The first entry
2019-07-19 14:23 The second entry 2019-07-19 14:23 The second entry
"""
Scenario: Update near-valid dates after journal is edited Scenario: Update near-valid dates after journal is edited
@ -179,9 +175,7 @@ Feature: Reading and writing to journal with custom date formats
When we run "jrnl -1" When we run "jrnl -1"
Then we should get no error Then we should get no error
And the output should be And the output should be
"""
2013-10-27 04:27 Some text. 2013-10-27 04:27 Some text.
"""
@skip #1422 @skip #1422

View file

@ -8,19 +8,15 @@ Feature: Delete entries from journal
When we run "jrnl -1" When we run "jrnl -1"
Then the output should contain "2020-09-24 09:14 The third entry finally" Then the output should contain "2020-09-24 09:14 The third entry finally"
When we run "jrnl --delete" and enter When we run "jrnl --delete" and enter
"""
N N
N N
Y Y
"""
Then the error output should contain "3 entries found" Then the error output should contain "3 entries found"
And the error output should contain "1 entry deleted" And the error output should contain "1 entry deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -33,17 +29,13 @@ Feature: Delete entries from journal
Scenario Outline: Backing out of interactive delete does not change journal Scenario Outline: Backing out of interactive delete does not change journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete -n 1" and enter When we run "jrnl --delete -n 1" and enter
"""
N N
"""
Then the error output should not contain "deleted" Then the error output should not contain "deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -58,11 +50,9 @@ Feature: Delete entries from journal
Then the error output should contain "No entries to delete" Then the error output should contain "No entries to delete"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -74,17 +64,13 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with tag only deletes tagged entries Scenario Outline: Delete flag with tag only deletes tagged entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete @ipsum" and enter When we run "jrnl --delete @ipsum" and enter
"""
Y Y
"""
Then the error output should contain "1 entry found" Then the error output should contain "1 entry found"
Then the error output should contain "1 entry deleted" Then the error output should contain "1 entry deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -96,17 +82,13 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with multiple tags deletes all entries matching any of the tags Scenario Outline: Delete flag with multiple tags deletes all entries matching any of the tags
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete @ipsum @tagthree" and enter When we run "jrnl --delete @ipsum @tagthree" and enter
"""
Y Y
Y Y
"""
Then the error output should contain "2 entries found" Then the error output should contain "2 entries found"
And the error output should contain "2 entries deleted" And the error output should contain "2 entries deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -118,17 +100,13 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with -and deletes boolean AND of tagged entries Scenario Outline: Delete flag with -and deletes boolean AND of tagged entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete -and @tagone @tagtwo" and enter When we run "jrnl --delete -and @tagone @tagtwo" and enter
"""
Y Y
"""
Then the error output should contain "1 entry found" Then the error output should contain "1 entry found"
And the error output should contain "1 entry deleted" And the error output should contain "1 entry deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -140,17 +118,13 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with -not does not delete entries from given tag Scenario Outline: Delete flag with -not does not delete entries from given tag
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete @tagone -not @ipsum" and enter When we run "jrnl --delete @tagone -not @ipsum" and enter
"""
Y Y
"""
Then the error output should contain "1 entry found" Then the error output should contain "1 entry found"
And the error output should contain "1 entry deleted" And the error output should contain "1 entry deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -162,17 +136,13 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with -from search operator only deletes entries since that date Scenario Outline: Delete flag with -from search operator only deletes entries since that date
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete -from 2020-09-01" and enter When we run "jrnl --delete -from 2020-09-01" and enter
"""
Y Y
"""
Then the error output should contain "1 entry found" Then the error output should contain "1 entry found"
And the error output should contain "1 entry deleted" And the error output should contain "1 entry deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -184,17 +154,13 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with -to only deletes entries up to specified date Scenario Outline: Delete flag with -to only deletes entries up to specified date
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete -to 2020-08-31" and enter When we run "jrnl --delete -to 2020-08-31" and enter
"""
Y Y
Y Y
"""
Then the error output should contain "2 entries found" Then the error output should contain "2 entries found"
And the error output should contain "2 entries deleted" And the error output should contain "2 entries deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -206,16 +172,12 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with -starred only deletes starred entries Scenario Outline: Delete flag with -starred only deletes starred entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete -starred" and enter When we run "jrnl --delete -starred" and enter
"""
Y Y
"""
Then the error output should contain "1 entry deleted" Then the error output should contain "1 entry deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |
@ -227,17 +189,13 @@ Feature: Delete entries from journal
Scenario Outline: Delete flag with -contains only entries containing expression Scenario Outline: Delete flag with -contains only entries containing expression
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --delete -contains dignissim" and enter When we run "jrnl --delete -contains dignissim" and enter
"""
Y Y
"""
Then the error output should contain "1 entry found" Then the error output should contain "1 entry found"
And the error output should contain "1 entry deleted" And the error output should contain "1 entry deleted"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing. 2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: Configs Examples: Configs
| config_file | | config_file |

View file

@ -11,10 +11,8 @@ Feature: Encrypting and decrypting journals
And the config for journal "default" should contain "encrypt: false" And the config for journal "default" should contain "encrypt: false"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
@todo @todo
@ -25,10 +23,8 @@ Feature: Encrypting and decrypting journals
Then the config for journal "default" should contain "encrypt: false" Then the config for journal "default" should contain "encrypt: false"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
Scenario: Trying to encrypt an already encrypted journal Scenario: Trying to encrypt an already encrypted journal
@ -40,11 +36,9 @@ Feature: Encrypting and decrypting journals
Scenario Outline: Encrypting a journal Scenario Outline: Encrypting a journal
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
swordfish swordfish
swordfish swordfish
n n
"""
Then we should get no error Then we should get no error
And the output should contain "Journal encrypted" And the output should contain "Journal encrypted"
And the config for journal "default" should contain "encrypt: true" And the config for journal "default" should contain "encrypt: true"
@ -56,20 +50,16 @@ Feature: Encrypting and decrypting journals
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
And we don't have a keyring And we don't have a keyring
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
swordfish swordfish
swordfish swordfish
y y
"""
Then we should get no error Then we should get no error
And the output should contain "Journal encrypted" And the output should contain "Journal encrypted"
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
swordfish swordfish
tuna tuna
tuna tuna
y y
"""
Then we should get no error Then we should get no error
And the output should contain "Journal default is already encrypted. Create a new password." And the output should contain "Journal default is already encrypted. Create a new password."
And we should be prompted for a password And we should be prompted for a password
@ -79,19 +69,15 @@ Feature: Encrypting and decrypting journals
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
And we have a keyring And we have a keyring
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
swordfish swordfish
swordfish swordfish
y y
"""
Then we should get no error Then we should get no error
And the output should contain "Journal encrypted" And the output should contain "Journal encrypted"
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
tuna tuna
tuna tuna
y y
"""
Then we should get no error Then we should get no error
And the output should contain "Journal default is already encrypted. Create a new password." And the output should contain "Journal default is already encrypted. Create a new password."
And we should be prompted for a password And we should be prompted for a password

View file

@ -8,9 +8,7 @@ Feature: Journals iteracting with the file system in a way that users can see
When we run "jrnl 23 July 2013: Testing folder journal." When we run "jrnl 23 July 2013: Testing folder journal."
Then we should get no error Then we should get no error
And the journal directory should contain And the journal directory should contain
"""
2013/07/23.txt 2013/07/23.txt
"""
Scenario: Adding multiple entries to a Folder journal should generate multiple date files Scenario: Adding multiple entries to a Folder journal should generate multiple date files
Given we use the config "empty_folder.yaml" Given we use the config "empty_folder.yaml"
@ -18,9 +16,7 @@ Feature: Journals iteracting with the file system in a way that users can see
And we run "jrnl 3/7/2014: Second entry of journal." And we run "jrnl 3/7/2014: Second entry of journal."
Then we should get no error Then we should get no error
And the journal directory should contain And the journal directory should contain
"""
2013/07/23.txt 2013/07/23.txt
"""
Scenario: If the journal and its parent directory don't exist, they should be created Scenario: If the journal and its parent directory don't exist, they should be created
Given we use the config "missing_directory.yaml" Given we use the config "missing_directory.yaml"
@ -37,7 +33,7 @@ Feature: Journals iteracting with the file system in a way that users can see
Then the journal should exist Then the journal should exist
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should contain "This is a new entry in my journal" Then the output should contain "This is a new entry in my journal"
@on_posix @on_posix
Scenario: If the directory for a Folder journal ending in a slash ('/') doesn't exist, then it should be created Scenario: If the directory for a Folder journal ending in a slash ('/') doesn't exist, then it should be created
Given we use the config "missing_directory.yaml" Given we use the config "missing_directory.yaml"
@ -59,11 +55,9 @@ Feature: Journals iteracting with the file system in a way that users can see
Scenario: Creating journal with relative path should update to absolute path Scenario: Creating journal with relative path should update to absolute path
Given we use no config Given we use no config
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
"""
test.txt test.txt
n n
\n \n
"""
Then the output should contain "Journal 'default' created" Then the output should contain "Journal 'default' created"
When we change directory to "subfolder" When we change directory to "subfolder"
And we run "jrnl -n 1" And we run "jrnl -n 1"

View file

@ -40,12 +40,10 @@ Feature: Custom formats
Given we parse the output as JSON Given we parse the output as JSON
Then "entries" in the parsed output should have 3 elements Then "entries" in the parsed output should have 3 elements
And "tags" in the parsed output should be And "tags" in the parsed output should be
"""
@ipsum @ipsum
@tagone @tagone
@tagtwo @tagtwo
@tagthree @tagthree
"""
And "entries.0.tags" in the parsed output should have 3 elements And "entries.0.tags" in the parsed output should have 3 elements
And "entries.1.tags" in the parsed output should have 1 elements And "entries.1.tags" in the parsed output should have 1 elements
And "entries.2.tags" in the parsed output should have 2 elements And "entries.2.tags" in the parsed output should have 2 elements
@ -64,9 +62,7 @@ Feature: Custom formats
And the output should be valid JSON And the output should be valid JSON
Given we parse the output as JSON Given we parse the output as JSON
Then "entries.0.uuid" in the parsed output should be Then "entries.0.uuid" in the parsed output should be
"""
4BB1F46946AD439996C9B59DE7C4DDC1 4BB1F46946AD439996C9B59DE7C4DDC1
"""
Scenario Outline: Printing a journal that has multiline entries with tags Scenario Outline: Printing a journal that has multiline entries with tags
Given we use the config "<config_file>" Given we use the config "<config_file>"
@ -74,10 +70,9 @@ Feature: Custom formats
When we run "jrnl -n 1 @ipsum" When we run "jrnl -n 1 @ipsum"
Then we should get no error Then we should get no error
And the output should be And the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
| Lorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada | Lorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada
| quis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus | quis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus
| pellentesque | pellentesque
| augue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu | augue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu
| consequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In | consequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In
@ -95,7 +90,6 @@ Feature: Custom formats
| velit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac | velit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac
| porta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per | porta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per
| conubia nostra, per inceptos himenaeos. | conubia nostra, per inceptos himenaeos.
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -114,11 +108,9 @@ Feature: Custom formats
Given we parse the output as JSON Given we parse the output as JSON
Then "entries" in the parsed output should have 2 elements Then "entries" in the parsed output should have 2 elements
And "tags" in the parsed output should be And "tags" in the parsed output should be
"""
@ipsum @ipsum
@tagone @tagone
@tagtwo @tagtwo
"""
And "entries.0.tags" in the parsed output should have 3 elements And "entries.0.tags" in the parsed output should have 3 elements
And "entries.1.tags" in the parsed output should have 1 elements And "entries.1.tags" in the parsed output should have 1 elements
@ -133,7 +125,6 @@ Feature: Custom formats
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
Given we append to the editor if opened Given we append to the editor if opened
"""
[2021-10-14 13:23] Heading Test [2021-10-14 13:23] Heading Test
H1-1 H1-1
@ -176,12 +167,10 @@ Feature: Custom formats
More stuff More stuff
more stuff again more stuff again
"""
When we run "jrnl --edit -1" When we run "jrnl --edit -1"
Then the editor should have been called Then the editor should have been called
When we run "jrnl -1 --export markdown" When we run "jrnl -1 --export markdown"
Then the output should be Then the output should be
"""
# 2021 # 2021
## October ## October
@ -222,7 +211,6 @@ Feature: Custom formats
More stuff More stuff
more stuff again more stuff again
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -239,12 +227,10 @@ Feature: Custom formats
Then the output should be a valid XML string Then the output should be a valid XML string
And "entries" in the xml output should have 3 elements And "entries" in the xml output should have 3 elements
And "tags" in the xml output should contain And "tags" in the xml output should contain
"""
@ipsum @ipsum
@tagone @tagone
@tagtwo @tagtwo
@tagthree @tagthree
"""
And there should be 10 "tag" elements And there should be 10 "tag" elements
Examples: configs Examples: configs
@ -262,11 +248,9 @@ Feature: Custom formats
Given we parse the output as XML Given we parse the output as XML
Then "entries" in the parsed output should have 2 elements Then "entries" in the parsed output should have 2 elements
And "tags" in the parsed output should be And "tags" in the parsed output should be
"""
@idea @idea
@journal @journal
@dan @dan
"""
And there should be 7 "tag" elements And there should be 7 "tag" elements
Scenario Outline: Exporting tags Scenario Outline: Exporting tags
@ -274,12 +258,10 @@ Feature: Custom formats
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --export tags" When we run "jrnl --export tags"
Then the output should be Then the output should be
"""
@tagtwo : 2 @tagtwo : 2
@tagone : 2 @tagone : 2
@tagthree : 1 @tagthree : 1
@ipsum : 1 @ipsum : 1
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -311,7 +293,6 @@ Feature: Custom formats
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --export fancy" When we run "jrnl --export fancy"
Then the output should be Then the output should be
"""
2020-08-29 11:11 2020-08-29 11:11
Entry the first. Entry the first.
@ -394,7 +375,6 @@ Feature: Custom formats
Phasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at Phasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at
ante eget fringilla. @tagthree and also @tagone ante eget fringilla. @tagthree and also @tagone
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -410,14 +390,11 @@ Feature: Custom formats
And we create a cache directory And we create a cache directory
When we run "jrnl --format yaml --file {cache_dir}" When we run "jrnl --format yaml --file {cache_dir}"
Then the cache directory should contain the files Then the cache directory should contain the files
"""
2020-08-29_entry-the-first.md 2020-08-29_entry-the-first.md
2020-08-31_a-second-entry-in-what-i-hope-to-be-a-long-series.md 2020-08-31_a-second-entry-in-what-i-hope-to-be-a-long-series.md
2020-09-24_the-third-entry-finally-after-weeks-without-writing.md 2020-09-24_the-third-entry-finally-after-weeks-without-writing.md
"""
And the content of file "2020-08-29_entry-the-first.md" in the cache should be And the content of file "2020-08-29_entry-the-first.md" in the cache should be
"""
--- ---
title: Entry the first. title: Entry the first.
date: 2020-08-29 11:11 date: 2020-08-29 11:11
@ -443,8 +420,7 @@ Feature: Custom formats
porta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per porta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per
conubia nostra, per inceptos himenaeos. conubia nostra, per inceptos himenaeos.
... ...
"""
Examples: configs Examples: configs
| config_file | | config_file |
| basic_onefile.yaml | | basic_onefile.yaml |
@ -475,13 +451,10 @@ Feature: Custom formats
And we create a cache directory And we create a cache directory
When we run "jrnl --export yaml -o {cache_dir}" When we run "jrnl --export yaml -o {cache_dir}"
Then the cache should contain the files Then the cache should contain the files
"""
2020-08-29_entry-the-first.md 2020-08-29_entry-the-first.md
2020-08-31_a-second-entry-in-what-i-hope-to-be-a-long-series.md 2020-08-31_a-second-entry-in-what-i-hope-to-be-a-long-series.md
2020-09-24_the-third-entry-finally-after-weeks-without-writing.md 2020-09-24_the-third-entry-finally-after-weeks-without-writing.md
"""
And the content of file "2020-09-24_the-third-entry-finally-after-weeks-without-writing.md" in the cache should be And the content of file "2020-09-24_the-third-entry-finally-after-weeks-without-writing.md" in the cache should be
"""
--- ---
title: The third entry finally after weeks without writing. title: The third entry finally after weeks without writing.
date: 2020-09-24 09:14 date: 2020-09-24 09:14
@ -499,7 +472,6 @@ Feature: Custom formats
Phasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at Phasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at
ante eget fringilla. @tagthree and also @tagone ante eget fringilla. @tagthree and also @tagone
... ...
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -544,7 +516,6 @@ Feature: Custom formats
Given we use the config "format_md.yaml" Given we use the config "format_md.yaml"
When we run "jrnl -n 1" When we run "jrnl -n 1"
Then the output should be Then the output should be
"""
# 2013 # 2013
## June ## June
@ -552,16 +523,13 @@ Feature: Custom formats
### 2013-06-10 15:40 Life is good. ### 2013-06-10 15:40 Life is good.
But I'm better. But I'm better.
"""
Scenario: Text Formatter from config file Scenario: Text Formatter from config file
Given we use the config "format_text.yaml" Given we use the config "format_text.yaml"
When we run "jrnl -n 1" When we run "jrnl -n 1"
Then the output should be Then the output should be
"""
[2013-06-10 15:40] Life is good. [2013-06-10 15:40] Life is good.
But I'm better. But I'm better.
"""
Scenario Outline: Exporting entries with Cyrillic characters to directory should not fail Scenario Outline: Exporting entries with Cyrillic characters to directory should not fail
Given we use the config "<config_file>" Given we use the config "<config_file>"
@ -570,9 +538,7 @@ Feature: Custom formats
When we run "jrnl 2020-11-21: Первая" When we run "jrnl 2020-11-21: Первая"
When we run "jrnl --format md --file {cache_dir} -on 2020-11-21" When we run "jrnl --format md --file {cache_dir} -on 2020-11-21"
Then the cache should contain the files Then the cache should contain the files
"""
2020-11-21_первая.md 2020-11-21_первая.md
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -587,11 +553,9 @@ Feature: Custom formats
When we run "jrnl 2020-08-31 01:01: Hi." When we run "jrnl 2020-08-31 01:01: Hi."
And we run "jrnl --format dates" And we run "jrnl --format dates"
Then the output should be Then the output should be
"""
2020-08-29, 1 2020-08-29, 1
2020-08-31, 2 2020-08-31, 2
2020-09-24, 1 2020-09-24, 1
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -607,7 +571,7 @@ Feature: Custom formats
When we run "jrnl --config-override display_format short -1" When we run "jrnl --config-override display_format short -1"
Then we should get no error Then we should get no error
When we run "jrnl --config-override display_format pretty -1" When we run "jrnl --config-override display_format pretty -1"
Then we should get no error Then we should get no error
Examples: configs Examples: configs
| config_file | | config_file |
@ -624,7 +588,7 @@ Feature: Custom formats
When we run "jrnl --format markdown --file {cache_dir}" When we run "jrnl --format markdown --file {cache_dir}"
Then the cache directory should contain 5 files Then the cache directory should contain 5 files
And we should get no error And we should get no error
Scenario: Export entries in text format with a title longer than max file name length. Scenario: Export entries in text format with a title longer than max file name length.
Given we use the config "basic_onefile.yaml" Given we use the config "basic_onefile.yaml"
And we create a cache directory And we create a cache directory
@ -638,42 +602,30 @@ Feature: Custom formats
Given we use the config "basic_onefile.yaml" Given we use the config "basic_onefile.yaml"
When we run "jrnl --list" When we run "jrnl --list"
Then the output should match Then the output should match
"""
Journals defined in config \(.+basic_onefile\.yaml\) Journals defined in config \(.+basic_onefile\.yaml\)
\* default -> features/journals/basic_onefile\.journal \* default -> features/journals/basic_onefile\.journal
"""
When we run "jrnl --list --format json" When we run "jrnl --list --format json"
Then the output should match Then the output should match
"""
{"config_path": ".+basic_onefile\.yaml", "journals": {"default": "features/journals/basic_onefile\.journal"}} {"config_path": ".+basic_onefile\.yaml", "journals": {"default": "features/journals/basic_onefile\.journal"}}
"""
When we run "jrnl --list --format yaml" When we run "jrnl --list --format yaml"
Then the output should match Then the output should match
"""
config_path: .+basic_onefile\.yaml config_path: .+basic_onefile\.yaml
journals: journals:
default: features/journals/basic_onefile\.journal default: features/journals/basic_onefile\.journal
"""
Scenario: Export journal list to formats with no default journal Scenario: Export journal list to formats with no default journal
Given we use the config "no_default_journal.yaml" Given we use the config "no_default_journal.yaml"
When we run "jrnl --list" When we run "jrnl --list"
Then the output should match Then the output should match
"""
Journals defined in config \(.+no_default_journal\.yaml\) Journals defined in config \(.+no_default_journal\.yaml\)
\* simple -> features/journals/simple\.journal \* simple -> features/journals/simple\.journal
\* work -> features/journals/work\.journal \* work -> features/journals/work\.journal
"""
When we run "jrnl --list --format json" When we run "jrnl --list --format json"
Then the output should match Then the output should match
"""
{"config_path": ".+no_default_journal\.yaml", "journals": {"simple": "features/journals/simple\.journal", "work": "features/journals/work\.journal"}} {"config_path": ".+no_default_journal\.yaml", "journals": {"simple": "features/journals/simple\.journal", "work": "features/journals/work\.journal"}}
"""
When we run "jrnl --list --format yaml" When we run "jrnl --list --format yaml"
Then the output should match Then the output should match
"""
config_path: .+no_default_journal\.yaml config_path: .+no_default_journal\.yaml
journals: journals:
simple: features/journals/simple\.journal simple: features/journals/simple\.journal
work: features/journals/work\.journal work: features/journals/work\.journal
"""

View file

@ -21,7 +21,6 @@ Feature: Importing data
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --import" and pipe When we run "jrnl --import" and pipe
"""
[2020-07-05 15:00] Observe and import. [2020-07-05 15:00] Observe and import.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada quis Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada quis
est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque augue est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque augue
@ -29,7 +28,6 @@ Feature: Importing data
Aenean ante ex, elementum ut interdum et, mattis eget lacus. In commodo nulla nec Aenean ante ex, elementum ut interdum et, mattis eget lacus. In commodo nulla nec
tellus placerat, sed ultricies metus bibendum. Duis eget venenatis erat. In at tellus placerat, sed ultricies metus bibendum. Duis eget venenatis erat. In at
dolor dui end of entry. dolor dui end of entry.
"""
When we run "jrnl -on 2020-07-05" When we run "jrnl -on 2020-07-05"
Then the output should contain "2020-07-05 15:00 Observe and import." Then the output should contain "2020-07-05 15:00 Observe and import."
And the output should contain "Lorem ipsum" And the output should contain "Lorem ipsum"
@ -46,13 +44,11 @@ Feature: Importing data
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --import" and pipe When we run "jrnl --import" and pipe
"""
[2020-07-05 15:00] Observe and import. [2020-07-05 15:00] Observe and import.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
[2020-07-05 15:01] Twice as nice. [2020-07-05 15:01] Twice as nice.
Sed dignissim sed nisl eu consequat. Sed dignissim sed nisl eu consequat.
"""
When we run "jrnl -on 2020-07-05" When we run "jrnl -on 2020-07-05"
Then the output should contain "2020-07-05 15:00 Observe and import." Then the output should contain "2020-07-05 15:00 Observe and import."
And the output should contain "Lorem ipsum" And the output should contain "Lorem ipsum"
@ -87,9 +83,7 @@ Feature: Importing data
But the output should not contain "I have an @idea" But the output should not contain "I have an @idea"
And the output should not contain "I met with" And the output should not contain "I met with"
When we run "jrnl --import --file features/journals/tags.journal" and pipe When we run "jrnl --import --file features/journals/tags.journal" and pipe
"""
[2020-07-05 15:00] I should not exist! [2020-07-05 15:00] I should not exist!
"""
And we run "jrnl -99" And we run "jrnl -99"
Then the output should contain "My first entry." Then the output should contain "My first entry."
And the output should contain "PROFIT!" And the output should contain "PROFIT!"

View file

@ -3,11 +3,9 @@ Feature: Installing jrnl
Scenario: Install jrnl with default options Scenario: Install jrnl with default options
Given we use no config Given we use no config
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
"""
\n \n
\n \n
\n \n
"""
Then the output should contain "jrnl configuration created at" Then the output should contain "jrnl configuration created at"
And the output should contain "For advanced features, read the docs at https://jrnl.sh" And the output should contain "For advanced features, read the docs at https://jrnl.sh"
And the output should contain "Journal 'default' created" And the output should contain "Journal 'default' created"
@ -18,11 +16,9 @@ Feature: Installing jrnl
Scenario: Install jrnl with custom relative default journal path Scenario: Install jrnl with custom relative default journal path
Given we use no config Given we use no config
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
"""
default/custom.txt default/custom.txt
n n
\n \n
"""
Then the output should contain "Journal 'default' created" Then the output should contain "Journal 'default' created"
And the default journal "custom.txt" should be in the "default" directory And the default journal "custom.txt" should be in the "default" directory
And the config should contain "encrypt: false" And the config should contain "encrypt: false"
@ -32,11 +28,9 @@ Feature: Installing jrnl
Given we use no config Given we use no config
And the home directory is called "home" And the home directory is called "home"
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
"""
~/custom.txt ~/custom.txt
n n
\n \n
"""
Then the output should contain "Journal 'default' created" Then the output should contain "Journal 'default' created"
And the default journal "custom.txt" should be in the "home" directory And the default journal "custom.txt" should be in the "home" directory
And the config should contain "encrypt: false" And the config should contain "encrypt: false"
@ -45,11 +39,9 @@ Feature: Installing jrnl
Scenario: Install jrnl with encrypted default journal Scenario: Install jrnl with encrypted default journal
Given we use no config Given we use no config
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
"""
encrypted.txt encrypted.txt
y y
\n \n
"""
Then the output should contain "Journal will be encrypted" Then the output should contain "Journal will be encrypted"
And the default journal "encrypted.txt" should be in the "." directory And the default journal "encrypted.txt" should be in the "." directory
And the config should contain "encrypt: true" And the config should contain "encrypt: true"
@ -60,58 +52,46 @@ Feature: Installing jrnl
Scenario: Install jrnl with colors by default Scenario: Install jrnl with colors by default
Given we use no config Given we use no config
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
"""
\n \n
\n \n
\n \n
"""
Then the output should contain "Journal 'default' created" Then the output should contain "Journal 'default' created"
And the config should contain And the config should contain
"""
colors: colors:
body: none body: none
date: black date: black
tags: yellow tags: yellow
title: cyan title: cyan
"""
Scenario: Install jrnl without colors Scenario: Install jrnl without colors
Given we use no config Given we use no config
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
"""
\n \n
\n \n
N N
"""
Then the output should contain "Journal 'default' created" Then the output should contain "Journal 'default' created"
And the config should contain And the config should contain
"""
colors: colors:
body: none body: none
date: none date: none
tags: none tags: none
title: none title: none
"""
Scenario: Install jrnl with encrypted default journal with no entries Scenario: Install jrnl with encrypted default journal with no entries
Given we use no config Given we use no config
When we run "jrnl -1" and enter When we run "jrnl -1" and enter
"""
encrypted.txt encrypted.txt
y y
n n
test test
test test
n n
"""
Then the error output should contain "Journal will be encrypted" Then the error output should contain "Journal will be encrypted"
And the default journal "encrypted.txt" should be in the "." directory And the default journal "encrypted.txt" should be in the "." directory
And the config should contain "encrypt: true" And the config should contain "encrypt: true"
And the version in the config file should be up-to-date And the version in the config file should be up-to-date
When we run "jrnl -1" and enter When we run "jrnl -1" and enter
""" test
test
"""
Then we should be prompted for a password Then we should be prompted for a password
And the error output should contain "no entries found" And the error output should contain "no entries found"
And the error output should not contain "Wrong password, try again" And the error output should not contain "Wrong password, try again"

View file

@ -7,10 +7,8 @@ Feature: Multiple journals
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
When we run "jrnl work -99 --short" When we run "jrnl work -99 --short"
Then the output should be empty Then the output should be empty
@ -19,14 +17,10 @@ Feature: Multiple journals
When we run "jrnl this goes to default" When we run "jrnl this goes to default"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should contain Then the output should contain
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
Then the output should contain Then the output should contain
"""
this goes to default this goes to default
"""
When we run "jrnl work -99 --short" When we run "jrnl work -99 --short"
Then the output should be empty Then the output should be empty
@ -35,10 +29,8 @@ Feature: Multiple journals
When we run "jrnl work a long day in the office" When we run "jrnl work a long day in the office"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
When we run "jrnl work -99 --short" When we run "jrnl work -99 --short"
Then the output should contain "a long day in the office" Then the output should contain "a long day in the office"
@ -52,44 +44,32 @@ Feature: Multiple journals
When we run "jrnl work 23 july 2012: a long day in the office" When we run "jrnl work 23 july 2012: a long day in the office"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
When we run "jrnl work -99 --short" When we run "jrnl work -99 --short"
Then the output should be Then the output should be
"""
2012-07-23 09:00 a long day in the office 2012-07-23 09:00 a long day in the office
"""
Scenario: Write to specified journal without a timestamp but with colon Scenario: Write to specified journal without a timestamp but with colon
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"
When we run "jrnl work : a long day in the office" When we run "jrnl work : a long day in the office"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
When we run "jrnl work -99 --short" When we run "jrnl work -99 --short"
Then the output should be contain Then the output should be contain
"""
a long day in the office a long day in the office
"""
Scenario: Write to specified journal without a timestamp but with colon Scenario: Write to specified journal without a timestamp but with colon
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"
When we run "jrnl work: a long day in the office" When we run "jrnl work: a long day in the office"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
When we run "jrnl work -99 --short" When we run "jrnl work -99 --short"
Then the output should contain Then the output should contain
"""
a long day in the office a long day in the office
"""
Scenario: Create new journals as required Scenario: Create new journals as required
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"
@ -97,9 +77,7 @@ Feature: Multiple journals
When we run "jrnl ideas 23 july 2012: sell my junk on ebay and make lots of money" When we run "jrnl ideas 23 july 2012: sell my junk on ebay and make lots of money"
When we run "jrnl ideas -99 --short" When we run "jrnl ideas -99 --short"
Then the output should be Then the output should be
"""
2012-07-23 09:00 sell my junk on ebay and make lots of money 2012-07-23 09:00 sell my junk on ebay and make lots of money
"""
Scenario: Don't crash if no default journal is specified Scenario: Don't crash if no default journal is specified
Given we use the config "no_default_journal.yaml" Given we use the config "no_default_journal.yaml"
@ -109,11 +87,9 @@ Feature: Multiple journals
Scenario: Don't crash if no file exists for a configured encrypted journal Scenario: Don't crash if no file exists for a configured encrypted journal
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"
When we run "jrnl new_encrypted Adding first entry" and enter When we run "jrnl new_encrypted Adding first entry" and enter
"""
these three eyes these three eyes
these three eyes these three eyes
n n
"""
Then the output should contain "Journal 'new_encrypted' created at" Then the output should contain "Journal 'new_encrypted' created at"
Scenario: Read and write to journal with emoji name Scenario: Read and write to journal with emoji name

View file

@ -7,9 +7,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
Given we use the config "basic_encrypted.yaml" Given we use the config "basic_encrypted.yaml"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --config-override editor ''" and type When we run "jrnl --config-override editor ''" and type
""" This is a journal entry
This is a journal entry
"""
Then the stdin prompt should have been called Then the stdin prompt should have been called
And the editor should not have been called And the editor should not have been called
When we run "jrnl -1" When we run "jrnl -1"
@ -29,7 +27,6 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl -2 --config-override linewrap 23 --format fancy" When we run "jrnl -2 --config-override linewrap 23 --format fancy"
Then the output should be Then the output should be
"""
2013-06-09 15:39 2013-06-09 15:39
My My
fir st ent ry. fir st ent ry.
@ -43,7 +40,6 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
But I'm better. But I'm better.
"""
Scenario: Override color selections with runtime overrides Scenario: Override color selections with runtime overrides
@ -63,14 +59,12 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl -1 --config-override colors.body green --config-override editor 'nano'" When we run "jrnl -1 --config-override colors.body green --config-override editor 'nano'"
Then the config in memory should contain Then the config in memory should contain
"""
editor: nano editor: nano
colors: colors:
title: none title: none
body: green body: green
tags: none tags: none
date: none date: none
"""
Scenario: Override default journal Scenario: Override default journal
@ -80,7 +74,6 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
Then we should get no error Then we should get no error
When we run "jrnl -3 --config-override journals.default features/journals/simple.journal" When we run "jrnl -3 --config-override journals.default features/journals/simple.journal"
Then the output should be Then the output should be
"""
2000-03-20 09:00 The rain in Spain comes from clouds 2000-03-20 09:00 The rain in Spain comes from clouds
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
@ -88,7 +81,6 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
| But I'm better. | But I'm better.
"""
Scenario: Make an entry into an overridden journal Scenario: Make an entry into an overridden journal
@ -99,7 +91,6 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
And the output should contain "Entry added" And the output should contain "Entry added"
When we run "jrnl --config-override journals.temp features/journals/simple.journal temp -3" When we run "jrnl --config-override journals.temp features/journals/simple.journal temp -3"
Then the output should be Then the output should be
"""
1969-09-06 09:00 @say Ni 1969-09-06 09:00 @say Ni
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
@ -107,4 +98,3 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
| But I'm better. | But I'm better.
"""

View file

@ -7,11 +7,9 @@ Feature: Using the installed keyring
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"
And we have a keyring And we have a keyring
When we run "jrnl simple --encrypt" and enter When we run "jrnl simple --encrypt" and enter
"""
sabertooth sabertooth
sabertooth sabertooth
Y Y
"""
Then the config for journal "simple" should contain "encrypt: true" Then the config for journal "simple" should contain "encrypt: true"
When we run "jrnl simple -n 1" When we run "jrnl simple -n 1"
Then the output should contain "2013-06-10 15:40 Life is good" Then the output should contain "2013-06-10 15:40 Life is good"
@ -21,11 +19,9 @@ Feature: Using the installed keyring
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
When we run "jrnl test entry" When we run "jrnl test entry"
And we run "jrnl --encrypt" and enter And we run "jrnl --encrypt" and enter
"""
password password
password password
n n
"""
Then we should get no error Then we should get no error
And the output should not contain "Failed to retrieve keyring" And the output should not contain "Failed to retrieve keyring"
@ -34,11 +30,9 @@ Feature: Using the installed keyring
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
When we run "jrnl test entry" When we run "jrnl test entry"
And we run "jrnl --encrypt" and enter And we run "jrnl --encrypt" and enter
"""
password password
password password
y y
"""
Then we should get no error Then we should get no error
And the output should not contain "Failed to retrieve keyring" And the output should not contain "Failed to retrieve keyring"
# @todo add step to check contents of keyring # @todo add step to check contents of keyring
@ -61,11 +55,9 @@ Feature: Using the installed keyring
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
And we have a failed keyring And we have a failed keyring
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
this password will not be saved in keyring this password will not be saved in keyring
this password will not be saved in keyring this password will not be saved in keyring
y y
"""
Then the output should contain "Failed to retrieve keyring" Then the output should contain "Failed to retrieve keyring"
And we should get no error And we should get no error
And we should be prompted for a password And we should be prompted for a password
@ -85,10 +77,8 @@ Feature: Using the installed keyring
When we run "jrnl --short" When we run "jrnl --short"
Then we should not be prompted for a password Then we should not be prompted for a password
And the output should be And the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
Scenario: Open encrypted journal when keyring exists but fails Scenario: Open encrypted journal when keyring exists but fails
@ -106,31 +96,25 @@ Feature: Using the installed keyring
Scenario: Mistyping your password Scenario: Mistyping your password
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
swordfish swordfish
sordfish sordfish
"""
Then we should be prompted for a password Then we should be prompted for a password
And the output should contain "Passwords did not match" And the output should contain "Passwords did not match"
And the config for journal "default" should not contain "encrypt: true" And the config for journal "default" should not contain "encrypt: true"
When we run "jrnl --short" When we run "jrnl --short"
Then the output should be Then the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
"""
Scenario: Mistyping your password, then getting it right Scenario: Mistyping your password, then getting it right
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
When we run "jrnl --encrypt" and enter When we run "jrnl --encrypt" and enter
"""
swordfish swordfish
sordfish sordfish
swordfish swordfish
swordfish swordfish
n n
"""
Then we should be prompted for a password Then we should be prompted for a password
And the output should contain "Passwords did not match" And the output should contain "Passwords did not match"
And the output should contain "Journal encrypted" And the output should contain "Journal encrypted"

View file

@ -27,7 +27,7 @@ Feature: Searching in a journal
When we run "jrnl tomorrow: A future entry." When we run "jrnl tomorrow: A future entry."
Then we should get no error Then we should get no error
When we run "jrnl -from today" When we run "jrnl -from today"
Then the output should contain "2 entries found" Then the output should contain "2 entries found"
And the output should contain "Adding an entry right now." And the output should contain "Adding an entry right now."
And the output should contain "A future entry." And the output should contain "A future entry."
And the output should not contain "This thing happened yesterday" And the output should not contain "This thing happened yesterday"
@ -65,9 +65,7 @@ Feature: Searching in a journal
Then we should get no error Then we should get no error
And the output should contain "1 entry found" And the output should contain "1 entry found"
And the output should be And the output should be
"""
2020-08-29 11:11 Entry the first. 2020-08-29 11:11 Entry the first.
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -88,40 +86,6 @@ Feature: Searching in a journal
| basic_folder.yaml | | basic_folder.yaml |
| basic_dayone.yaml | | basic_dayone.yaml |
Scenario Outline: Multiple -contains returns entries that match any
Given we use the config "<config_file>"
When we run "jrnl -contains emojis -contains lorem --short"
Then we should get no error
And the output should contain "3 entries found"
And the output should be
"""
2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: configs
| config_file |
| basic_onefile.yaml |
| basic_folder.yaml |
| basic_dayone.yaml |
Scenario Outline: Multiple -contains with -and returns only entries that match all
Given we use the config "<config_file>"
When we run "jrnl -contains emojis -contains nulla -and --short"
Then we should get no error
And the output should contain "1 entry found"
And the output should be
"""
2020-09-24 09:14 The third entry finally after weeks without writing.
"""
Examples: configs
| config_file |
| basic_onefile.yaml |
| basic_folder.yaml |
| basic_dayone.yaml |
Scenario Outline: Searching for a string within tag results Scenario Outline: Searching for a string within tag results
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl @tagone -contains maybe" When we run "jrnl @tagone -contains maybe"
@ -225,23 +189,19 @@ Feature: Searching in a journal
Then we should get no error Then we should get no error
When we run "jrnl -2" When we run "jrnl -2"
Then the output should be Then the output should be
"""
2013-07-23 09:00 Testing folder journal. 2013-07-23 09:00 Testing folder journal.
2014-03-07 16:37 Second entry of journal. 2014-03-07 16:37 Second entry of journal.
"""
Scenario Outline: Searching for all tags should show counts of each tag Scenario Outline: Searching for all tags should show counts of each tag
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --tags" When we run "jrnl --tags"
Then we should get no error Then we should get no error
And the output should be And the output should be
"""
@tagtwo : 2 @tagtwo : 2
@tagone : 2 @tagone : 2
@tagthree : 1 @tagthree : 1
@ipsum : 1 @ipsum : 1
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -254,10 +214,8 @@ Feature: Searching in a journal
When we run "jrnl -from 'september 2020' --tags" When we run "jrnl -from 'september 2020' --tags"
Then we should get no error Then we should get no error
And the output should be And the output should be
"""
@tagthree : 1 @tagthree : 1
@tagone : 1 @tagone : 1
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -269,10 +227,8 @@ Feature: Searching in a journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --tags -not @tagtwo" When we run "jrnl --tags -not @tagtwo"
Then the output should be Then the output should be
"""
@tagthree : 1 @tagthree : 1
@tagone : 1 @tagone : 1
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -284,9 +240,7 @@ Feature: Searching in a journal
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl --tags -not @tagone -not @tagthree" When we run "jrnl --tags -not @tagone -not @tagthree"
Then the output should be Then the output should be
"""
@tagtwo : 1 @tagtwo : 1
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -318,13 +272,11 @@ Feature: Searching in a journal
When we run "jrnl -2" When we run "jrnl -2"
Then we should get no error Then we should get no error
And the output should be And the output should be
"""
2013-06-09 15:39 My first entry. 2013-06-09 15:39 My first entry.
| Everything is alright | Everything is alright
2013-06-10 15:40 Life is good. 2013-06-10 15:40 Life is good.
| But I'm better. | But I'm better.
"""
Scenario Outline: Searching by month Scenario Outline: Searching by month
Given we use the config "<config_file>" Given we use the config "<config_file>"
@ -399,10 +351,8 @@ Feature: Searching in a journal
And we run "jrnl -today-in-history --short" And we run "jrnl -today-in-history --short"
Then the output should contain "2 entries found" Then the output should contain "2 entries found"
And the output should be And the output should be
"""
2019-08-31 01:01 Hi, from last year. 2019-08-31 01:01 Hi, from last year.
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -417,13 +367,11 @@ Feature: Searching in a journal
Then we should get no error Then we should get no error
And the output should contain "3 entries found" And the output should contain "3 entries found"
And the output should be And the output should be
"""
2013-05-17 11:39 This entry has tags! 2013-05-17 11:39 This entry has tags!
2013-06-17 20:38 This entry has a location. 2013-06-17 20:38 This entry has a location.
2013-07-17 11:38 This entry is starred! 2013-07-17 11:38 This entry is starred!
"""
Scenario Outline: Searching the most recent entry should not show found count Scenario Outline: Searching the most recent entry should not show found count
Given we use the config "<config_file>" Given we use the config "<config_file>"

View file

@ -11,11 +11,9 @@ Feature: Tagging
When we run "jrnl --tags -on 2020-09-26" When we run "jrnl --tags -on 2020-09-26"
Then we should get no error Then we should get no error
And the output should be And the output should be
"""
@os/2 : 1 @os/2 : 1
@c++ : 1 @c++ : 1
@c# : 1 @c# : 1
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -43,10 +41,8 @@ Feature: Tagging
When we run "jrnl 2020-09-26: @foo came over, we went to a @bar" When we run "jrnl 2020-09-26: @foo came over, we went to a @bar"
When we run "jrnl --tags -on 2020-09-26" When we run "jrnl --tags -on 2020-09-26"
Then the output should be Then the output should be
"""
@foo : 1 @foo : 1
@bar : 1 @bar : 1
"""
Examples: configs Examples: configs
| config_file | | config_file |

View file

@ -2,13 +2,12 @@
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
Feature: Using templates Feature: Using templates
Scenario Outline: Template contents should be used in new entry Scenario Outline: Template contents should be used in new entry
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
And we append to the editor if opened And we append to the editor if opened
"""
This is an addition to a templated entry This is an addition to a templated entry
"""
When we run "jrnl --config-override template features/templates/basic.template" When we run "jrnl --config-override template features/templates/basic.template"
And we run "jrnl -1" And we run "jrnl -1"
Then the output should contain "This text is in the basic template" Then the output should contain "This text is in the basic template"
@ -39,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" Then the error output should contain "Unable to find a template file based on the passed arg"
Examples: configs Examples: configs
| config_file | | config_file |

View file

@ -8,27 +8,19 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x
When we run "jrnl -9" and enter "Y" When we run "jrnl -9" and enter "Y"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
"""
2010-06-10 15:00 A life without chocolate is like a bad analogy. 2010-06-10 15:00 A life without chocolate is like a bad analogy.
2013-06-10 15:40 He said "[this] is the best time to be alive".Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada 2013-06-10 15:40 He said "[this] is the best time to be alive".Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada
"""
And the output should contain And the output should contain
"""
2010-06-10 15:00 A life without chocolate is like a bad analogy. 2010-06-10 15:00 A life without chocolate is like a bad analogy.
"""
And the output should contain And the output should contain
"""
2013-06-10 15:40 He said "[this] is the best time to be alive". 2013-06-10 15:40 He said "[this] is the best time to be alive".
"""
Scenario: Upgrading a journal encrypted with jrnl 1.x Scenario: Upgrading a journal encrypted with jrnl 1.x
Given we use the config "encrypted_old.json" Given we use the config "encrypted_old.json"
When we run "jrnl -n 1" and enter When we run "jrnl -n 1" and enter
"""
Y Y
bad doggie no biscuit bad doggie no biscuit
bad doggie no biscuit bad doggie no biscuit
"""
Then we should be prompted for a password Then we should be prompted for a password
And the output should contain "2013-06-10 15:40 Life is good" And the output should contain "2013-06-10 15:40 Life is good"
@ -36,22 +28,18 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x
Given we use the config "no_colors.yaml" Given we use the config "no_colors.yaml"
When we run "jrnl -n 1" When we run "jrnl -n 1"
Then the config should contain Then the config should contain
"""
colors: colors:
date: none date: none
title: none title: none
body: none body: none
tags: none tags: none
"""
Scenario: Upgrade and parse journals with little endian date format Scenario: Upgrade and parse journals with little endian date format
Given we use the config "upgrade_from_195_little_endian_dates.json" Given we use the config "upgrade_from_195_little_endian_dates.json"
When we run "jrnl -9 --short" and enter "Y" When we run "jrnl -9 --short" and enter "Y"
Then the output should contain Then the output should contain
"""
10.06.2010 15:00 A life without chocolate is like a bad analogy. 10.06.2010 15:00 A life without chocolate is like a bad analogy.
10.06.2013 15:40 He said "[this] is the best time to be alive". 10.06.2013 15:40 He said "[this] is the best time to be alive".
"""
Scenario: Upgrade with missing journal Scenario: Upgrade with missing journal
Given we use the config "upgrade_from_195_with_missing_journal.json" Given we use the config "upgrade_from_195_with_missing_journal.json"
@ -62,10 +50,8 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x
Scenario: Upgrade with missing encrypted journal Scenario: Upgrade with missing encrypted journal
Given we use the config "upgrade_from_195_with_missing_encrypted_journal.json" Given we use the config "upgrade_from_195_with_missing_encrypted_journal.json"
When we run "jrnl --list" and enter When we run "jrnl --list" and enter
"""
Y Y
bad doggie no biscuit bad doggie no biscuit
"""
Then the output should contain "features/journals/missing.journal does not exist" Then the output should contain "features/journals/missing.journal does not exist"
And the output should contain "We're all done" And the output should contain "We're all done"
And we should get no error And we should get no error

View file

@ -67,9 +67,7 @@ Feature: Writing new entries.
Then we should get no error Then we should get no error
Then the editor should have been called Then the editor should have been called
And the editor file content should be And the editor file content should be
"""
this is a partial this is a partial
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -148,10 +146,8 @@ Feature: Writing new entries.
Then we should get no error Then we should get no error
When we run "jrnl -1" When we run "jrnl -1"
Then the output should be Then the output should be
"""
2014-04-24 09:00 Created a new website - empty.com. 2014-04-24 09:00 Created a new website - empty.com.
| Hope to get a lot of traffic. | Hope to get a lot of traffic.
"""
Examples: configs Examples: configs
| config_file | | config_file |
@ -210,17 +206,13 @@ Feature: Writing new entries.
Given we parse the output as JSON Given we parse the output as JSON
Then "entries" in the parsed output should have 5 elements Then "entries" in the parsed output should have 5 elements
And "entries.0.creator" in the parsed output should be And "entries.0.creator" in the parsed output should be
"""
software_agent software_agent
os_agent os_agent
host_name host_name
generation_date generation_date
device_agent device_agent
"""
And "entries.0.creator.software_agent" in the parsed output should contain And "entries.0.creator.software_agent" in the parsed output should contain
"""
jrnl jrnl
"""
Scenario: Title with an embedded period on DayOne journal Scenario: Title with an embedded period on DayOne journal
Given we use the config "dayone.yaml" Given we use the config "dayone.yaml"
@ -228,10 +220,8 @@ Feature: Writing new entries.
Then we should get no error Then we should get no error
When we run "jrnl -1" When we run "jrnl -1"
Then the output should be Then the output should be
"""
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03. 2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
| I am feeling sore because I forgot to stretch. | I am feeling sore because I forgot to stretch.
"""
Scenario: Opening an folder that's not a DayOne folder should treat as folder journal Scenario: Opening an folder that's not a DayOne folder should treat as folder journal
Given we use the config "empty_folder.yaml" Given we use the config "empty_folder.yaml"
@ -244,9 +234,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
And we append to the editor if opened And we append to the editor if opened
"""
[2021-11-13] worked on jrnl tests [2021-11-13] worked on jrnl tests
"""
When we run "jrnl --edit" When we run "jrnl --edit"
Then the error output should contain "3 entries found" Then the error output should contain "3 entries found"
And the error output should contain "1 entry added" And the error output should contain "1 entry added"
@ -264,11 +252,9 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
And we append to the editor if opened And we append to the editor if opened
"""
[2021-11-11] worked on jrnl tests [2021-11-11] worked on jrnl tests
[2021-11-12] worked on jrnl tests again [2021-11-12] worked on jrnl tests again
[2021-11-13] worked on jrnl tests a little bit more [2021-11-13] worked on jrnl tests a little bit more
"""
When we run "jrnl --edit" When we run "jrnl --edit"
Then the error output should contain "3 entries found" Then the error output should contain "3 entries found"
And the error output should contain "3 entries added" And the error output should contain "3 entries added"
@ -285,9 +271,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
And we write to the editor if opened And we write to the editor if opened
"""
[2021-11-13] I am replacing my whole journal with this entry [2021-11-13] I am replacing my whole journal with this entry
"""
When we run "jrnl --edit" When we run "jrnl --edit"
Then the output should contain "2 entries deleted" Then the output should contain "2 entries deleted"
And the output should contain "1 entry modified" And the output should contain "1 entry modified"
@ -304,9 +288,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
And we write to the editor if opened And we write to the editor if opened
"""
[2021-11-13] I am replacing the last entry with this entry [2021-11-13] I am replacing the last entry with this entry
"""
When we run "jrnl --edit -1" When we run "jrnl --edit -1"
Then the error output should contain "1 entry modified" Then the error output should contain "1 entry modified"
@ -322,9 +304,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
And we append to the editor if opened And we append to the editor if opened
"""
This is a small addendum to my latest entry. This is a small addendum to my latest entry.
"""
When we run "jrnl --edit" When we run "jrnl --edit"
Then the error output should contain "3 entries found" Then the error output should contain "3 entries found"
And the error output should contain "1 entry modified" And the error output should contain "1 entry modified"
@ -361,16 +341,12 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
And we append to the editor if opened And we append to the editor if opened
"""
@newtag @newtag
"""
When we run "jrnl --edit -1" When we run "jrnl --edit -1"
Then the error output should contain "1 entry modified" Then the error output should contain "1 entry modified"
When we run "jrnl --tags @newtag" When we run "jrnl --tags @newtag"
Then the output should contain Then the output should contain
"""
1 entry found 1 entry found
"""
Examples: configs Examples: configs
| config_file | | config_file |

View 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 = os.path.join(temp_dir.name, "templates") templates_path = Path(temp_dir.name, "templates")
return { return {
"get_templates_path": lambda: patch( "get_templates_path": lambda: patch(
"jrnl.editor.get_templates_path", return_value=templates_path "jrnl.controller.get_templates_path", return_value=templates_path
), ),
} }

View file

@ -9,11 +9,10 @@ import string
from datetime import datetime from datetime import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
from unittest.mock import patch from unittest.mock import patch
from xml.etree import ElementTree as ET from xml.etree import ElementTree
from pytest_bdd import given from pytest_bdd import given
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re
from jrnl import __version__ from jrnl import __version__
from jrnl.time import __get_pdt_calendar from jrnl.time import __get_pdt_calendar
@ -22,11 +21,7 @@ from tests.lib.fixtures import NoKeyring
from tests.lib.fixtures import TestKeyring from tests.lib.fixtures import TestKeyring
@given(re(r"we (?P<editor_method>\w+) to the editor if opened")) @given(parse("we {editor_method} to the editor if opened\n{editor_input}"))
def we_enter_editor_docstring(editor_method, editor_state, docstring):
we_enter_editor(editor_method, docstring, editor_state)
@given(parse("we {editor_method} nothing to the editor if opened")) @given(parse("we {editor_method} nothing to the editor if opened"))
def we_enter_editor(editor_method, editor_input, editor_state): def we_enter_editor(editor_method, editor_input, editor_state):
file_method = editor_state["intent"]["method"] file_method = editor_state["intent"]["method"]
@ -173,7 +168,7 @@ def parse_output_as_language(cli_run, language_name):
actual_output = cli_run["stdout"] actual_output = cli_run["stdout"]
if language_name == "XML": if language_name == "XML":
parsed_output = ET.fromstring(actual_output) parsed_output = ElementTree.fromstring(actual_output)
elif language_name == "JSON": elif language_name == "JSON":
parsed_output = json.loads(actual_output) parsed_output = json.loads(actual_output)
else: else:

View file

@ -4,11 +4,10 @@
import json import json
import os import os
import re import re
from xml.etree import ElementTree as ET from xml.etree import ElementTree
from pytest_bdd import then from pytest_bdd import then
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re as pytest_bdd_parsers_re
from ruamel.yaml import YAML from ruamel.yaml import YAML
from jrnl.config import scope_config from jrnl.config import scope_config
@ -31,11 +30,7 @@ def should_get_an_error(cli_run):
assert cli_run["status"] != 0, cli_run["status"] assert cli_run["status"] != 0, cli_run["status"]
@then(parse("the output should match")) @then(parse("the output should match\n{regex}"))
def output_should_match_docstring(cli_run, docstring):
output_should_match(docstring, cli_run)
@then(parse('the output should match "{regex}"')) @then(parse('the output should match "{regex}"'))
def output_should_match(regex, cli_run): def output_should_match(regex, cli_run):
out = cli_run["stdout"] out = cli_run["stdout"]
@ -43,62 +38,47 @@ 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", SHOULD_DICT)) @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( @then(
parse( parse(
"the {which_output_stream} output {it_should:Should} contain", "the {which_output_stream} output {it_should:Should} contain\n{expected_output}",
SHOULD_DICT, SHOULD_DICT,
) )
) )
def output_should_contain_docstring(which_output_stream, cli_run, it_should, docstring):
output_should_contain(docstring, which_output_stream, cli_run, it_should)
@then(parse('the output {it_should:Should} contain "{expected}"', SHOULD_DICT))
@then( @then(
parse( parse(
'the {which_output_stream} output {it_should:Should} contain "{expected}"', 'the {which_output_stream} output {it_should:Should} contain "{expected_output}"',
SHOULD_DICT, SHOULD_DICT,
) )
) )
def output_should_contain(expected, which_output_stream, cli_run, it_should): def output_should_contain(expected_output, which_output_stream, cli_run, it_should):
output_str = ( output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}"
f"\nEXPECTED:\n{expected}\n\n" assert expected_output
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 in cli_run["stdout"]) == it_should) or ( assert ((expected_output in cli_run["stdout"]) == it_should) or (
(expected in cli_run["stderr"]) == it_should (expected_output in cli_run["stderr"]) == it_should
), output_str ), output_str
elif which_output_stream == "standard": elif which_output_stream == "standard":
assert (expected in cli_run["stdout"]) == it_should, output_str assert (expected_output in cli_run["stdout"]) == it_should, output_str
elif which_output_stream == "error": elif which_output_stream == "error":
assert (expected in cli_run["stderr"]) == it_should, output_str assert (expected_output in cli_run["stderr"]) == it_should, output_str
else: else:
assert (expected in cli_run[which_output_stream]) == it_should, output_str assert (
expected_output in cli_run[which_output_stream]
) == it_should, output_str
@then(parse("the output should not contain"))
def output_should_not_contain_docstring(cli_run, docstring):
output_should_not_contain(docstring, cli_run)
@then(parse("the output should not contain\n{expected_output}"))
@then(parse('the output should not contain "{expected_output}"')) @then(parse('the output should not contain "{expected_output}"'))
def output_should_not_contain(expected_output, cli_run): def output_should_not_contain(expected_output, cli_run):
assert expected_output not in cli_run["stdout"] assert expected_output not in cli_run["stdout"]
@then(parse("the output should be")) @then(parse("the output should be\n{expected_output}"))
def output_should_be_docstring(cli_run, docstring):
output_should_be(docstring, cli_run)
@then(parse('the output should be "{expected_output}"')) @then(parse('the output should be "{expected_output}"'))
def output_should_be(expected_output, cli_run): def output_should_be(expected_output, cli_run):
actual = cli_run["stdout"].strip() actual = cli_run["stdout"].strip()
@ -139,8 +119,7 @@ def output_should_be_columns_wide(cli_run, width):
@then( @then(
parse( parse(
'the default journal "{journal_file}" ' 'the default journal "{journal_file}" should be in the "{journal_dir}" directory'
'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):
@ -156,23 +135,18 @@ 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', 'the config for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
SHOULD_DICT, SHOULD_DICT,
) )
) )
@then(parse("the config {it_should:Should} contain", SHOULD_DICT))
def config_var_on_disk_docstring(config_on_disk, journal_name, it_should, docstring):
config_var_on_disk(config_on_disk, journal_name, it_should, docstring)
@then( @then(
parse( parse(
'the config for journal "{journal_name}" ' 'the config for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
'{it_should:Should} contain "{some_yaml}"',
SHOULD_DICT, SHOULD_DICT,
) )
) )
@then(parse('the config {it_should:Should} contain "{some_yaml}"', SHOULD_DICT)) @then(parse('the config {it_should:Should} contain "{some_yaml}"', SHOULD_DICT))
@then(parse("the config {it_should:Should} contain\n{some_yaml}", SHOULD_DICT))
def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml): def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
actual = config_on_disk actual = config_on_disk
if journal_name: if journal_name:
@ -181,37 +155,31 @@ def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
expected = YAML(typ="safe").load(some_yaml) expected = YAML(typ="safe").load(some_yaml)
actual_slice = actual actual_slice = actual
if isinstance(actual, 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) for key in expected.keys()} actual_slice = {key: actual.get(key, None) 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}" ' 'the config in memory for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
"{it_should:Should} contain",
SHOULD_DICT, SHOULD_DICT,
) )
) )
@then(parse("the config in memory {it_should:Should} contain", SHOULD_DICT))
def config_var_in_memory_docstring(
config_in_memory, journal_name, it_should, docstring
):
config_var_in_memory(config_in_memory, journal_name, it_should, docstring)
@then( @then(
parse( parse(
'the config in memory for journal "{journal_name}" ' 'the config in memory for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
'{it_should:Should} contain "{some_yaml}"',
SHOULD_DICT, SHOULD_DICT,
) )
) )
@then( @then(
parse('the config in memory {it_should:Should} contain "{some_yaml}"', SHOULD_DICT) parse('the config in memory {it_should:Should} contain "{some_yaml}"', SHOULD_DICT)
) )
@then(
parse("the config in memory {it_should:Should} contain\n{some_yaml}", SHOULD_DICT)
)
def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml): def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
actual = config_in_memory["overrides"] actual = config_in_memory["overrides"]
if journal_name: if journal_name:
@ -220,7 +188,7 @@ def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
expected = YAML(typ="safe").load(some_yaml) expected = YAML(typ="safe").load(some_yaml)
actual_slice = actual actual_slice = actual
if isinstance(actual, 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: get_nested_val(actual, key) for key in expected.keys()} actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()}
@ -237,23 +205,21 @@ def password_was_not_called(cli_run):
assert not cli_run["mocks"]["user_input"].return_value.input.called assert not cli_run["mocks"]["user_input"].return_value.input.called
@then(parse("the cache directory should contain the files")) @then(parse("the cache directory should contain the files\n{file_list}"))
def assert_dir_contains_files(cache_dir, docstring): def assert_dir_contains_files(file_list, cache_dir):
assert does_directory_contain_files(docstring, cache_dir["path"]) assert does_directory_contain_files(file_list, cache_dir["path"])
@then( @then(parse("the cache directory should contain {number} files"))
pytest_bdd_parsers_re(r"the cache directory should contain (?P<number>\d+) files")
)
def assert_dir_contains_n_files(cache_dir, number): def assert_dir_contains_n_files(cache_dir, number):
assert does_directory_contain_n_files(cache_dir["path"], number) assert does_directory_contain_n_files(cache_dir["path"], number)
@then(parse("the journal directory should contain")) @then(parse("the journal directory should contain\n{file_list}"))
def journal_directory_should_contain(config_on_disk, docstring): def journal_directory_should_contain(config_on_disk, file_list):
scoped_config = scope_config(config_on_disk, "default") scoped_config = scope_config(config_on_disk, "default")
assert does_directory_contain_files(docstring, scoped_config["journal"]) assert does_directory_contain_files(file_list, scoped_config["journal"])
@then(parse('journal "{journal_name}" should not exist')) @then(parse('journal "{journal_name}" should not exist'))
@ -288,10 +254,10 @@ def directory_should_not_exist(config_on_disk, it_should, journal_name):
assert dir_exists == it_should assert dir_exists == it_should
@then(parse('the content of file "{file_path}" in the cache should be')) @then(parse('the content of file "{file_path}" in the cache should be\n{file_content}'))
def content_of_file_should_be(file_path, cache_dir, docstring): def content_of_file_should_be(file_path, file_content, cache_dir):
assert cache_dir["exists"] assert cache_dir["exists"]
expected_content = docstring.strip().splitlines() expected_content = file_content.strip().splitlines()
with open(os.path.join(cache_dir["path"], file_path), "r") as f: with open(os.path.join(cache_dir["path"], file_path), "r") as f:
actual_content = f.read().strip().splitlines() actual_content = f.read().strip().splitlines()
@ -308,12 +274,12 @@ def content_of_file_should_be(file_path, cache_dir, docstring):
] ]
@then(parse("the cache should contain the files")) @then(parse("the cache should contain the files\n{file_list}"))
def cache_dir_contains_files(cache_dir, docstring): def cache_dir_contains_files(file_list, cache_dir):
assert cache_dir["exists"] assert cache_dir["exists"]
actual_files = os.listdir(cache_dir["path"]) actual_files = os.listdir(cache_dir["path"])
expected_files = docstring.split("\n") expected_files = file_list.split("\n")
# sort to deal with inconsistent default file ordering on different OS's # sort to deal with inconsistent default file ordering on different OS's
actual_files.sort() actual_files.sort()
@ -326,7 +292,7 @@ def cache_dir_contains_files(cache_dir, docstring):
def assert_output_is_valid_language(cli_run, language_name): def assert_output_is_valid_language(cli_run, language_name):
language_name = language_name.upper() language_name = language_name.upper()
if language_name == "XML": if language_name == "XML":
xml_tree = ET.fromstring(cli_run["stdout"]) xml_tree = ElementTree.fromstring(cli_run["stdout"])
assert xml_tree, "Invalid XML" assert xml_tree, "Invalid XML"
elif language_name == "JSON": elif language_name == "JSON":
assert json.loads(cli_run["stdout"]), "Invalid JSON" assert json.loads(cli_run["stdout"]), "Invalid JSON"
@ -362,11 +328,11 @@ def assert_parsed_output_item_count(node_name, number, parsed_output):
assert False, f"Language name {lang} not recognized" assert False, f"Language name {lang} not recognized"
@then(parse('"{field_name}" in the parsed output should {comparison}')) @then(parse('"{field_name}" in the parsed output should {comparison}\n{expected_keys}'))
def assert_output_field_content(field_name, comparison, parsed_output, docstring): def assert_output_field_content(field_name, comparison, expected_keys, parsed_output):
lang = parsed_output["lang"] lang = parsed_output["lang"]
obj = parsed_output["obj"] obj = parsed_output["obj"]
expected_keys = docstring.split("\n") expected_keys = expected_keys.split("\n")
if len(expected_keys) == 1: if len(expected_keys) == 1:
expected_keys = expected_keys[0] expected_keys = expected_keys[0]
@ -394,7 +360,7 @@ def assert_output_field_content(field_name, comparison, parsed_output, docstring
my_obj = my_obj[node] my_obj = my_obj[node]
if comparison == "be": if comparison == "be":
if isinstance(my_obj, str): if type(my_obj) is str:
assert expected_keys == my_obj, [my_obj, expected_keys] assert expected_keys == my_obj, [my_obj, expected_keys]
else: else:
assert set(expected_keys) == set(my_obj), [ assert set(expected_keys) == set(my_obj), [
@ -402,7 +368,7 @@ def assert_output_field_content(field_name, comparison, parsed_output, docstring
set(expected_keys), set(expected_keys),
] ]
elif comparison == "contain": elif comparison == "contain":
if isinstance(my_obj, str): if type(my_obj) is str:
assert expected_keys in my_obj, [my_obj, expected_keys] assert expected_keys in my_obj, [my_obj, expected_keys]
else: else:
assert all(elem in my_obj for elem in expected_keys), [ assert all(elem in my_obj for elem in expected_keys), [
@ -416,7 +382,7 @@ def assert_output_field_content(field_name, comparison, parsed_output, docstring
@then(parse('there should be {number:d} "{item}" elements')) @then(parse('there should be {number:d} "{item}" elements'))
def count_elements(number, item, cli_run): def count_elements(number, item, cli_run):
actual_output = cli_run["stdout"] actual_output = cli_run["stdout"]
xml_tree = ET.fromstring(actual_output) xml_tree = ElementTree.fromstring(actual_output)
assert len(xml_tree.findall(".//" + item)) == number assert len(xml_tree.findall(".//" + item)) == number
@ -446,13 +412,9 @@ def editor_filename_suffix(suffix, editor_state):
assert editor_state["tmpfile"]["name"].endswith(suffix), (editor_filename, suffix) assert editor_state["tmpfile"]["name"].endswith(suffix), (editor_filename, suffix)
@then(parse("the editor file content should {comparison}"))
def contains_editor_file_docstring(comparison, editor_state, docstring):
contains_editor_file(comparison, docstring, editor_state)
@then(parse('the editor file content should {comparison} "{str_value}"')) @then(parse('the editor file content should {comparison} "{str_value}"'))
@then(parse("the editor file content should {comparison} empty")) @then(parse("the editor file content should {comparison} empty"))
@then(parse("the editor file content should {comparison}\n{str_value}"))
def contains_editor_file(comparison, str_value, editor_state): def contains_editor_file(comparison, str_value, editor_state):
content = editor_state["tmpfile"]["content"] content = editor_state["tmpfile"]["content"]
# content = f'\n"""\n{content}\n"""\n' # content = f'\n"""\n{content}\n"""\n'

View file

@ -7,13 +7,7 @@ from contextlib import ExitStack
from pytest_bdd import when from pytest_bdd import when
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re from pytest_bdd.parsers import re
from pytest_bdd.steps import inject_fixture
# This is an undocumented and unsupported function:
# https://github.com/pytest-dev/pytest-bdd/issues/684
try:
from pytest_bdd.compat import inject_fixture # pytest_bdd 7.1.2 and later
except ImportError:
from pytest_bdd.steps import inject_fixture # pytest_bdd 7.1.1 and earlier
from jrnl.main import run from jrnl.main import run
@ -34,11 +28,7 @@ all_input = '("(?P<all_input>[^"]*)")'
# an empty line of input internally for testing purposes. # an empty line of input internally for testing purposes.
@when(re(f'we run "jrnl {command}" and {input_method}')) @when(parse('we run "jrnl {command}" and {input_method}\n{all_input}'))
def we_run_jrnl_docstring(capsys, keyring, request, command, input_method, docstring):
we_run_jrnl(capsys, keyring, request, command, input_method, docstring)
@when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}')) @when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}'))
@when(re(f'we run "jrnl {command}"(?! and)')) @when(re(f'we run "jrnl {command}"(?! and)'))
@when('we run "jrnl"') @when('we run "jrnl"')

View file

@ -1,45 +0,0 @@
# 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

View file

@ -6,6 +6,9 @@ from unittest import mock
import pytest import pytest
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.messages import Message
from jrnl.messages import MsgStyle
from jrnl.messages import MsgText
from jrnl.plugins.fancy_exporter import check_provided_linewrap_viability from jrnl.plugins.fancy_exporter import check_provided_linewrap_viability
from jrnl.plugins.yaml_exporter import YAMLExporter from jrnl.plugins.yaml_exporter import YAMLExporter
@ -33,8 +36,12 @@ class TestFancy:
class TestYaml: class TestYaml:
@mock.patch("jrnl.plugins.yaml_exporter.YAMLExporter.export_journal")
@mock.patch("builtins.open") @mock.patch("builtins.open")
def test_export_to_nonexisting_folder(self, mock_open): def test_export_to_nonexisting_folder(self, mock_open, mock_export_journal):
mock_export_journal.side_effect = JrnlException(
Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR)
)
with pytest.raises(JrnlException): with pytest.raises(JrnlException):
YAMLExporter.write_file("journal", "non-existing-path") YAMLExporter.write_file("journal", "non-existing-path")
mock_open.assert_not_called() mock_open.assert_not_called()

View file

@ -55,7 +55,7 @@ def test_empty():
def test_contains_alone(): def test_contains_alone():
assert cli_as_dict("-contains whatever") == expected_args(contains=["whatever"]) assert cli_as_dict("-contains whatever") == expected_args(contains="whatever")
def test_debug_alone(): def test_debug_alone():
@ -242,9 +242,7 @@ 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 colors.title green --config-override editor "nano" --config-override journal.scratchpad "/tmp/scratchpad"'
'--config-override editor "nano" '
'--config-override journal.scratchpad "/tmp/scratchpad"'
) )
assert parsed_args == expected_args( assert parsed_args == expected_args(
config_override=[ config_override=[
@ -296,7 +294,7 @@ class TestDeserialization:
) )
def test_deserialize_multiword_strings(self, input_str): def test_deserialize_multiword_strings(self, input_str):
runtime_config = make_yaml_valid_dict(input_str) runtime_config = make_yaml_valid_dict(input_str)
assert runtime_config.__class__ is dict assert runtime_config.__class__ == dict
assert input_str[0] in runtime_config assert input_str[0] in runtime_config
assert runtime_config[input_str[0]] == input_str[1] assert runtime_config[input_str[0]] == input_str[1]