[
  {
    "path": ".build/allow_all_python_version.py",
    "content": "import toml\n\npyproject = toml.load(\"pyproject.toml\")\n\npyproject[\"tool\"][\"poetry\"][\"dependencies\"][\"python\"] = \"*\"\n\nwith open(\"pyproject.toml\", \"w\") as toml_file:\n    toml.dump(pyproject, toml_file)\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.journal text eol=lf\n*.feature text eol=lf\npoetry.lock text eol=lf\npyrpoject.toml text eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: jrnl\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: Bug Report\ndescription: Create a report to help us improve\ntitle: \"Bug Report\"\nlabels: [ \":new:\", \"bug\" ]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        # Bug Report\n        Hello, and thank you for reporting an issue!\n\n        Please fill out the points below, as it will make our process much easier.\n\n  - type: textarea\n    id: diagnostic\n    attributes:\n      label: Diagnostic output\n      description: Run `jrnl --diagnostic` and paste the output below\n      placeholder: Paste output here\n    validations:\n      required: true\n\n  - type: textarea\n    id: current-behavior\n    attributes:\n      label: Current Behavior\n      description: Please put a short description of what is currently happening.\n      placeholder: Tell us what is happening\n    validations:\n      required: true\n\n  - type: textarea\n    id: expected-behavior\n    attributes:\n      label: Expected Behavior\n      description: Please write a short description of what you would expect to happen\n      placeholder: Tell us what should be happening\n    validations:\n      required: true\n\n  - type: textarea\n    id: repro-steps\n    attributes:\n      label: Repro Steps\n      description: |\n        Provide the steps to reproduce the problem.\n\n        Please be as precise as possible, since more info will let us help you faster.\n      placeholder: Repro steps\n    validations:\n      required: true\n\n  - type: textarea\n    id: debug-output\n    attributes:\n      label: Debug output\n      description: |\n        Please provide the output of your command with the `--debug` flag on.\n      placeholder: \"example: `jrnl --debug lorem ipsum`\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: other-info\n    attributes:\n      label: Other Information\n      description: >\n        Is there anything else we should know?\n\n        (e.g. more detailed explanation, stacktraces, related\n        issues, suggestions how to fix, links for us to have context, eg.\n        stackoverflow, gitter, etc)\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.yaml",
    "content": "name: Documentation Change\ndescription: Request or report any updates to our documentation (https://jrnl.sh)\ntitle: Documentation Change\nlabels: [ \":new:\", \"documentation\" ]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        # Documentation Change\n        Hello, and thank you for reporting an issue!\n\n        Please fill out the points below, as it will make our process much easier.\n\n  - type: textarea\n    id: affected-pages\n    attributes:\n      label: Affected Page(s)\n      description: >\n        Please tell us which page, or pages, from the documentation site\n        (https://jrnl.sh) are affected in this issue\n      placeholder: \"example: https://jrnl.sh/en/stable/overview\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: what-could-be-better\n    attributes:\n      label: What Could Be Better?\n      description: >\n        Please write a short description of what you hope can be clarified or\n        further explained.\n    validations:\n      required: true\n\n  - type: textarea\n    id: other-info\n    attributes:\n      label: Other Information\n      description: Is there anything else we should know that might be helpful?\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "content": "name: Feature Request\ndescription: Suggest an idea for jrnl\ntitle: \"Feature Report\"\nlabels: [ \":new:\", \"enhancement\" ]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        # Feature Request\n        Hello, and thank you for reporting an issue!\n\n        Please fill out the points below, as it will make our process much easier.\n\n  - type: textarea\n    id: user-case\n    attributes:\n      label: Use Case/Motivation\n      description: What is the motivation / use case for changing the behavior?\n      placeholder: Tell us about your idea\n    validations:\n      required: true\n\n  - type: textarea\n    id: example-usage\n    attributes:\n      label: Example Usage\n      description: Please provide examples of the usage you would like to see.\n      placeholder: e.g `jrnl --new-flag=\"super cool new feature\"`\n    validations:\n      required: true\n\n  - type: textarea\n    id: other-info\n    attributes:\n      label: Other Information\n      description: >\n        Is there anything else we should know?\n\n        (e.g. more detailed explanation, stacktraces, related\n        issues, suggestions how to fix, links for us to have context, eg.\n        stackoverflow, gitter, etc)\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/support_request.yaml",
    "content": "name: Support Request\ndescription: Get help with jrnl\ntitle: Support Request\nlabels: [ \":new:\", \"support\" ]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        # Support Request\n        Hello, and thank you for reporting an issue!\n\n        Please fill out the points below, as it will make our process much easier.\n\n  - type: textarea\n    id: diagnostic\n    attributes:\n      label: Diagnostic output\n      description: Run `jrnl --diagnostic` and paste the output below\n      placeholder: Paste output here\n    validations:\n      required: true\n\n  - type: textarea\n    id: current-behavior\n    attributes:\n      label: What are you trying to do?\n      description: Please put a short description of what is happening.\n      placeholder: Tell us what is happening\n    validations:\n      required: true\n\n  - type: textarea\n    id: tried\n    attributes:\n      label: What have you tried?\n      description: >\n        Have you tried anything to fix the problem? This can help give us more\n        information to help you with.\n      placeholder: Tell us what should be happening\n    validations:\n      required: true\n\n  - type: textarea\n    id: other-info\n    attributes:\n      label: Other Information\n      description: >\n        Is there anything else we should know?\n\n        (e.g. more detailed explanation, stacktraces, related\n        issues, suggestions how to fix, links for us to have context, eg.\n        stackoverflow, gitter, etc)\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nThank you for wanting to contribute!\n\nPlease fill out this description, and the checklist below.\n\nHere are some key points to include in your description:\n- What is this new code intended to do?\n- Are there any related issues?\n- What is the motivation for this change?\n- What is an example of usage, or changes to config files? (if applicable)\n-->\n\n### Checklist\n\n- [ ] I have read the [contributing doc](https://github.com/jrnl-org/jrnl/blob/develop/docs/contributing.md).\n- [ ] I have included a link to the relevant issue number.\n- [ ] I have checked to ensure there aren't other open [pull requests](../pulls)\n  for the same issue.\n- [ ] I have written new tests for these changes, as needed.\n<!--\nNOTE: Your PR may not be reviewed if there are any failing tests. You can run\ntests locally with `poe test` (see the contributing doc if you need help with\n`poe`), or use our automated tests after you submit your PR.\n-->\n"
  },
  {
    "path": ".github/actionlint-matcher.json",
    "content": "{\n  \"problemMatcher\": [\n    {\n      \"owner\": \"actionlint\",\n      \"pattern\": [\n        {\n          \"regexp\": \"^(?:\\\\x1b\\\\[\\\\d+m)?(.+?)(?:\\\\x1b\\\\[\\\\d+m)*:(?:\\\\x1b\\\\[\\\\d+m)*(\\\\d+)(?:\\\\x1b\\\\[\\\\d+m)*:(?:\\\\x1b\\\\[\\\\d+m)*(\\\\d+)(?:\\\\x1b\\\\[\\\\d+m)*: (?:\\\\x1b\\\\[\\\\d+m)*(.+?)(?:\\\\x1b\\\\[\\\\d+m)* \\\\[(.+?)\\\\]$\",\n          \"file\": 1,\n          \"line\": 2,\n          \"column\": 3,\n          \"message\": 4,\n          \"code\": 5\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/actions/run_tests/action.yaml",
    "content": "name: run jrnl tests\r\ndescription: Runs all jrnl tests on multiple platforms\r\ninputs:\r\n  cache-string:\r\n    description: 'Cache string secret. Change to bust the cache'\r\n    required: true\r\nruns:\r\n  using: \"composite\"\r\n  steps:\r\n  - run: git config --global core.autocrlf false\r\n    shell: bash\r\n\r\n  - name: Set up Python ${{ matrix.python-version }}\r\n    uses: actions/setup-python@v6\r\n    with:\r\n      python-version: ${{ matrix.python-version }}\r\n      allow-prereleases: true\r\n\r\n  - name: Capture full Python version in env\r\n    run: echo \"PYTHON_FULL_VERSION=$(python --version)\" >> $GITHUB_ENV\r\n    shell: bash\r\n\r\n  - name: poetry cache # Change CACHE_STRING secret to bust the cache\r\n    uses: actions/cache@v5\r\n    with:\r\n      path: .venv\r\n      key: ${{ runner.os }}-${{ hashFiles('poetry.lock') }}-${{ env.PYTHON_FULL_VERSION }}-${{ inputs.cache-string }}\r\n\r\n  - name: Install dependencies\r\n    run: |\r\n      echo '::group::poetry'\r\n      pip --disable-pip-version-check install poetry\r\n      poetry config --local virtualenvs.in-project true\r\n      echo '::endgroup::'\r\n\r\n      echo '::group::Other dependencies'\r\n      poetry sync\r\n      echo '::endgroup::'\r\n\r\n      echo 'DEPS_INSTALLED=true' >> $GITHUB_ENV\r\n    shell: bash\r\n\r\n  - name: Linting & Testing\r\n    if: ${{ env.DEPS_INSTALLED == 'true' }}\r\n    run: poetry run poe test\r\n    shell: bash\r\n"
  },
  {
    "path": ".github/lock.yml",
    "content": "# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app\n\n# Number of days of inactivity before a closed issue or pull request is locked\ndaysUntilLock: 90\n\n# Skip issues and pull requests created before a given timestamp. Timestamp must\n# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable\nskipCreatedBefore: false\n\n# Issues and pull requests with these labels will be ignored. Set to `[]` to disable\nexemptLabels: []\n\n# Label to add before locking, such as `outdated`. Set to `false` to disable\nlockLabel: ':lock:'\n\n# Comment to post before locking. Set to `false` to disable\nlockComment: >\n  This thread has been automatically locked since there has not been\n  any recent activity after it was closed. Please open a new issue for\n  related bugs. You can link back here from your new issue to continue\n  the conversation.\n\n# Assign `resolved` as the reason for locking. Set to `false` to disable\nsetLockReason: true\n\n# Limit to only `issues` or `pulls`\n# only: issues\n\n# Optionally, specify configuration settings just for `issues` or `pulls`\n# issues:\n#   exemptLabels:\n#     - help-wanted\n#   lockLabel: outdated\n\n# pulls:\n#   daysUntilLock: 30\n\n# Repository to extend settings from\n# _extends: repo\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\"\n  ],\n  \"schedule\": [ \"at any time\" ],\n  \"prConcurrentLimit\": 10,\n  \"prHourlyLimit\": 10,\n  \"reviewers\": [\n    \"wren\",\n    \"micahellison\"\n  ],\n  \"labels\": [ \"packaging\" ]\n}\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - ':pushpin:'\n  - critical\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/changelog.yaml",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nname: Changelog\n\non:\n  push:\n    branches:\n      - develop\n    tags:\n      - 'v*'\n\njobs:\n  generate:\n    if: >\n      ! contains(github.event.head_commit.message, '[ci skip]') &&\n      ! (\n        startsWith(github.event.head_commit.message, 'Increment version to v') &&\n        startsWith(github.ref, 'refs/heads/')\n      )\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          token: ${{ secrets.JRNL_BOT_TOKEN }}\n\n      - name: Check branch for new commits, and env vars\n        run: |\n          echo \"::group::git fetch origin --tags --force\"\n          git fetch origin --tags --force\n          echo \"::endgroup::\"\n\n          TAG_REGEX='include-all'\n          echo \"::debug::GITHUB_REF: $GITHUB_REF\"\n          BRANCH=\"${GITHUB_REF##*/}\"\n\n          if [[ $GITHUB_REF =~ ^refs/tags/ ]]; then\n            # This is a tag build (i.e. a release)\n            echo '::debug::Release build'\n            if [[ ! $BRANCH =~ ^v[0-9]+(\\.[0-9]+){1,2}(-(alpha|beta)([0-9]+)?)?$ ]]; then\n              echo \"::error::Invalid tag format: ${BRANCH}\"\n              exit 1\n            fi\n\n            RELEASE=1\n            BRANCH=develop\n            git checkout $BRANCH\n\n            # if actual release (not pre), then changelog should exclude prereleases on update\n            prerelease_regex='(alpha|beta)'\n            if [[ ! ${GITHUB_REF##*/} =~ $prerelease_regex ]]; then\n              echo '::debug::Actual release (not a prerelease)'\n              TAG_REGEX=\"$prerelease_regex\"\n              echo \"FULL_RELEASE=true\" >> \"$GITHUB_ENV\"\n            fi\n          fi\n          echo \"::debug::TAG_REGEX: $TAG_REGEX\"\n\n          if [[ \"$(git rev-parse \"origin/$BRANCH\")\" != \"$GITHUB_SHA\" ]]; then\n            # Normal build on a branch (no tag)\n            echo \"::debug::BRANCH: $BRANCH $(git rev-parse \"origin/$BRANCH\")\"\n            echo \"::debug::GITHUB_SHA: $GITHUB_SHA\"\n            echo \"::error::$BRANCH has been updated since build started. Aborting changelog.\"\n            exit 1\n          fi\n\n          SINCE_TAG=$(git tag --sort=-creatordate | grep -Ev \"$TAG_REGEX\" | awk \"NR==$(( 1 + ${RELEASE:-0} ))\")\n\n          echo \"::debug::BRANCH: $BRANCH\"\n          echo \"::debug::TAG_REGEX: $TAG_REGEX\"\n          echo \"::debug::FILENAME: CHANGELOG.md\"\n          echo \"::debug::SINCE_TAG: $SINCE_TAG\"\n\n          {\n          echo \"BRANCH=$BRANCH\"\n          echo \"TAG_REGEX=$TAG_REGEX\"\n          echo \"FILENAME=CHANGELOG.md\"\n          echo \"SINCE_TAG=$SINCE_TAG\"\n          } >> \"$GITHUB_ENV\"\n\n      - name: Prep changelog file (clear out old lines)\n        run: |\n          # delete the top of the changelog up to the correct tag\n          tagline=$(grep -n \"^## \\[${SINCE_TAG}\\]\" \"$FILENAME\" | awk '{print $1}' FS=':' | head -1)\n          echo \"tagline: ${tagline}\"\n\n          if [[ -z $tagline ]]; then\n            echo \"::error::Something is wrong. ${SINCE_TAG} isn't in the changelog.\"\n            exit 1\n          fi\n\n          if [[ $tagline == 1 ]]; then\n            echo \"::error::Something is wrong.\"\n            echo \"::error::The latest release ${SINCE_TAG} is the first line in the changelog,\"\n            echo \"::error::but the h1 '# Changelog' should always be the first line.\"\n            exit 1\n          fi\n\n          sed -i \"1,$(( tagline - 1 ))d\" \"$FILENAME\"\n          # delete generated line (or it will be added multiple times)\n          sed -i '/This Changelog was automatically generated by/d' \"$FILENAME\"\n          # delete trailing empty lines\n          sed -i -e :a -e '/^\\n*$/{$d;N;};/\\n$/ba' \"$FILENAME\"\n\n      - name: Generate changelog\n        uses: heinrichreimer/action-github-changelog-generator@v2.1.1\n        with:\n          # see: https://github.com/heinrichreimer/action-github-changelog-generator\n          repo: jrnl-org/jrnl\n          token: ${{ secrets.JRNL_BOT_TOKEN }}\n          base: CHANGELOG.md\n          addSections: '{\"build\":{\"prefix\":\"**Build:**\",\"labels\":[\"build\"]},\"docs\":{\"prefix\":\"**Documentation:**\",\"labels\":[\"documentation\"]},\"packaging\":{\"prefix\":\"**Packaging:**\",\"labels\":[\"packaging\"]}}'\n          issues: true\n          pullRequests: true\n          issuesWoLabels: false\n          unreleased: true\n          compareLink: true\n          includeLabels: bug,enhancement,documentation,build,packaging,deprecated\n          excludeLabels: stale,wontfix\n          excludeTagsRegex: ${{ env.TAG_REGEX }}\n          sinceTag: ${{ env.SINCE_TAG }}\n          maxIssues: 150\n          releaseUrl: https://pypi.org/project/jrnl/%s/\n          releaseBranch: develop\n          verbose: false\n          author: true\n\n      - name: Small fixes\n        run: |\n          # Change unreleased link to correct url\n          sed -i 's!https://pypi.org/project/jrnl/HEAD/!https://github.com/jrnl-org/jrnl/!' \"$FILENAME\"\n\n      - name: Diff and consistency check\n        run: |\n          git diff\n          if [[ $(grep -c '^# Changelog$' \"$FILENAME\") != 1 ]]; then\n            echo '::error::Something is wrong with the changelog.'\n            exit 1\n          fi\n          SOMETHING_CHANGED=false\n          git diff --exit-code || SOMETHING_CHANGED=true\n          echo \"::debug::SOMETHING_CHANGED: $SOMETHING_CHANGED\"\n          echo \"SOMETHING_CHANGED=$SOMETHING_CHANGED\" >> \"$GITHUB_ENV\"\n\n      - name: Commit\n        if: env.SOMETHING_CHANGED == 'true'\n        run: |\n          git config --global user.name \"${{ secrets.JRNL_BOT_NAME }}\"\n          git config --global user.email \"${{ secrets.JRNL_BOT_EMAIL }}\"\n          git add \"$FILENAME\"\n          git commit -m \"Update changelog [ci skip]\"\n          git push origin \"$BRANCH\"\n\n      - name: Update tag to include changelog\n        if: startsWith(env.GITHUB_REF, 'refs/tags/')\n        run: |\n          # This is a tag build (releases and prereleases)\n          # update the tag to include the changelog\n          git tag -fam \"$GITHUB_REF_NAME\" \"$GITHUB_REF_NAME\"\n          git push --tags --force\n\n      - name: Merge to Release branch\n        if: env.FULL_RELEASE == 'true'\n        run: |\n          git fetch --unshallow origin\n          git checkout release\n          git merge --ff-only \"$BRANCH\"\n          git push origin release\n\n"
  },
  {
    "path": ".github/workflows/docs.yaml",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nname: Docs\n\non:\n  push:\n    branches: [ develop, release ]\n    paths:\n    - 'docs/**'\n    - 'docs_theme/**'\n    - 'mkdocs.yml'\n    - 'readthedocs.yml'\n    - '.github/workflows/docs.yaml'\n    - 'tasks.py'\n    - 'pyproject.toml'\n  pull_request:\n    branches: [ develop ]\n    paths:\n    - 'docs/**'\n    - 'docs_theme/**'\n    - 'mkdocs.yml'\n    - 'readthedocs.yml'\n    - '.github/workflows/docs.yaml'\n    - 'tasks.py'\n    - 'pyproject.toml'\n\njobs:\n  accessibility:\n    if: contains(toJson(github.event.commits), '[ci skip]') == false\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n      matrix:\n        python-version: [ '3.11' ]\n        os: [ ubuntu-latest ]\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Set up Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Setup Node.js environment\n      uses: actions/setup-node@main\n\n    - name: Capture full Python version in env\n      run: echo \"PYTHON_FULL_VERSION=$(python --version)\" >> \"$GITHUB_ENV\"\n\n    - name: poetry cache\n      uses: actions/cache@v5\n      with:\n        path: .venv\n        key: ${{ runner.os }}-${{ hashFiles('poetry.lock') }}-${{ env.PYTHON_FULL_VERSION }}-${{ secrets.CACHE_STRING }}\n\n    - name: npm cache\n      uses: actions/cache@v5\n      with:\n        path: node_modules\n        key: ${{ runner.os }}-pa11y-cache-${{ secrets.CACHE_STRING }}\n\n    - name: puppeteer cache for pa11y\n      uses: actions/cache@v5\n      with:\n        path: home/runner/.cache/puppeteer\n        key: ${{ runner.os }}-puppeteer-cache-${{ secrets.CACHE_STRING }}\n\n    - name: Install dependencies\n      run: |\n        pip install poetry\n        poetry config --local virtualenvs.in-project true\n        poetry sync --no-root\n        npm install\n        npx puppeteer browsers install chrome\n        echo \"node_modules/.bin\" >> \"$GITHUB_PATH\"\n\n    - name: Start docs server\n      run: poetry run poe docs-run &\n\n    - name: Accessibility testing (Pa11y)\n      run: poetry run poe docs-check\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nname: Release\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'Version (e.g. v2.5, v2.5.1-beta, v2.6-beta2)'\n        type: string\n        required: true\n      include_repo_version:\n        description: 'Update version in repo?'\n        type: boolean\n        required: true\n        default: true\n      include_pypi:\n        description: 'Publish to PyPI?'\n        type: boolean\n        required: true\n        default: true\n\njobs:\n  validate:\n    name: \"Validate version string\"\n    runs-on: ubuntu-latest\n    steps:\n    - name: Validate version\n      run: |\n        JRNL_VERSION=\"${{ github.event.inputs.version }}\"\n        echo \"::debug::version: $JRNL_VERSION\"\n        if [[ ! $JRNL_VERSION =~ ^v[0-9]+(\\.[0-9]+){1,2}(-(alpha|beta)([0-9]+)?)?$ ]]; then\n          echo\n          echo \"::error::Bad version\"\n          echo\n          echo \"Version string should match pattern above.\"\n          echo \"Here are some examples of valid version numbers:\"\n          echo\n          echo \"  v2.5\"\n          echo \"  v2.5-alpha\"\n          echo \"  v2.5-beta\"\n          echo \"  v2.5.1\"\n          echo \"  v2.5.1-alpha\"\n          echo \"  v2.5.1-beta\"\n          exit 1\n        fi\n\n  release_pypi:\n    needs: validate\n    name: \"Release to PyPI\"\n    runs-on: ubuntu-latest\n    outputs:\n      pypi_version: ${{ steps.pypi-version-getter.outputs.pypi_version }}\n    env:\n      HOME_REPO: ${{ secrets.HOME_REPO }}\n    steps:\n    - name: Get version\n      run: |\n        JRNL_VERSION=\"${{ github.event.inputs.version }}\"\n        echo \"::debug::version: $JRNL_VERSION\"\n        echo \"JRNL_VERSION=$JRNL_VERSION\" >> \"$GITHUB_ENV\"\n\n    - name: Set up Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: '3.14'\n\n    - name: Checkout repo\n      uses: actions/checkout@v6\n      with:\n        token: ${{ secrets.JRNL_BOT_TOKEN }}\n\n    - name: Config git user\n      run: |\n        git config --global user.name \"${{ secrets.JRNL_BOT_NAME }}\"\n        git config --global user.email \"${{ secrets.JRNL_BOT_EMAIL }}\"\n\n    - name: Install dependencies\n      run: pip install poetry\n\n    - name: Update version in files\n      if: ${{ github.event.inputs.include_repo_version == 'true' }}\n      run: |\n        poetry version \"$JRNL_VERSION\"\n        echo \"__version__ = \\\"$JRNL_VERSION\\\"\" > jrnl/__version__.py\n\n    - name: Commit updated files\n      if: ${{ github.event.inputs.include_repo_version == 'true' && github.repository == env.HOME_REPO }}\n      run: |\n        git add pyproject.toml jrnl/__version__.py\n        git commit -m \"Increment version to ${JRNL_VERSION}\"\n        git tag -a -m \"$JRNL_VERSION\" \"$JRNL_VERSION\"\n        git push\n        git push --tags\n\n    - name: Build\n      run: poetry build\n\n    - name: Deploy to PyPI\n      if: ${{ github.event.inputs.include_pypi == 'true' && github.repository == env.HOME_REPO }}\n      env:\n        POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}\n      run: poetry publish\n\n    - name: Get PyPI version\n      id: pypi-version-getter\n      run: |\n        pypi_version=\"$(find dist/jrnl-*.tar.gz | sed -r 's!dist/jrnl-(.*)\\.tar\\.gz!\\1!')\"\n        echo \"pypi_version=$pypi_version\" >> \"$GITHUB_OUTPUT\"\n"
  },
  {
    "path": ".github/workflows/testing_pipelines.yaml",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nname: Testing Pipeline Files\n\non:\n  push:\n    branches: [ develop, release ]\n    paths:\n    - '.github/workflows/**'\n    - '.github/actions/**'\n  pull_request:\n    branches: [ develop ]\n    paths:\n    - '.github/workflows/**'\n    - '.github/actions/**'\n  schedule:\n    - cron: '0 0 * * SAT'\n\njobs:\n  test:\n    if: >\n      ! contains(github.event.head_commit.message, '[ci skip]')\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ ubuntu-latest ]\n    steps:\n      - run: git config --global core.autocrlf false\n      - uses: actions/checkout@v6\n      - name: Check workflow files\n        uses: docker://rhysd/actionlint:latest\n        with:\n          args: -color\n"
  },
  {
    "path": ".github/workflows/testing_prs.yaml",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nname: Testing\n\non:\n  push:\n    branches: [ develop, release ]\n    paths:\n    - 'jrnl/**'\n    - 'features/**'\n    - 'tests/**'\n    - 'poetry.lock'\n    - 'pyproject.toml'\n    - '.github/workflows/testing_prs.yaml'\n    - 'tasks.py'\n  pull_request:\n    branches: [ develop ]\n    paths:\n    - 'jrnl/**'\n    - 'features/**'\n    - 'tests/**'\n    - 'poetry.lock'\n    - 'pyproject.toml'\n    - '.github/workflows/testing_prs.yaml'\n    - 'tasks.py'\n\ndefaults:\n  run:\n    shell: bash # needed to prevent Windows from using PowerShell\n\njobs:\n  test:\n    if: >\n      ! contains(github.event.head_commit.message, '[ci skip]')\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14' ]\n        os: [ ubuntu-latest, macos-latest, windows-latest ]\n    steps:\n      - run: git config --global core.autocrlf false\n      - uses: actions/checkout@v6\n      - name: Run tests\n        uses: ./.github/actions/run_tests\n        with:\n          cache-string: ${{ secrets.CACHE_STRING }}\n"
  },
  {
    "path": ".github/workflows/testing_schedule.yaml",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nname: Testing\n\non:\n  schedule:\n    - cron: '0 0 * * SAT'\n\ndefaults:\n  run:\n    shell: bash # needed to prevent Windows from using PowerShell\n\njobs:\n  test_all:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14' ]\n        os: [ ubuntu-latest, macos-latest, windows-latest ]\n    steps:\n      - run: git config --global core.autocrlf false\n      - uses: actions/checkout@v6\n      - name: Run tests\n        uses: ./.github/actions/run_tests\n        with:\n          cache-string: ${{ secrets.CACHE_STRING }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled DLL and Shared Library files\n*.py[cod]\n*.so\n\n# Packages\n*.egg\n*.egg-info/\n.installed.cfg\nbin/\nbuild/\ndevelop-eggs/\ndist/\neggs/\nlib64/\nparts/\nsdist/\n.tox/\nvar/\nnode_modules/\n__pycache__/\n.pytest_cache/\n.flakeheaven_cache/\n\n# Versioning\n.python-version\n.tool-versions\n\n# Installer logs\n.DS_Store\n.travis-solo\nIcon\npip-log.txt\n\n# Documentation\n_build\n_sources\n_static\ncoverage.xml\nexp/\nobjects.inv\nsearchindex.js\n\n# virtualenv\n.venv*/\nenv/\nenv*/\nvenv*/\n\n# Editor and IDE specific files\n#  Since contributors may want to user a variety of development tools it is \n#  recommended that editor specific file types be ignored globally by each\n#  contributor via a global gitignore. Instructions for setting up a global\n#  ignore file can be found here:\n#  https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files\n#  (Configuring ignored files for all repositories on your computer)\n#  Examples of such files are:\n# MS Visual Studio (PyTools)\n#   obj\n#   *.suo\n# PyCharm\n#   .idea/\n# VS Code\n#   .vscode/settings.json"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# readthedocs.yml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the OS\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3\"\n\n\n# Build documentation in the docs/ directory\nmkdocs:\n  configuration: mkdocs.yml\n  fail_on_warning: false\n\n# Optionally build your docs in additional formats such as PDF\nformats:\n  - pdf\n  - epub\n\n# Optionally set the version of Python and requirements required to build your docs\npython:\n  install:\n    - requirements: docs_theme/requirements.txt\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [v4.3](https://pypi.org/project/jrnl/v4.3/) (2026-02-24)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.3-beta...v4.3)\n\n**Build:**\n\n- Modernize pyproject [\\#2046](https://github.com/jrnl-org/jrnl/pull/2046) ([micahellison](https://github.com/micahellison))\n- Remove release step to publish to Homebrew [\\#1994](https://github.com/jrnl-org/jrnl/pull/1994) ([micahellison](https://github.com/micahellison))\n\n**Documentation:**\n\n- Doc missing: Setting location of journal [\\#2019](https://github.com/jrnl-org/jrnl/issues/2019)\n- \\[Documentation change\\] Update command line reference [\\#2032](https://github.com/jrnl-org/jrnl/pull/2032) ([katielin019](https://github.com/katielin019))\n- Add example of how to configure location of a journal [\\#2020](https://github.com/jrnl-org/jrnl/pull/2020) ([matthiasbeyer](https://github.com/matthiasbeyer))\n\n**Packaging:**\n\n- Update actions/checkout action to v6 [\\#2053](https://github.com/jrnl-org/jrnl/pull/2053) ([renovate[bot]](https://github.com/apps/renovate))\n- Update actions/cache action to v5 [\\#2052](https://github.com/jrnl-org/jrnl/pull/2052) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to \\>=14.3.2,\\<14.4.0 [\\#2051](https://github.com/jrnl-org/jrnl/pull/2051) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency python to 3.14 [\\#2050](https://github.com/jrnl-org/jrnl/pull/2050) ([renovate[bot]](https://github.com/apps/renovate))\n- Update actions/setup-python action to v6 [\\#2031](https://github.com/jrnl-org/jrnl/pull/2031) ([renovate[bot]](https://github.com/apps/renovate))\n- Add support for Python 3.14 [\\#2015](https://github.com/jrnl-org/jrnl/pull/2015) ([micahellison](https://github.com/micahellison))\n- Update dependency pa11y-ci to v4 [\\#2014](https://github.com/jrnl-org/jrnl/pull/2014) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to \\>=14.1.0, \\<14.2.0 [\\#2013](https://github.com/jrnl-org/jrnl/pull/2013) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency requests to v2.32.4 [\\#2010](https://github.com/jrnl-org/jrnl/pull/2010) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest to v8.4.1 [\\#2009](https://github.com/jrnl-org/jrnl/pull/2009) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest-xdist to v3.8.0 [\\#2005](https://github.com/jrnl-org/jrnl/pull/2005) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruamel.yaml to v0.18.14 [\\#2004](https://github.com/jrnl-org/jrnl/pull/2004) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency cryptography to v45 [\\#2001](https://github.com/jrnl-org/jrnl/pull/2001) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tox to v4.28.3 [\\#2000](https://github.com/jrnl-org/jrnl/pull/2000) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency poethepoet to v0.36.0 [\\#1993](https://github.com/jrnl-org/jrnl/pull/1993) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruff to v0.12.5 [\\#1991](https://github.com/jrnl-org/jrnl/pull/1991) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to v14 [\\#1989](https://github.com/jrnl-org/jrnl/pull/1989) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency python to 3.13 [\\#1988](https://github.com/jrnl-org/jrnl/pull/1988) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tox to v4.25.0 [\\#1986](https://github.com/jrnl-org/jrnl/pull/1986) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tzlocal to v5.3.1 [\\#1984](https://github.com/jrnl-org/jrnl/pull/1984) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency jinja2 to v3.1.6 [\\#1983](https://github.com/jrnl-org/jrnl/pull/1983) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency poethepoet to v0.33.1 [\\#1982](https://github.com/jrnl-org/jrnl/pull/1982) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest to v8.3.5 [\\#1981](https://github.com/jrnl-org/jrnl/pull/1981) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency cryptography to v44.0.2 [\\#1980](https://github.com/jrnl-org/jrnl/pull/1980) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruff to v0.11.3 [\\#1978](https://github.com/jrnl-org/jrnl/pull/1978) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v4.2.1](https://pypi.org/project/jrnl/v4.2.1/) (2025-02-25)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.2...v4.2.1)\n\n**Documentation:**\n\n- Typing animation in landing page is broken [\\#1969](https://github.com/jrnl-org/jrnl/issues/1969)\n\n**Packaging:**\n\n- Update dependency pytest to \\>=8.1.1 [\\#1974](https://github.com/jrnl-org/jrnl/pull/1974) ([wren](https://github.com/wren))\n- Update dependency black to v25 [\\#1973](https://github.com/jrnl-org/jrnl/pull/1973) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tzlocal to v5.3 [\\#1972](https://github.com/jrnl-org/jrnl/pull/1972) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruamel.yaml to v0.18.10 [\\#1967](https://github.com/jrnl-org/jrnl/pull/1967) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency jinja2 to v3.1.5 [\\#1966](https://github.com/jrnl-org/jrnl/pull/1966) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency cryptography to v44 [\\#1962](https://github.com/jrnl-org/jrnl/pull/1962) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest-bdd to v8.1.0 [\\#1952](https://github.com/jrnl-org/jrnl/pull/1952) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency poethepoet to v0.32.2 [\\#1951](https://github.com/jrnl-org/jrnl/pull/1951) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency keyring to v25.6.0 [\\#1948](https://github.com/jrnl-org/jrnl/pull/1948) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruff to v0.9.7 [\\#1947](https://github.com/jrnl-org/jrnl/pull/1947) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to v13.9.4 [\\#1946](https://github.com/jrnl-org/jrnl/pull/1946) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tox to v4.24.1 [\\#1945](https://github.com/jrnl-org/jrnl/pull/1945) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v4.2](https://pypi.org/project/jrnl/v4.2/) (2024-11-17)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.2-beta...v4.2)\n\n**Implemented enhancements:**\n\n- Add Python 3.13 support [\\#1893](https://github.com/jrnl-org/jrnl/issues/1893)\n- Add calendar heatmap display format [\\#1759](https://github.com/jrnl-org/jrnl/pull/1759) ([alichtman](https://github.com/alichtman))\n\n**Fixed bugs:**\n\n- -contains doesn't accept multiple search terms, doesn't work with -and [\\#1877](https://github.com/jrnl-org/jrnl/issues/1877)\n- Tests failing on develop branch starting with pytest-bdd 7.1.2 [\\#1875](https://github.com/jrnl-org/jrnl/issues/1875)\n- Ignore color when used in a pipeline [\\#1839](https://github.com/jrnl-org/jrnl/issues/1839)\n- 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))\n\n**Documentation:**\n\n- Recommend pipx as default installation method [\\#1888](https://github.com/jrnl-org/jrnl/issues/1888)\n- Remove documentation recommendation to install pipx through brew or pip [\\#1886](https://github.com/jrnl-org/jrnl/issues/1886)\n- Document security risks of using a computer that someone else has admin access to [\\#1793](https://github.com/jrnl-org/jrnl/issues/1793)\n- 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))\n- Docs accessibility checker failure - contrast ratio [\\#1934](https://github.com/jrnl-org/jrnl/issues/1934)\n- Docs accessibility test runner failing [\\#1932](https://github.com/jrnl-org/jrnl/issues/1932)\n\n**Packaging:**\n\n- Update actions/cache action to v4 [\\#1847](https://github.com/jrnl-org/jrnl/pull/1847) ([renovate[bot]](https://github.com/apps/renovate))\n- Update actions/setup-python action to v5 [\\#1848](https://github.com/jrnl-org/jrnl/pull/1848) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency black to v24.8.0 [\\#1923](https://github.com/jrnl-org/jrnl/pull/1923) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency cryptography to v43.0.3 [\\#1942](https://github.com/jrnl-org/jrnl/pull/1942) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency jinja2 to v3.1.4 [\\#1892](https://github.com/jrnl-org/jrnl/pull/1892) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency keyring to v25.4.1 [\\#1924](https://github.com/jrnl-org/jrnl/pull/1924) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency mkdocs to v1.6.1 [\\#1895](https://github.com/jrnl-org/jrnl/pull/1895) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pa11y-ci to v3.1.0 [\\#1831](https://github.com/jrnl-org/jrnl/pull/1831) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency parse-type to v0.6.4 [\\#1936](https://github.com/jrnl-org/jrnl/pull/1936) ([renovate[bot]](https://github.com/apps/renovate))\n- 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))\n- Update dependency poethepoet to v0.29.0 [\\#1925](https://github.com/jrnl-org/jrnl/pull/1925) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest to v7.4.4 [\\#1845](https://github.com/jrnl-org/jrnl/pull/1845) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest-bdd to v7.3.0 [\\#1896](https://github.com/jrnl-org/jrnl/pull/1896) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest-xdist to v3.6.1 [\\#1897](https://github.com/jrnl-org/jrnl/pull/1897) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency python-dateutil to v2.9.0 [\\#1898](https://github.com/jrnl-org/jrnl/pull/1898) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency requests to v2.32.3 [\\#1899](https://github.com/jrnl-org/jrnl/pull/1899) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to v13.9.2 [\\#1937](https://github.com/jrnl-org/jrnl/pull/1937) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruamel.yaml to v0.18.6 [\\#1855](https://github.com/jrnl-org/jrnl/pull/1855) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruff to v0.7.0 [\\#1938](https://github.com/jrnl-org/jrnl/pull/1938) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tox to v4.23.0 [\\#1935](https://github.com/jrnl-org/jrnl/pull/1935) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency typed.js to v2.1.0 [\\#1861](https://github.com/jrnl-org/jrnl/pull/1861) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency xmltodict to v0.14.2 [\\#1940](https://github.com/jrnl-org/jrnl/pull/1940) ([renovate[bot]](https://github.com/apps/renovate))\n- Update nick-invision/retry action to v3 [\\#1851](https://github.com/jrnl-org/jrnl/pull/1851) ([renovate[bot]](https://github.com/apps/renovate))\n- 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))\n\n## [v4.1](https://pypi.org/project/jrnl/v4.1/) (2023-11-04)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.1-beta2...v4.1)\n\n**Build:**\n\n- Add Python 3.12 support [\\#1761](https://github.com/jrnl-org/jrnl/pull/1761) ([micahellison](https://github.com/micahellison))\n- Set new required build fields in the ReadTheDocs config file [\\#1803](https://github.com/jrnl-org/jrnl/pull/1803) ([micahellison](https://github.com/micahellison))\n- 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))\n\n**Documentation:**\n\n- 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))\n\n**Packaging:**\n\n- Drop/replace ansiwrap dependency [\\#1191](https://github.com/jrnl-org/jrnl/issues/1191)\n- Use rich instead of ansiwrap to wrap text [\\#1693](https://github.com/jrnl-org/jrnl/pull/1693) ([micahellison](https://github.com/micahellison))\n- Update actions/checkout action to v4 [\\#1788](https://github.com/jrnl-org/jrnl/pull/1788) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency black to v23.10.1 [\\#1811](https://github.com/jrnl-org/jrnl/pull/1811) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency cryptography to v41.0.5 [\\#1815](https://github.com/jrnl-org/jrnl/pull/1815) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency keyring to v24.2.0 [\\#1760](https://github.com/jrnl-org/jrnl/pull/1760) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency mkdocs to v1.5.3 [\\#1795](https://github.com/jrnl-org/jrnl/pull/1795) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency parse-type to v0.6.2 [\\#1762](https://github.com/jrnl-org/jrnl/pull/1762) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency poethepoet to v0.24.1 [\\#1806](https://github.com/jrnl-org/jrnl/pull/1806) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest to v7.4.3 [\\#1816](https://github.com/jrnl-org/jrnl/pull/1816) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest-bdd to v7 [\\#1807](https://github.com/jrnl-org/jrnl/pull/1807) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to v13.6.0 [\\#1794](https://github.com/jrnl-org/jrnl/pull/1794) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruamel.yaml to v0.18.3 [\\#1813](https://github.com/jrnl-org/jrnl/pull/1813) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruff to v0.1.3 [\\#1810](https://github.com/jrnl-org/jrnl/pull/1810) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tox to v4.11.3 [\\#1782](https://github.com/jrnl-org/jrnl/pull/1782) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tzlocal to v5.2 [\\#1814](https://github.com/jrnl-org/jrnl/pull/1814) ([renovate[bot]](https://github.com/apps/renovate))\n\n**Special thanks:**\n- 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)\n\n## [v4.0.1](https://pypi.org/project/jrnl/v4.0.1/) (2023-06-20)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0.1-beta...v4.0.1)\n\n**Fixed bugs:**\n\n- jrnl crashes when running `jrnl --list --format json` and `jrnl --list --format yaml` [\\#1737](https://github.com/jrnl-org/jrnl/issues/1737)\n- Refactor --template code [\\#1711](https://github.com/jrnl-org/jrnl/pull/1711) ([micahellison](https://github.com/micahellison))\n\n**Build:**\n\n- Fix linting issue in CI pipeline [\\#1743](https://github.com/jrnl-org/jrnl/pull/1743) ([wren](https://github.com/wren))\n\n**Packaging:**\n\n- Update dependency ruamel.yaml to v0.17.28 [\\#1749](https://github.com/jrnl-org/jrnl/pull/1749) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency requests to v2.31.0 [\\#1748](https://github.com/jrnl-org/jrnl/pull/1748) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ruamel.yaml to v0.17.26 [\\#1746](https://github.com/jrnl-org/jrnl/pull/1746) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tzlocal to v5 [\\#1741](https://github.com/jrnl-org/jrnl/pull/1741) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest-xdist to v3.3.1 [\\#1740](https://github.com/jrnl-org/jrnl/pull/1740) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency poethepoet to v0.20.0 [\\#1735](https://github.com/jrnl-org/jrnl/pull/1735) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency mkdocs to v1.4.3 [\\#1733](https://github.com/jrnl-org/jrnl/pull/1733) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to v13.3.5 [\\#1729](https://github.com/jrnl-org/jrnl/pull/1729) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency requests to v2.30.0 [\\#1728](https://github.com/jrnl-org/jrnl/pull/1728) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tox to v4.5.1 [\\#1727](https://github.com/jrnl-org/jrnl/pull/1727) ([renovate[bot]](https://github.com/apps/renovate))\n- 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))\n- Update dependency flake8-simplify to v0.20.0 [\\#1716](https://github.com/jrnl-org/jrnl/pull/1716) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v4.0](https://pypi.org/project/jrnl/v4.0/) (2023-05-20)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta3...v4.0)\n\n🚨 **BREAKING CHANGES** 🚨\n\n**Deprecated:**\n\n- 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))\n\n**Implemented enhancements:**\n\n- 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))\n- Prompt to include colors in config when first running jrnl [\\#1687](https://github.com/jrnl-org/jrnl/pull/1687) ([micahellison](https://github.com/micahellison))\n- Add ability to use template with `--template` [\\#1667](https://github.com/jrnl-org/jrnl/pull/1667) ([alichtman](https://github.com/alichtman))\n- Search for entries with no tags or stars with `-not -starred` and `-not -tagged` [\\#1663](https://github.com/jrnl-org/jrnl/pull/1663) ([cjcon90](https://github.com/cjcon90))\n- Refactor flow for easier access to some files \\(avoid things like `jrnl.Journal.Journal` and `jrnl.jrnl` co-existing\\) [\\#1662](https://github.com/jrnl-org/jrnl/pull/1662) ([wren](https://github.com/wren))\n- Add more type hints [\\#1642](https://github.com/jrnl-org/jrnl/pull/1642) ([outa](https://github.com/outa))\n- Add `rich` handler to debug logging [\\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren))\n- Rework Encryption to enable future support of other encryption methods [\\#1602](https://github.com/jrnl-org/jrnl/pull/1602) ([wren](https://github.com/wren))\n\n**Fixed bugs:**\n\n- 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))\n- 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))\n- 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))\n- 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))\n- 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))\n- Update version key in config file after version changes [\\#1646](https://github.com/jrnl-org/jrnl/pull/1646) ([jonakeys](https://github.com/jonakeys))\n\n**Build:**\n\n- Update copyright notices for 2023 [\\#1660](https://github.com/jrnl-org/jrnl/pull/1660) ([wren](https://github.com/wren))\n- 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))\n- Add `simplify` plugin to linting checks [\\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren))\n- Add type hints [\\#1614](https://github.com/jrnl-org/jrnl/pull/1614) ([outa](https://github.com/outa))\n\n**Documentation:**\n\n- Update contributing.md links in documentation [\\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking))\n- Fix various typos [\\#1718](https://github.com/jrnl-org/jrnl/pull/1718) ([hezhizhen](https://github.com/hezhizhen))\n- Update documentation front page text [\\#1698](https://github.com/jrnl-org/jrnl/pull/1698) ([micahellison](https://github.com/micahellison))\n- Support mkdocs 1.4.2 and fix its missing breadcrumb [\\#1691](https://github.com/jrnl-org/jrnl/pull/1691) ([micahellison](https://github.com/micahellison))\n- Document temporary file extension behavior when using template [\\#1686](https://github.com/jrnl-org/jrnl/pull/1686) ([micahellison](https://github.com/micahellison))\n- Document `-tagged`, `-not -tagged`, and `-not -starred` [\\#1684](https://github.com/jrnl-org/jrnl/pull/1684) ([micahellison](https://github.com/micahellison))\n- Update documentation about privacy and security in VSCode [\\#1680](https://github.com/jrnl-org/jrnl/pull/1680) ([giuseppedandrea](https://github.com/giuseppedandrea))\n- Update documentation on temporary files naming [\\#1673](https://github.com/jrnl-org/jrnl/pull/1673) ([giuseppedandrea](https://github.com/giuseppedandrea))\n- Update docs to include time and title in arguments with `--edit` [\\#1657](https://github.com/jrnl-org/jrnl/pull/1657) ([pconrad-fb](https://github.com/pconrad-fb))\n- Fix markup in \"Advanced Usage\" doc [\\#1655](https://github.com/jrnl-org/jrnl/pull/1655) ([multani](https://github.com/multani))\n- Remove Windows 7 known issue since Windows 7 is no longer supported [\\#1636](https://github.com/jrnl-org/jrnl/pull/1636) ([micahellison](https://github.com/micahellison))\n\n**Packaging:**\n\n- 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))\n- Update dependency black to v23.3.0 [\\#1715](https://github.com/jrnl-org/jrnl/pull/1715) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency cryptography to v40.0.2 [\\#1723](https://github.com/jrnl-org/jrnl/pull/1723) ([renovate[bot]](https://github.com/apps/renovate))\n- 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))\n- Update dependency flakeheaven to v3.3.0 [\\#1722](https://github.com/jrnl-org/jrnl/pull/1722) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ipdb to v0.13.13 [\\#1703](https://github.com/jrnl-org/jrnl/pull/1703) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency poethepoet to v0.19.0 [\\#1709](https://github.com/jrnl-org/jrnl/pull/1709) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest to v7.3.1 [\\#1720](https://github.com/jrnl-org/jrnl/pull/1720) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency pytest-xdist to v3.2.1 [\\#1705](https://github.com/jrnl-org/jrnl/pull/1705) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency rich to v13.3.4 [\\#1713](https://github.com/jrnl-org/jrnl/pull/1713) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tox to v4.4.7 [\\#1707](https://github.com/jrnl-org/jrnl/pull/1707) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tzlocal to v4.3 [\\#1708](https://github.com/jrnl-org/jrnl/pull/1708) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3-beta2...v3.3)\n\n**Implemented enhancements:**\n\n- Change default config to use journal key [\\#1594](https://github.com/jrnl-org/jrnl/pull/1594) ([micahellison](https://github.com/micahellison))\n- Add machine readable --list output [\\#1592](https://github.com/jrnl-org/jrnl/pull/1592) ([apainintheneck](https://github.com/apainintheneck))\n\n**Fixed bugs:**\n\n- Fix bug for new `--list --format` options when no default journal is specified [\\#1621](https://github.com/jrnl-org/jrnl/pull/1621) ([wren](https://github.com/wren))\n- Don't create empty file when attempting a YAML export to a non-existing folder [\\#1600](https://github.com/jrnl-org/jrnl/pull/1600) ([outa](https://github.com/outa))\n\n**Build:**\n\n- Update `.gitignore` [\\#1604](https://github.com/jrnl-org/jrnl/pull/1604) ([wren](https://github.com/wren))\n- Fix Docs Accessibility Testing [\\#1588](https://github.com/jrnl-org/jrnl/pull/1588) ([wren](https://github.com/wren))\n- Update to use renamed flag for `brew bump-formula-pr` [\\#1587](https://github.com/jrnl-org/jrnl/pull/1587) ([wren](https://github.com/wren))\n- Update peter-evans/create-pull-request action to v4 [\\#1585](https://github.com/jrnl-org/jrnl/pull/1585) ([renovate[bot]](https://github.com/apps/renovate))\n- Update actions/setup-python action to v4 [\\#1583](https://github.com/jrnl-org/jrnl/pull/1583) ([renovate[bot]](https://github.com/apps/renovate))\n- Update actions/checkout action to v3 [\\#1582](https://github.com/jrnl-org/jrnl/pull/1582) ([renovate[bot]](https://github.com/apps/renovate))\n- Update actions/cache action to v3 [\\#1581](https://github.com/jrnl-org/jrnl/pull/1581) ([renovate[bot]](https://github.com/apps/renovate))\n- Replace Dependabot with Renovate [\\#1575](https://github.com/jrnl-org/jrnl/pull/1575) ([renovate[bot]](https://github.com/apps/renovate))\n\n**Documentation:**\n\n- Add documentation about how the editor must be a blocking process [\\#1456](https://github.com/jrnl-org/jrnl/issues/1456)\n- Document that editors must be blocking processes [\\#1624](https://github.com/jrnl-org/jrnl/pull/1624) ([micahellison](https://github.com/micahellison))\n- Remove wrong option in configuration file reference [\\#1618](https://github.com/jrnl-org/jrnl/pull/1618) ([DSiekmeier](https://github.com/DSiekmeier))\n- Update YAML export description in docs [\\#1591](https://github.com/jrnl-org/jrnl/pull/1591) ([apainintheneck](https://github.com/apainintheneck))\n- Update dependency jinja2 to v3.1.2 [\\#1579](https://github.com/jrnl-org/jrnl/pull/1579) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency typed.js to v2.0.12 [\\#1578](https://github.com/jrnl-org/jrnl/pull/1578) ([renovate[bot]](https://github.com/apps/renovate))\n- Add hint for how to get vi to go to end-of-file [\\#1563](https://github.com/jrnl-org/jrnl/pull/1563) ([pjz](https://github.com/pjz))\n\n**Packaging:**\n\n- Pin dependencies [\\#1577](https://github.com/jrnl-org/jrnl/pull/1577) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v3.2](https://pypi.org/project/jrnl/v3.2/) (2022-09-03)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.2-beta...v3.2)\n\n**Implemented enhancements:**\n\n- Update issue & PR templates to use forms [\\#1559](https://github.com/jrnl-org/jrnl/issues/1559)\n- Suppress \"Entry added\" message if using default journal [\\#1561](https://github.com/jrnl-org/jrnl/pull/1561) ([micahellison](https://github.com/micahellison))\n- Add message showing the number of search results [\\#1524](https://github.com/jrnl-org/jrnl/pull/1524) ([apainintheneck](https://github.com/apainintheneck))\n\n**Build:**\n\n- Quick fix follow up for actionlint [\\#1565](https://github.com/jrnl-org/jrnl/pull/1565) ([wren](https://github.com/wren))\n\n**Documentation:**\n\n- Remove note in contributing docs about gh-pages branch that we no longer use [\\#1566](https://github.com/jrnl-org/jrnl/pull/1566) ([micahellison](https://github.com/micahellison))\n\n## [v3.1](https://pypi.org/project/jrnl/v3.1/) (2022-08-21)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.1-beta2...v3.1)\n\n**Implemented enhancements:**\n\n- Update tzlocal to v4.x and remove pytz dependency [\\#1528](https://github.com/jrnl-org/jrnl/pull/1528) ([outa](https://github.com/outa))\n- Add linewrap option 'auto' [\\#1507](https://github.com/jrnl-org/jrnl/pull/1507) ([jonakeys](https://github.com/jonakeys))\n\n**Fixed bugs:**\n\n- Update formatting function to better account for indentation [\\#1541](https://github.com/jrnl-org/jrnl/pull/1541) ([wren](https://github.com/wren))\n- Export to file\\(s\\) when first line/title of an entry is very long [\\#1527](https://github.com/jrnl-org/jrnl/pull/1527) ([jonakeys](https://github.com/jonakeys))\n- Fixed index out of range error in fancy exporter [\\#1522](https://github.com/jrnl-org/jrnl/pull/1522) ([apainintheneck](https://github.com/apainintheneck))\n\n**Build:**\n\n- Add actionlint to testing pipelines [\\#1555](https://github.com/jrnl-org/jrnl/pull/1555) ([wren](https://github.com/wren))\n- Fix docs pipeline, make docs tests easier to run locally and on different OSes [\\#1554](https://github.com/jrnl-org/jrnl/pull/1554) ([wren](https://github.com/wren))\n\n**Documentation:**\n\n- Reformat contributor appreciation on `--help` screen [\\#1556](https://github.com/jrnl-org/jrnl/pull/1556) ([xeruf](https://github.com/xeruf))\n- Clean up copyright notices and version screen [\\#1553](https://github.com/jrnl-org/jrnl/pull/1553) ([wren](https://github.com/wren))\n\n**Packaging:**\n\n- Bump pyproject-flake8 from 0.0.1a4 to 0.0.1a5 [\\#1552](https://github.com/jrnl-org/jrnl/pull/1552) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 23.7.0 to 23.8.2 [\\#1551](https://github.com/jrnl-org/jrnl/pull/1551) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump yq from 3.0.2 to 3.1.0 [\\#1546](https://github.com/jrnl-org/jrnl/pull/1546) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump poethepoet from 0.15.0 to 0.16.0 [\\#1542](https://github.com/jrnl-org/jrnl/pull/1542) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 23.6.0 to 23.7.0 [\\#1539](https://github.com/jrnl-org/jrnl/pull/1539) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump rich from 12.4.4 to 12.5.1 [\\#1538](https://github.com/jrnl-org/jrnl/pull/1538) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v3.0](https://pypi.org/project/jrnl/v3.0/) (2022-07-09)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.0-beta2...v3.0)\n\n🚨 **BREAKING CHANGES** 🚨\n\n**Deprecated:**\n\n- Drop support for Python 3.7 and 3.8 [\\#1412](https://github.com/jrnl-org/jrnl/pull/1412) ([micahellison](https://github.com/micahellison))\n\n**Implemented enhancements:**\n\n- Show name of journal when creating a password/encrypting [\\#1478](https://github.com/jrnl-org/jrnl/pull/1478) ([jonakeys](https://github.com/jonakeys))\n- Rework how all output and messaging works in jrnl [\\#1475](https://github.com/jrnl-org/jrnl/pull/1475) ([wren](https://github.com/wren))\n- Implement --change-time flag [\\#1452](https://github.com/jrnl-org/jrnl/pull/1452) ([richardjs](https://github.com/richardjs))\n- Reformat additional messages and finish centralizing exception handling [\\#1424](https://github.com/jrnl-org/jrnl/pull/1424) ([wren](https://github.com/wren))\n- Reformat messages and add new centralized exception handling [\\#1417](https://github.com/jrnl-org/jrnl/pull/1417) ([wren](https://github.com/wren))\n\n**Fixed bugs:**\n\n- Display message when no edits take place [\\#1510](https://github.com/jrnl-org/jrnl/pull/1510) ([apainintheneck](https://github.com/apainintheneck))\n- Fixed error related to display\\_format in config file for some values [\\#1495](https://github.com/jrnl-org/jrnl/pull/1495) ([apainintheneck](https://github.com/apainintheneck))\n- Create folder if config ends with \\(back\\)slash [\\#1492](https://github.com/jrnl-org/jrnl/pull/1492) ([jonakeys](https://github.com/jonakeys))\n- `-not` search parameter no longer opens editor [\\#1490](https://github.com/jrnl-org/jrnl/pull/1490) ([apainintheneck](https://github.com/apainintheneck))\n- Fix TypeError when using debug flag [\\#1484](https://github.com/jrnl-org/jrnl/pull/1484) ([jonakeys](https://github.com/jonakeys))\n- Prompt for password change when using 'jrnl --encrypt' on already encrypted journal [\\#1477](https://github.com/jrnl-org/jrnl/pull/1477) ([jonakeys](https://github.com/jonakeys))\n- Always expand all paths \\(journals, templates, etc\\) [\\#1468](https://github.com/jrnl-org/jrnl/pull/1468) ([apainintheneck](https://github.com/apainintheneck))\n- The `-not` option with no arguments now outputs error instead of stack trace [\\#1466](https://github.com/jrnl-org/jrnl/pull/1466) ([apainintheneck](https://github.com/apainintheneck))\n- Give a proper message when trying to use an empty config file [\\#1461](https://github.com/jrnl-org/jrnl/pull/1461) ([jonakeys](https://github.com/jonakeys))\n- 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))\n- Yaml export errors now don't show stack trace [\\#1449](https://github.com/jrnl-org/jrnl/pull/1449) ([apainintheneck](https://github.com/apainintheneck))\n\n**Build:**\n\n- 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))\n- Reduce difference between local and CI environments [\\#1518](https://github.com/jrnl-org/jrnl/pull/1518) ([wren](https://github.com/wren))\n- Add bdd tests for jrnl installation [\\#1513](https://github.com/jrnl-org/jrnl/pull/1513) ([apainintheneck](https://github.com/apainintheneck))\n- Stop hardcoding bot info in changelog pipeline [\\#1506](https://github.com/jrnl-org/jrnl/pull/1506) ([wren](https://github.com/wren))\n- Fix Poetry caching for accessibility tests [\\#1505](https://github.com/jrnl-org/jrnl/pull/1505) ([wren](https://github.com/wren))\n- Implement Tox for testing [\\#1504](https://github.com/jrnl-org/jrnl/pull/1504) ([wren](https://github.com/wren))\n- Replace `make` with python alternative \\(`poe`\\) [\\#1503](https://github.com/jrnl-org/jrnl/pull/1503) ([wren](https://github.com/wren))\n- Update copyright year [\\#1502](https://github.com/jrnl-org/jrnl/pull/1502) ([wren](https://github.com/wren))\n- Add Python 3.11 to PR tests [\\#1500](https://github.com/jrnl-org/jrnl/pull/1500) ([micahellison](https://github.com/micahellison))\n- Pin jinja2 in docs requirements to keep readthedocs builds from failing [\\#1439](https://github.com/jrnl-org/jrnl/pull/1439) ([micahellison](https://github.com/micahellison))\n- Tidy up git ignore [\\#1414](https://github.com/jrnl-org/jrnl/pull/1414) ([nelnog](https://github.com/nelnog))\n\n**Documentation:**\n\n- Document --change-time in web-based docs' command line reference [\\#1471](https://github.com/jrnl-org/jrnl/pull/1471) ([micahellison](https://github.com/micahellison))\n\n**Packaging:**\n\n- Bump cryptography from 37.0.2 to 37.0.3 [\\#1516](https://github.com/jrnl-org/jrnl/pull/1516) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump poethepoet from 0.13.1 to 0.14.0 [\\#1514](https://github.com/jrnl-org/jrnl/pull/1514) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 23.5.1 to 23.6.0 [\\#1499](https://github.com/jrnl-org/jrnl/pull/1499) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pyxdg from 0.27 to 0.28 [\\#1497](https://github.com/jrnl-org/jrnl/pull/1497) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 23.5.0 to 23.5.1 [\\#1487](https://github.com/jrnl-org/jrnl/pull/1487) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump cryptography from 37.0.1 to 37.0.2 [\\#1467](https://github.com/jrnl-org/jrnl/pull/1467) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump cryptography from 36.0.2 to 37.0.1 [\\#1462](https://github.com/jrnl-org/jrnl/pull/1462) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytest from 7.1.1 to 7.1.2 [\\#1458](https://github.com/jrnl-org/jrnl/pull/1458) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pyproject-flake8 from 0.0.1a3 to 0.0.1a4 [\\#1447](https://github.com/jrnl-org/jrnl/pull/1447) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump black from 22.1.0 to 22.3.0 [\\#1442](https://github.com/jrnl-org/jrnl/pull/1442) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump mkdocs from 1.2.3 to 1.3.0 [\\#1441](https://github.com/jrnl-org/jrnl/pull/1441) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pyproject-flake8 from 0.0.1a2 to 0.0.1a3 [\\#1440](https://github.com/jrnl-org/jrnl/pull/1440) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytz from 2021.3 to 2022.1 [\\#1438](https://github.com/jrnl-org/jrnl/pull/1438) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytest from 7.0.1 to 7.1.1 [\\#1430](https://github.com/jrnl-org/jrnl/pull/1430) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump cryptography from 36.0.1 to 36.0.2 [\\#1427](https://github.com/jrnl-org/jrnl/pull/1427) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump yq from 2.13.0 to 2.14.0 [\\#1418](https://github.com/jrnl-org/jrnl/pull/1418) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Replace PyYAML with ruamel.yaml [\\#1416](https://github.com/jrnl-org/jrnl/pull/1416) ([micahellison](https://github.com/micahellison))\n- Bump pytest from 6.2.5 to 7.0.0 [\\#1407](https://github.com/jrnl-org/jrnl/pull/1407) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v2.8.4](https://pypi.org/project/jrnl/v2.8.4/) (2022-02-12)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.8.4-beta2...v2.8.4)\n\n**Implemented enhancements:**\n\n- Add hash as a default tag symbol for new jrnl config file [\\#1398](https://github.com/jrnl-org/jrnl/pull/1398) ([micahellison](https://github.com/micahellison))\n- Add --config-file argument to use alternate config file at runtime [\\#1290](https://github.com/jrnl-org/jrnl/pull/1290) ([samuelgregorovic](https://github.com/samuelgregorovic))\n\n**Fixed bugs:**\n\n- Certs broken on website [\\#1408](https://github.com/jrnl-org/jrnl/issues/1408)\n- Add added option to \\_print\\_edited\\_summary [\\#1366](https://github.com/jrnl-org/jrnl/pull/1366) ([piero-vic](https://github.com/piero-vic))\n\n**Build:**\n\n- Improve handling of mocking logic in pytest [\\#1382](https://github.com/jrnl-org/jrnl/pull/1382) ([wren](https://github.com/wren))\n- Use full Python version for GitHub Actions cache key [\\#1373](https://github.com/jrnl-org/jrnl/pull/1373) ([micahellison](https://github.com/micahellison))\n- Use Python 3.10 stable in CI [\\#1362](https://github.com/jrnl-org/jrnl/pull/1362) ([micahellison](https://github.com/micahellison))\n- Switch from poetry to poetry-core [\\#1359](https://github.com/jrnl-org/jrnl/pull/1359) ([fabaff](https://github.com/fabaff))\n- Add more steps to `pytest`, fully remove `behave` [\\#1347](https://github.com/jrnl-org/jrnl/pull/1347) ([wren](https://github.com/wren))\n\n**Documentation:**\n\n- Fix styling on documentation sidebar [\\#1395](https://github.com/jrnl-org/jrnl/pull/1395) ([wren](https://github.com/wren))\n- Added Recipe for visualizing Markdown in the CLI [\\#1354](https://github.com/jrnl-org/jrnl/pull/1354) ([viegasfh](https://github.com/viegasfh))\n- Fix recipe 'Launch a terminal for rapid logging' [\\#1351](https://github.com/jrnl-org/jrnl/pull/1351) ([zapateo](https://github.com/zapateo))\n- Fix readme splash and add changelog link to readme [\\#1339](https://github.com/jrnl-org/jrnl/pull/1339) ([micahellison](https://github.com/micahellison))\n- Add reference documentation to docs site and separate out \"Tips and Tricks\" and \"External Editors\" from \"Recipes\" [\\#1332](https://github.com/jrnl-org/jrnl/pull/1332) ([micahellison](https://github.com/micahellison))\n- Document journal types [\\#1331](https://github.com/jrnl-org/jrnl/pull/1331) ([micahellison](https://github.com/micahellison))\n\n**Packaging:**\n\n- Bump asteval from 0.9.25 to 0.9.26 [\\#1400](https://github.com/jrnl-org/jrnl/pull/1400) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump black from 21.7b0 to 22.1.0 [\\#1404](https://github.com/jrnl-org/jrnl/pull/1404) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump cryptography from 3.4.8 to 36.0.1 [\\#1389](https://github.com/jrnl-org/jrnl/pull/1389) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump ipython from 7.28.0 to 7.31.1 [\\#1401](https://github.com/jrnl-org/jrnl/pull/1401) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 23.1.0 to 23.5.0 [\\#1392](https://github.com/jrnl-org/jrnl/pull/1392) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump mkdocs from 1.2.2 to 1.2.3 [\\#1355](https://github.com/jrnl-org/jrnl/pull/1355) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytest from 6.2.4 to 6.2.5 [\\#1334](https://github.com/jrnl-org/jrnl/pull/1334) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytest-bdd from 4.1.0 to 5.0.0 [\\#1368](https://github.com/jrnl-org/jrnl/pull/1368) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytz from 2021.1 to 2021.3 [\\#1348](https://github.com/jrnl-org/jrnl/pull/1348) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump yq from 2.12.2 to 2.13.0 [\\#1385](https://github.com/jrnl-org/jrnl/pull/1385) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v2.8.3](https://pypi.org/project/jrnl/v2.8.3/) (2021-09-06)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.8.3-beta...v2.8.3)\n\n**Fixed bugs:**\n\n- Fix deletion of entries on folder journal through `--delete` flag [\\#1328](https://github.com/jrnl-org/jrnl/pull/1328) ([micahellison](https://github.com/micahellison))\n- Warn when DayOne/directory journals have encrypt: true in config [\\#1325](https://github.com/jrnl-org/jrnl/pull/1325) ([micahellison](https://github.com/micahellison))\n- Fix failure to import into directory journal [\\#1314](https://github.com/jrnl-org/jrnl/pull/1314) ([micahellison](https://github.com/micahellison))\n- Allow emoji in config file in Windows by always opening it as unicode [\\#1313](https://github.com/jrnl-org/jrnl/pull/1313) ([micahellison](https://github.com/micahellison))\n\n**Build:**\n\n- Set bash as default shell [\\#1324](https://github.com/jrnl-org/jrnl/pull/1324) ([micahellison](https://github.com/micahellison))\n\n**Packaging:**\n\n- Bump cryptography from 3.4.7 to 3.4.8 [\\#1329](https://github.com/jrnl-org/jrnl/pull/1329) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 23.0.1 to 23.1.0 [\\#1318](https://github.com/jrnl-org/jrnl/pull/1318) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v2.8.2](https://pypi.org/project/jrnl/v2.8.2/) (2021-07-31)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.8.2-beta...v2.8.2)\n\n**Build:**\n\n- Add CI tests for latest dev Python build [\\#1273](https://github.com/jrnl-org/jrnl/issues/1273)\n- Fix lock file from stable Poetry version [\\#1298](https://github.com/jrnl-org/jrnl/pull/1298) ([wren](https://github.com/wren))\n- Change all YAML FullLoader calls to SafeLoader [\\#1285](https://github.com/jrnl-org/jrnl/pull/1285) ([micahellison](https://github.com/micahellison))\n- Remove useless shebangs and executable permissions [\\#1283](https://github.com/jrnl-org/jrnl/pull/1283) ([musicinmybrain](https://github.com/musicinmybrain))\n- Add Python 3.10 support [\\#1271](https://github.com/jrnl-org/jrnl/pull/1271) ([micahellison](https://github.com/micahellison))\n- Ensure that line endings in all py files are Linux style instead of Windows [\\#1250](https://github.com/jrnl-org/jrnl/pull/1250) ([micahellison](https://github.com/micahellison))\n- Remove `--version` from brew release workflow [\\#1233](https://github.com/jrnl-org/jrnl/pull/1233) ([wren](https://github.com/wren))\n- Move test suite to Pytest \\(replace Behave\\) [\\#1193](https://github.com/jrnl-org/jrnl/pull/1193) ([wren](https://github.com/wren))\n\n**Documentation:**\n\n- Add documentation about saved passwords in Windows [\\#1301](https://github.com/jrnl-org/jrnl/pull/1301) ([micahellison](https://github.com/micahellison))\n- Add security.md [\\#1299](https://github.com/jrnl-org/jrnl/pull/1299) ([micahellison](https://github.com/micahellison))\n\n**Packaging:**\n\n- Bump mkdocs from 1.2.1 to 1.2.2 [\\#1307](https://github.com/jrnl-org/jrnl/pull/1307) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump python-dateutil from 2.8.1 to 2.8.2 [\\#1302](https://github.com/jrnl-org/jrnl/pull/1302) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump black from 21.5b1 to 21.5b2 [\\#1254](https://github.com/jrnl-org/jrnl/pull/1254) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump black from 21.5b0 to 21.5b1 [\\#1244](https://github.com/jrnl-org/jrnl/pull/1244) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump black from 20.8b1 to 21.5b0 [\\#1241](https://github.com/jrnl-org/jrnl/pull/1241) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytest from 6.2.3 to 6.2.4 [\\#1240](https://github.com/jrnl-org/jrnl/pull/1240) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v2.8.1](https://pypi.org/project/jrnl/v2.8.1/) (2021-04-24)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.8.1-beta...v2.8.1)\n\n**Fixed bugs:**\n\n- More graceful handling of low linewrap values  [\\#1219](https://github.com/jrnl-org/jrnl/pull/1219) ([sriniv27](https://github.com/sriniv27))\n\n**Documentation:**\n\n- Update absolute URLs to preview images in metatags [\\#1229](https://github.com/jrnl-org/jrnl/pull/1229) ([maebert](https://github.com/maebert))\n- Docs: Add emacs as external editor to recipes [\\#1220](https://github.com/jrnl-org/jrnl/pull/1220) ([mandarvaze](https://github.com/mandarvaze))\n\n**Packaging:**\n\n- Bump pytest from 6.2.2 to 6.2.3 [\\#1228](https://github.com/jrnl-org/jrnl/pull/1228) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump cryptography from 3.4.6 to 3.4.7 [\\#1223](https://github.com/jrnl-org/jrnl/pull/1223) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 23.0.0 to 23.0.1 [\\#1222](https://github.com/jrnl-org/jrnl/pull/1222) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pyflakes from 2.3.0 to 2.3.1 [\\#1221](https://github.com/jrnl-org/jrnl/pull/1221) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v2.8](https://pypi.org/project/jrnl/v2.8/) (2021-03-27)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.8-beta...v2.8)\n\n**Implemented enhancements:**\n\n- Add `--config-override` feature [\\#1169](https://github.com/jrnl-org/jrnl/pull/1169) ([sriniv27](https://github.com/sriniv27))\n\n**Fixed bugs:**\n\n- Fix bug that prevented --format pretty and --format short from working [\\#1177](https://github.com/jrnl-org/jrnl/pull/1177) ([sriniv27](https://github.com/sriniv27))\n\n**Build:**\n\n- Fix broken brew release process [\\#1211](https://github.com/jrnl-org/jrnl/pull/1211) ([micahellison](https://github.com/micahellison))\n\n**Packaging:**\n\n- Bump pyflakes from 2.2.0 to 2.3.0 [\\#1215](https://github.com/jrnl-org/jrnl/pull/1215) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 22.3.0 to 23.0.0 [\\#1213](https://github.com/jrnl-org/jrnl/pull/1213) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 22.0.1 to 22.3.0 [\\#1210](https://github.com/jrnl-org/jrnl/pull/1210) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump asteval from 0.9.22 to 0.9.23 [\\#1209](https://github.com/jrnl-org/jrnl/pull/1209) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v2.7.1](https://pypi.org/project/jrnl/v2.7.1/) (2021-02-27)\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.7...v2.7.1)\n\n**Fixed bugs:**\n\n- Make journal selection behavior more consistent when there's a colon with no date [\\#1164](https://github.com/jrnl-org/jrnl/pull/1164) ([wren](https://github.com/wren))\n\n**Documentation:**\n\n- Update documentation about journal-level config values [\\#1196](https://github.com/jrnl-org/jrnl/issues/1196)\n- update per-journal config documentation [\\#1199](https://github.com/jrnl-org/jrnl/pull/1199) ([sriniv27](https://github.com/sriniv27))\n\n**Packaging:**\n\n- Bump cryptography from 3.4.4 to 3.4.6 [\\#1195](https://github.com/jrnl-org/jrnl/pull/1195) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump asteval from 0.9.21 to 0.9.22 [\\#1189](https://github.com/jrnl-org/jrnl/pull/1189) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump cryptography from 3.3.1 to 3.4.4 [\\#1188](https://github.com/jrnl-org/jrnl/pull/1188) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump yq from 2.11.1 to 2.12.0 [\\#1186](https://github.com/jrnl-org/jrnl/pull/1186) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytz from 2020.5 to 2021.1 [\\#1174](https://github.com/jrnl-org/jrnl/pull/1174) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 21.8.0 to 22.0.1 [\\#1168](https://github.com/jrnl-org/jrnl/pull/1168) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytest from 6.2.1 to 6.2.2 [\\#1167](https://github.com/jrnl-org/jrnl/pull/1167) ([dependabot[bot]](https://github.com/apps/dependabot))\n\n## [v2.7](https://pypi.org/project/jrnl/v2.7/) (2021-01-23)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.7-beta...v2.7)\n\n**Implemented enhancements:**\n\n- Add new date format \\(`--format date`\\) for heatmapping [\\#1146](https://github.com/jrnl-org/jrnl/pull/1146) ([KarimPwnz](https://github.com/KarimPwnz))\n- Add new `-today-in-history`, `-month`, `-day`, and `-year` search filters [\\#1145](https://github.com/jrnl-org/jrnl/pull/1145) ([KarimPwnz](https://github.com/KarimPwnz))\n- Allow custom extensions when editing \\(for easier syntax highlighting\\) [\\#1139](https://github.com/jrnl-org/jrnl/pull/1139) ([KarimPwnz](https://github.com/KarimPwnz))\n\n**Fixed bugs:**\n\n- Editor can't be launched on Windows when using full path to editor executable [\\#1096](https://github.com/jrnl-org/jrnl/issues/1096)\n- Fix OS compatibility issues for editors with spaces, slashes, and quotes [\\#1153](https://github.com/jrnl-org/jrnl/pull/1153) ([micahellison](https://github.com/micahellison))\n- Add delimiters in YAML format [\\#1150](https://github.com/jrnl-org/jrnl/pull/1150) ([Seopril](https://github.com/Seopril))\n- Fix keyring error handling [\\#1138](https://github.com/jrnl-org/jrnl/pull/1138) ([KarimPwnz](https://github.com/KarimPwnz))\n- Notify user when config directory can't be created because there is already a file with the same name [\\#1134](https://github.com/jrnl-org/jrnl/pull/1134) ([micahellison](https://github.com/micahellison))\n\n**Build:**\n\n- Fix homebrew release, add options for release pipeline [\\#1154](https://github.com/jrnl-org/jrnl/pull/1154) ([wren](https://github.com/wren))\n- Fix changelog generator [\\#1127](https://github.com/jrnl-org/jrnl/pull/1127) ([wren](https://github.com/wren))\n\n**Documentation:**\n\n- add instructions to add VSCode as an external editor for Windows [\\#1155](https://github.com/jrnl-org/jrnl/issues/1155)\n- Clarify editor documentation for PATH variable and VS Code [\\#1160](https://github.com/jrnl-org/jrnl/pull/1160) ([micahellison](https://github.com/micahellison))\n- Emphasize installing dependencies before testing [\\#1148](https://github.com/jrnl-org/jrnl/pull/1148) ([gumatias](https://github.com/gumatias))\n- Clarify installation documentation \\(\\#1097\\) [\\#1137](https://github.com/jrnl-org/jrnl/pull/1137) ([Seopril](https://github.com/Seopril))\n- Fix broken search bar in docs site [\\#1135](https://github.com/jrnl-org/jrnl/pull/1135) ([wren](https://github.com/wren))\n- Fix search on docs site [\\#1133](https://github.com/jrnl-org/jrnl/pull/1133) ([wren](https://github.com/wren))\n- Add packaging label to changelog generator config [\\#1132](https://github.com/jrnl-org/jrnl/pull/1132) ([wren](https://github.com/wren))\n- Fix failing contrast test in accessibility tools on docs site [\\#1126](https://github.com/jrnl-org/jrnl/pull/1126) ([wren](https://github.com/wren))\n\n**Packaging:**\n\n- Bump pyyaml from 5.3.1 to 5.4.1 [\\#1158](https://github.com/jrnl-org/jrnl/pull/1158) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump keyring from 21.7.0 to 21.8.0 [\\#1136](https://github.com/jrnl-org/jrnl/pull/1136) ([dependabot[bot]](https://github.com/apps/dependabot))\n- Bump pytz from 2020.4 to 2020.5 [\\#1130](https://github.com/jrnl-org/jrnl/pull/1130) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))\n- Bump pytest from 6.2.0 to 6.2.1 [\\#1129](https://github.com/jrnl-org/jrnl/pull/1129) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))\n- Bump keyring from 21.5.0 to 21.7.0 [\\#1128](https://github.com/jrnl-org/jrnl/pull/1128) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))\n\n## [v2.6](https://pypi.org/project/jrnl/v2.6/) (2020-12-20)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.5...v2.6)\n\n**Implemented enhancements:**\n\n- Add ability to put --edit partly through a cli entry to move it to the editor [\\#1091](https://github.com/jrnl-org/jrnl/pull/1091) ([wren](https://github.com/wren))\n- Allow --edit flag partway through an entry [\\#906](https://github.com/jrnl-org/jrnl/issues/906)\n\n**Fixed bugs:**\n\n- Check for readline module instead of Windows when initializing autocomplete in install [\\#1104](https://github.com/jrnl-org/jrnl/pull/1104) ([micahellison](https://github.com/micahellison))\n- Directory export crashes in Windows with certain characters - UnicodeEncodeError: 'locale' codec can't encode character [\\#1089](https://github.com/jrnl-org/jrnl/issues/1089)\n- Fix Unicode encoding failure in directory export when creating filenames from journal titles with certain characters [\\#1090](https://github.com/jrnl-org/jrnl/pull/1090) ([micahellison](https://github.com/micahellison))\n- Typo fix in output.py: \"us\" -\\> \"use\" [\\#1117](https://github.com/jrnl-org/jrnl/pull/1117) ([signal-9](https://github.com/signal-9))\n\n**Build:**\n\n- Add a release workflow for PyPI in CI \\(Github Actions\\) [\\#1095](https://github.com/jrnl-org/jrnl/pull/1095) ([wren](https://github.com/wren))\n- Add automatic deployment for homebrew releases \\(and prereleases\\) [\\#1111](https://github.com/jrnl-org/jrnl/pull/1111) ([wren](https://github.com/wren))\n- Add changelog generation workflow to github actions [\\#1086](https://github.com/jrnl-org/jrnl/pull/1086) ([wren](https://github.com/wren))\n- Add fix for changelog conditional always returning false [\\#1101](https://github.com/jrnl-org/jrnl/pull/1101) ([wren](https://github.com/wren))\n- Change approach for docs workflow to use pa11y-ci [\\#1116](https://github.com/jrnl-org/jrnl/pull/1116) ([wren](https://github.com/wren))\n- Changelog fixes [\\#1088](https://github.com/jrnl-org/jrnl/pull/1088) ([wren](https://github.com/wren))\n- Fix trigger for changelog [\\#1114](https://github.com/jrnl-org/jrnl/pull/1114) ([wren](https://github.com/wren))\n- Make changelog auto exclude stale and wontfix issues [\\#1081](https://github.com/jrnl-org/jrnl/pull/1081) ([wren](https://github.com/wren))\n- Migrate to Github Actions from Travis CI [\\#1060](https://github.com/jrnl-org/jrnl/issues/1060)\n- More changelog fixes [\\#1092](https://github.com/jrnl-org/jrnl/pull/1092) ([wren](https://github.com/wren))\n- Standardize version regex in release pipeline [\\#1124](https://github.com/jrnl-org/jrnl/pull/1124) ([wren](https://github.com/wren))\n- Udpate build badge in readme to point at github instead of travis [\\#1094](https://github.com/jrnl-org/jrnl/pull/1094) ([wren](https://github.com/wren))\n- Update all dependencies and lock file [\\#1110](https://github.com/jrnl-org/jrnl/pull/1110) ([wren](https://github.com/wren))\n- get rid of travis and circle configs \\(in favor of github actions\\) [\\#1082](https://github.com/jrnl-org/jrnl/pull/1082) ([wren](https://github.com/wren))\n\n**Documentation:**\n\n- Add visual header to readme [\\#1085](https://github.com/jrnl-org/jrnl/pull/1085) ([wren](https://github.com/wren))\n- Comply with GPL by acknowledging all authors and including license info in each source file [\\#1121](https://github.com/jrnl-org/jrnl/pull/1121) ([micahellison](https://github.com/micahellison))\n- Fix lone closing parenthesis [\\#1118](https://github.com/jrnl-org/jrnl/pull/1118) ([maebert](https://github.com/maebert))\n- Make docs site \\(jrnl.sh\\) fully meet Web Content Accessibility Guidelines \\(WCAG\\) 2.1 [\\#1105](https://github.com/jrnl-org/jrnl/pull/1105) ([wren](https://github.com/wren))\n- Small accessibility fixes for docs site [\\#1122](https://github.com/jrnl-org/jrnl/pull/1122) ([wren](https://github.com/wren))\n\n## [v2.5](https://pypi.org/project/jrnl/v2.5/) (2020-11-07)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4.5...v2.5)\n\n**Implemented enhancements:**\n\n- 🚨 Deprecate Python 3.6 🚨 [\\#992](https://github.com/jrnl-org/jrnl/issues/992)\n- Add support for Python 3.9 [\\#1017](https://github.com/jrnl-org/jrnl/issues/1017)\n- Implement arg parsing library [\\#866](https://github.com/jrnl-org/jrnl/issues/866)\n- Rename `--export` to `--format` and `--export -o` to `--format --file`  [\\#814](https://github.com/jrnl-org/jrnl/issues/814)\n- Pull functionality out of util.py [\\#737](https://github.com/jrnl-org/jrnl/issues/737)\n- Support -not for individual @tag in the command line [\\#374](https://github.com/jrnl-org/jrnl/issues/374)\n- Add punctuation more commonly used in Asian languages \\(ellipsis\\) to sentence parsing [\\#1044](https://github.com/jrnl-org/jrnl/pull/1044) ([felixonmars](https://github.com/felixonmars))\n- Clean up help screen, get rid of util.py [\\#1027](https://github.com/jrnl-org/jrnl/pull/1027) ([wren](https://github.com/wren))\n\n**Fixed bugs:**\n\n- Extra error when writing empty entry [\\#1048](https://github.com/jrnl-org/jrnl/issues/1048)\n- 'Edit on Github' Button in Documentation not working [\\#1039](https://github.com/jrnl-org/jrnl/issues/1039)\n- Decrypt jrnl file in dropbox on another machine fails  [\\#1019](https://github.com/jrnl-org/jrnl/issues/1019)\n- Listing jrnl entries by tag for non default journal seem to not work as expected. [\\#875](https://github.com/jrnl-org/jrnl/issues/875)\n- -and parameter seems to only work for the default journal [\\#520](https://github.com/jrnl-org/jrnl/issues/520)\n- Disable logging by default [\\#1053](https://github.com/jrnl-org/jrnl/pull/1053) ([wren](https://github.com/wren))\n- Partial refactor of cli.py \\(mainly help screen and arg parsing\\) [\\#991](https://github.com/jrnl-org/jrnl/pull/991) ([wren](https://github.com/wren))\n\n**Build:**\n\n- Add accessibility testing for docs site \\(https://jrnl.sh) [\\#1067](https://github.com/jrnl-org/jrnl/pull/1067) ([wren](https://github.com/wren))\n- Add circle ci config file for linux tests [\\#1063](https://github.com/jrnl-org/jrnl/pull/1063) ([wren](https://github.com/wren))\n- Lots of test refactoring [\\#1042](https://github.com/jrnl-org/jrnl/pull/1042) ([wren](https://github.com/wren))\n- Add support for Python 3.9 build testing [\\#1018](https://github.com/jrnl-org/jrnl/pull/1018) ([micahellison](https://github.com/micahellison))\n- Resolve Travis/Windows/pip issues with upgrade to cryptography 3.0 [\\#1016](https://github.com/jrnl-org/jrnl/pull/1016) ([micahellison](https://github.com/micahellison))\n\n**Updated documentation:**\n\n- Clarify usage output between export and reading sections [\\#344](https://github.com/jrnl-org/jrnl/issues/344)\n- Fix \"Edit on GitHub\" button on docs site [\\#1043](https://github.com/jrnl-org/jrnl/pull/1043) ([matildepark](https://github.com/matildepark))\n- Correct typos in CONTRIBUTING.md [\\#1040](https://github.com/jrnl-org/jrnl/pull/1040) ([felixonmars](https://github.com/felixonmars))\n- Change styling of terminal on docs site, small copy changes [\\#1038](https://github.com/jrnl-org/jrnl/pull/1038) ([wren](https://github.com/wren))\n- Documentation updates [\\#1032](https://github.com/jrnl-org/jrnl/pull/1032) ([micahellison](https://github.com/micahellison))\n- Updated advanced.md in docs to reflect all four subkeys under colors … [\\#1023](https://github.com/jrnl-org/jrnl/pull/1023) ([DacodaNelson](https://github.com/DacodaNelson))\n- Update github issue templates to use new diagnostic command [\\#1022](https://github.com/jrnl-org/jrnl/pull/1022) ([wren](https://github.com/wren))\n\n## [v2.4.5](https://pypi.org/project/jrnl/v2.4.5/) (2020-07-31)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4.4...v2.4.5)\n\n**Fixed bugs:**\n\n- Add missing dependency \\(packaging\\) [\\#1011](https://github.com/jrnl-org/jrnl/pull/1011) ([wren](https://github.com/wren))\n\n## [v2.4.4](https://pypi.org/project/jrnl/v2.4.4/) (2020-07-25)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4.4...v2.4.3)\n\n**Implemented enhancements:**\n\n- Add --diagnostic argument [\\#984](https://github.com/jrnl-org/jrnl/pull/984) ([micahellison](https://github.com/micahellison))\n- Add tags to json and xml exporters [\\#975](https://github.com/jrnl-org/jrnl/pull/975) ([eshrh](https://github.com/eshrh))\n- Add extended metadata support for DayOne Classic [\\#928](https://github.com/jrnl-org/jrnl/pull/928) ([MinchinWeb](https://github.com/MinchinWeb))\n\n**Fixed bugs:**\n\n- Allow editing of DayOne entries [\\#1001](https://github.com/jrnl-org/jrnl/pull/1001) ([minchinweb](https://github.com/minchinweb), [micahellison](https://github.com/micahellison), [wren](https://github.com/wren))\n- Create journal with absolute path when no path is specified [\\#972](https://github.com/jrnl-org/jrnl/pull/972) ([eshrh](https://github.com/eshrh))\n\n**Build:**\n\n- Add unit testing via pytest [\\#987](https://github.com/jrnl-org/jrnl/pull/987) ([micahellison](https://github.com/micahellison))\n- Rename master branch to release [\\#985](https://github.com/jrnl-org/jrnl/pull/985) ([wren](https://github.com/wren))\n\n**Updated documentation:**\n\n- Fix readme link to submit an issue [\\#1002](https://github.com/jrnl-org/jrnl/pull/1002) ([wren](https://github.com/wren))\n- Extensive modifications to overview.md [\\#957](https://github.com/jrnl-org/jrnl/pull/957) ([guydebros](https://github.com/guydebros))\n\n## [v2.4.3](https://pypi.org/project/jrnl/v2.4.3/) (2020-06-13)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4.2...v2.4.3)\n\n**Implemented enhancements:**\n\n- Speed up jrnl by 10%, improve slow imports [\\#959](https://github.com/jrnl-org/jrnl/pull/959) ([wotgl](https://github.com/wotgl))\n\n**Fixed bugs:**\n\n- Fix set\\_keychain errors [\\#964](https://github.com/jrnl-org/jrnl/pull/964) ([eshrh](https://github.com/eshrh))\n- Fix title splitting logic to account for both newlines and periods [\\#958](https://github.com/jrnl-org/jrnl/pull/958) ([eshrh](https://github.com/eshrh))\n- Fix editor config when an argument with a space is used [\\#953](https://github.com/jrnl-org/jrnl/pull/953) ([wren](https://github.com/wren))\n- Ask for password before adding entry instead of after [\\#951](https://github.com/jrnl-org/jrnl/pull/951) ([ollybritton](https://github.com/ollybritton))\n- Fix duplicate text in multiple tag search [\\#948](https://github.com/jrnl-org/jrnl/pull/948) ([micahellison](https://github.com/micahellison))\n\n**Build:**\n\n- Fix for hanging Windows tests on Travis [\\#969](https://github.com/jrnl-org/jrnl/pull/969) ([wren](https://github.com/wren))\n- Ensure test data is always checked out with LF line endings [\\#965](https://github.com/jrnl-org/jrnl/pull/965) ([micahellison](https://github.com/micahellison))\n- Clean up templates and issues [\\#954](https://github.com/jrnl-org/jrnl/pull/954) ([wren](https://github.com/wren))\n- Update lockbot comment to encourage linking to issue [\\#941](https://github.com/jrnl-org/jrnl/pull/941) ([MinchinWeb](https://github.com/MinchinWeb))\n\n**Updated documentation:**\n\n- Cleaned up usage.md for clarity, formatting, and grammar. [\\#956](https://github.com/jrnl-org/jrnl/pull/956) ([guydebros](https://github.com/guydebros))\n\n## [v2.4.2](https://pypi.org/project/jrnl/v2.4.2/) (2020-05-09)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4.1...v2.4.2)\n\n**Fixed bugs:**\n\n- Prevent filtered delete from deleting journal [\\#935](https://github.com/jrnl-org/jrnl/pull/935) ([micahellison](https://github.com/micahellison))\n\n**Build:**\n\n- Make sure testing cleans up after itself [\\#940](https://github.com/jrnl-org/jrnl/pull/940) ([wren](https://github.com/wren))\n- Allow most recent pytz version and update dependencies [\\#937](https://github.com/jrnl-org/jrnl/pull/937) ([micahellison](https://github.com/micahellison))\n- Use gitlab to trigger releases in pipeline [\\#947](https://github.com/jrnl-org/jrnl/pull/947) ([wren](https://github.com/wren))\n\n**Updated documentation:**\n\n- Change jrnl.sh GitHub new issue link to issue template chooser [\\#936](https://github.com/jrnl-org/jrnl/pull/936) ([micahellison](https://github.com/micahellison))\n- Improve privacy, security, and encryption documentation \\#896 [\\#925](https://github.com/jrnl-org/jrnl/pull/925) ([micahellison](https://github.com/micahellison))\n\n## [v2.4.1](https://pypi.org/project/jrnl/v2.4.1/) (2020-05-02)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4...v2.4.1)\n\n**Fixed bugs:**\n\n- Disable --delete due to critical bug [\\#934](https://github.com/jrnl-org/jrnl/pull/934) ([wren](https://github.com/wren))\n\n## [v2.4](https://pypi.org/project/jrnl/v2.4/) (2020-04-25)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.3.1...v2.4)\n\n**Implemented enhancements:**\n\n- Update keyring version from \"^19.0\" to \"\\>19.0, \\<22.0\" [\\#914](https://github.com/jrnl-org/jrnl/pull/914) ([micahellison](https://github.com/micahellison))\n- Allow tzlocal version \\>1.5 \\<3.0 instead of \\>1.5 \\<2.0 [\\#900](https://github.com/jrnl-org/jrnl/pull/900) ([micahellison](https://github.com/micahellison))\n- Interactive delete [\\#650](https://github.com/jrnl-org/jrnl/pull/850) ([alichtman](https://github.com/alichtman))\n- Upgrade license to GPLv3 [\\#918](https://github.com/jrnl-org/jrnl/pull/918) ([wren](https://github.com/wren), [micahellison](https://github.com/micahellison))\n\n**Fixed bugs:**\n\n- Fix Python 3.9 incompatibility by updating plistlib [\\#909](https://github.com/jrnl-org/jrnl/pull/909) ([MinchinWeb](https://github.com/MinchinWeb))\n- Ensure exported entries end in a newline for Markdown and YAML exporters [\\#908](https://github.com/jrnl-org/jrnl/pull/908) ([MinchinWeb](https://github.com/MinchinWeb))\n- Fix typo in YAML exporter \\(\"stared\" -\\> \"starred\"\\) [\\#907](https://github.com/jrnl-org/jrnl/pull/907) ([MinchinWeb](https://github.com/MinchinWeb))\n- Fix for upgrade with missing journal [\\#796](https://github.com/jrnl-org/jrnl/pull/796) ([dbxnr](https://github.com/dbxnr))\n\n**Build:**\n\n- Update Python versions in pipeline [\\#910](https://github.com/jrnl-org/jrnl/pull/910) ([MinchinWeb](https://github.com/MinchinWeb))\n- Update Poetry requirements for testing latest Python version [\\#898](https://github.com/jrnl-org/jrnl/pull/898) ([wren](https://github.com/wren))\n- Update makefile to match pipeline better [\\#919](https://github.com/jrnl-org/jrnl/pull/919) ([wren](https://github.com/wren))\n\n**Updated documentation:**\n\n- Update the code of conduct [\\#913](https://github.com/jrnl-org/jrnl/pull/913) ([wren](https://github.com/wren))\n- Update twitter buttons, contribution in footer [\\#905](https://github.com/jrnl-org/jrnl/pull/905) ([wren](https://github.com/wren))\n- Change install doc guideline from pip to pipx [\\#904](https://github.com/jrnl-org/jrnl/pull/904) ([micahellison](https://github.com/micahellison))\n- Update twitter buttons, contribution in footer [\\#905](https://github.com/jrnl-org/jrnl/pull/905) ([wren](https://github.com/wren))\n- Clean up readme file [\\#924](https://github.com/jrnl-org/jrnl/pull/924) ([wren](https://github.com/wren))\n- Clarify that editing config isn't always destructive [\\#923](https://github.com/jrnl-org/jrnl/pull/923) ([Epskampie](https://github.com/Epskampie))\n\n## [v2.3](https://pypi.org/project/jrnl/v2.3/) (2020-03-21)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.2...v2.3)\n\n**Implemented enhancements:**\n\n- Update YAML exporter to handle Dayone format [\\#773](https://github.com/jrnl-org/jrnl/pull/773) ([MinchinWeb](https://github.com/MinchinWeb))\n- Pretty print journal entries (add color) [\\#692](https://github.com/jrnl-org/jrnl/pull/692) ([alichtman](https://github.com/alichtman))\n- Allow journals to be saved into multiple files in a directory structure [\\#485](https://github.com/jrnl-org/jrnl/pull/485) ([notbalanced](https://github.com/notbalanced))\n\n**Fixed bugs:**\n\n- Listing all entries in DayOne Classic journal throws IndexError [\\#786](https://github.com/jrnl-org/jrnl/pull/786) ([MinchinWeb](https://github.com/MinchinWeb))\n- Add UTC support for failing DayOne tests [\\#785](https://github.com/jrnl-org/jrnl/pull/785) ([MinchinWeb](https://github.com/MinchinWeb))\n\n**Build:**\n\n- Stop multiple changelog generators from crashing into each other [\\#845](https://github.com/jrnl-org/jrnl/pull/845) ([wren](https://github.com/wren))\n- Don't re-run tests on deployment [\\#839](https://github.com/jrnl-org/jrnl/pull/839) ([wren](https://github.com/wren))\n- Put back build lines in Poetry config [\\#838](https://github.com/jrnl-org/jrnl/pull/838) ([wren](https://github.com/wren))\n- Restore emoji test [\\#837](https://github.com/jrnl-org/jrnl/pull/837) ([micahellison](https://github.com/micahellison))\n- Fix crashing unicode Travis tests on Windows and fail build if Windows tests fail [\\#836](https://github.com/jrnl-org/jrnl/pull/836) ([micahellison](https://github.com/micahellison))\n- Remove poetry from build system in pyproject config to fix `brew install` [\\#830](https://github.com/jrnl-org/jrnl/pull/830) ([wren](https://github.com/wren))\n- Fix all skipped tests on Travis Windows builds by preserving newlines [\\#823](https://github.com/jrnl-org/jrnl/pull/823) ([micahellison](https://github.com/micahellison))\n\n**Updated documentation:**\n\n- Update url for \"beautiful timeline\" in export.md [\\#879](https://github.com/jrnl-org/jrnl/pull/879) ([NGenetzky](https://github.com/NGenetzky))\n- Docs: Fix broken links in recipes.md [\\#854](https://github.com/jrnl-org/jrnl/pull/854) ([lrvl](https://github.com/lrvl))\n- Fix configuration slashes and indentation in advanced usage documentation [\\#852](https://github.com/jrnl-org/jrnl/pull/852) ([aallbrig](https://github.com/aallbrig))\n- Fix fish history instructions. [\\#846](https://github.com/jrnl-org/jrnl/pull/846) ([aureooms](https://github.com/aureooms))\n- Update site description [\\#841](https://github.com/jrnl-org/jrnl/pull/841) ([wren](https://github.com/wren))\n- Get rid of dumb sex joke [\\#840](https://github.com/jrnl-org/jrnl/pull/840) ([wren](https://github.com/wren))\n- Updating/clarifying template explanation [\\#829](https://github.com/jrnl-org/jrnl/pull/829) ([heymajor](https://github.com/heymajor))\n\n## [v2.2](https://pypi.org/project/jrnl/v2.2/) (2020-02-01)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.1.1...v2.2)\n\n**Implemented enhancements:**\n\n- Full text search \\(case insensitive\\) with \"-contains\" [\\#740](https://github.com/jrnl-org/jrnl/pull/740) ([empireshades](https://github.com/empireshades))\n- Reduce startup time by 55% [\\#719](https://github.com/jrnl-org/jrnl/pull/719) ([maebert](https://github.com/maebert))\n- Refactor password logic to prevent accidental password leakage [\\#708](https://github.com/jrnl-org/jrnl/pull/708) ([pspeter](https://github.com/pspeter))\n- Password confirmation [\\#706](https://github.com/jrnl-org/jrnl/pull/706) ([pspeter](https://github.com/pspeter))\n\n**Fixed bugs:**\n\n- Close temp file before passing it to editor to prevent file locking issues in Windows [\\#792](https://github.com/jrnl-org/jrnl/pull/792) ([micahellison](https://github.com/micahellison))\n- Fix crash while encrypting a journal on first run without saving password [\\#789](https://github.com/jrnl-org/jrnl/pull/789) ([dbxnr](https://github.com/dbxnr))\n\n**Build:**\n\n- Fix issue where jrnl would always out 'source' for version, fix Poetry config to build and publish properly [\\#820](https://github.com/jrnl-org/jrnl/pull/820) ([wren](https://github.com/wren))\n- Unpin poetry [\\#808](https://github.com/jrnl-org/jrnl/pull/808) ([wren](https://github.com/wren))\n- Fix all skipped tests on Travis Windows builds by preserving newlines [\\#823](https://github.com/jrnl-org/jrnl/pull/823) ([micahellison](https://github.com/micahellison))\n- Change PyPI auth method in build pipeline [\\#807](https://github.com/jrnl-org/jrnl/pull/807) ([wren](https://github.com/wren))\n- Automagically update the changelog you see before your very eyes! [\\#806](https://github.com/jrnl-org/jrnl/pull/806) ([wren](https://github.com/wren))\n- Update Black version and lock file to fix builds on develop branch [\\#784](https://github.com/jrnl-org/jrnl/pull/784) ([wren](https://github.com/wren))\n- Run black formatter on codebase for standardization [\\#778](https://github.com/jrnl-org/jrnl/pull/778) ([wren](https://github.com/wren))\n- Skip Broken Windows Tests [\\#772](https://github.com/jrnl-org/jrnl/pull/772) ([wren](https://github.com/wren))\n- Black Formatter [\\#769](https://github.com/jrnl-org/jrnl/pull/769) ([MinchinWeb](https://github.com/MinchinWeb))\n- Update lock file and testing suite for Python 3.8 [\\#765](https://github.com/jrnl-org/jrnl/pull/765) ([wren](https://github.com/wren))\n- Fix CI config to only deploy once [\\#761](https://github.com/jrnl-org/jrnl/pull/761) ([wren](https://github.com/wren))\n- More Travis-CI Testing [\\#759](https://github.com/jrnl-org/jrnl/pull/759) ([MinchinWeb](https://github.com/MinchinWeb))\n\n**Updated documentation:**\n\n- Explain how fish can be configured to exclude jrnl commands from history by default [\\#809](https://github.com/jrnl-org/jrnl/pull/809) ([aureooms](https://github.com/aureooms))\n- Remove merge marker in recipes.md [\\#782](https://github.com/jrnl-org/jrnl/pull/782) ([markphelps](https://github.com/markphelps))\n- Fix merge conflict left-over [\\#767](https://github.com/jrnl-org/jrnl/pull/767) ([thejspr](https://github.com/thejspr))\n- Display header in docs on mobile devices [\\#763](https://github.com/jrnl-org/jrnl/pull/763) ([maebert](https://github.com/maebert))\n\n## [v2.1.1](https://pypi.org/project/jrnl/v2.1.1/) (2019-11-26)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.1.post2...v2.1.1)\n\n**Implemented enhancements:**\n\n- Support Python 3.6+ [\\#710](https://github.com/jrnl-org/jrnl/pull/710) ([pspeter](https://github.com/pspeter))\n- Drop Python 2 support, add mocks in tests [\\#705](https://github.com/jrnl-org/jrnl/pull/705) ([pspeter](https://github.com/pspeter))\n\n**Fixed bugs:**\n\n- Prevent readline usage on Windows, which was causing Active Python crashes on install [\\#751](https://github.com/jrnl-org/jrnl/pull/751) ([micahellison](https://github.com/micahellison))\n- Exit jrnl if no text entered into editor [\\#744](https://github.com/jrnl-org/jrnl/pull/744) ([alichtman](https://github.com/alichtman))\n- Fix crash when no keyring backend available [\\#699](https://github.com/jrnl-org/jrnl/pull/699) ([pspeter](https://github.com/pspeter))\n- Fix parsing Journals using a little-endian date format [\\#694](https://github.com/jrnl-org/jrnl/pull/694) ([pspeter](https://github.com/pspeter))\n\n**Updated documentation:**\n\n- Update developer documentation [\\#752](https://github.com/jrnl-org/jrnl/pull/752) ([micahellison](https://github.com/micahellison))\n- Create templates for issues and pull requests [\\#679](https://github.com/jrnl-org/jrnl/pull/679) ([C0DK](https://github.com/C0DK))\n- Smaller doc fixes [\\#649](https://github.com/jrnl-org/jrnl/pull/649) ([maebert](https://github.com/maebert))\n- Move to mkdocs [\\#611](https://github.com/jrnl-org/jrnl/pull/611) ([maebert](https://github.com/maebert))\n\n## [v2.1.post2](https://pypi.org/project/jrnl/v2.1.post2/) (2019-11-11)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.0.1...v2.1.post2)\n\n**Fixed bugs:**\n\n- Expand paths that use ~ to full path [\\#704](https://github.com/jrnl-org/jrnl/pull/704) ([MinchinWeb](https://github.com/MinchinWeb))\n\n**Build:**\n\n- Separate local dev from pipeline releases [\\#684](https://github.com/jrnl-org/jrnl/pull/684) ([wren](https://github.com/wren))\n- Update version handling in source and travis deployments [\\#683](https://github.com/jrnl-org/jrnl/pull/683) ([wren](https://github.com/wren))\n- Use Poetry for dependency management and deployments [\\#612](https://github.com/jrnl-org/jrnl/pull/612) ([maebert](https://github.com/maebert))\n\n**Updated documentation:**\n\n- Fix typos, spelling [\\#734](https://github.com/jrnl-org/jrnl/pull/734) ([MinchinWeb](https://github.com/MinchinWeb))\n\n## [v2.0.1](https://pypi.org/project/jrnl/v2.0.1/) (2019-09-26)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.0.0...v2.0.1)\n\n**Implemented enhancements:**\n\n- Switch to hashmark Markdown headers on export \\(Mk II\\) [\\#639](https://github.com/jrnl-org/jrnl/pull/639) ([MinchinWeb](https://github.com/MinchinWeb))\n- Add '-not' flag for excluding tags from filter [\\#637](https://github.com/jrnl-org/jrnl/pull/637) ([jprof](https://github.com/jprof))\n- Handle KeyboardInterrupt when installing journal [\\#550](https://github.com/jrnl-org/jrnl/pull/550) ([silenc3r](https://github.com/silenc3r))\n\n**Fixed bugs:**\n\n- Change pyYAML required version [\\#660](https://github.com/jrnl-org/jrnl/pull/660) ([etnnth](https://github.com/etnnth))\n\n**Updated documentation:**\n\n- Fix references to Sphinx in CONTRIBUTING.md [\\#655](https://github.com/jrnl-org/jrnl/pull/655) ([maebert](https://github.com/maebert))\n\n## [v2.0.0](https://pypi.org/project/jrnl/v2.0.0/) (2019-08-24)\n\n[Full Changelog](https://github.com/jrnl-org/jrnl/compare/1.9.8...v2.0.0)\n\n🚨 **BREAKING CHANGES** 🚨\n\n**Implemented enhancements:**\n- Change cryptographic backend from PyCrypto to cryptography.io\n- Config now respects XDG conventions and may move accordingly\n- Config name changed from `journals.jrnl_name.journal` to `journals.jrnl_name.path`\n\n**Fixed bugs:**\n\n- Confirm that each journal can be parsed during upgrade, and abort upgrade if not [\\#650](https://github.com/jrnl-org/jrnl/pull/650) ([micahellison](https://github.com/micahellison))\n- Escape dates in square brackets [\\#644](https://github.com/jrnl-org/jrnl/pull/644) ([wren](https://github.com/wren))\n- Create encrypted journal [\\#641](https://github.com/jrnl-org/jrnl/pull/641) ([gregorybodnar](https://github.com/gregorybodnar))\n- Resolve issues around unreadable dates to allow markdown footnotes and prevent accidental deletion [\\#623](https://github.com/jrnl-org/jrnl/pull/623) ([micahellison](https://github.com/micahellison))\n- Update crypto module \\#610 [\\#621](https://github.com/jrnl-org/jrnl/pull/621) ([wren](https://github.com/wren))\n- Fix issue \\#584 YAMLLoadWarning [\\#585](https://github.com/jrnl-org/jrnl/pull/585) ([wren](https://github.com/wren))\n\n**Deprecated:**\n\n- Deprecate Python 2 [\\#624](https://github.com/jrnl-org/jrnl/pull/624) ([micahellison](https://github.com/micahellison))\n- Config now saved as YAML (no more JSON)\n\n**Build:**\n\n- change pinned label to a super cool emoji ⭐️ [\\#646](https://github.com/jrnl-org/jrnl/pull/646) ([wren](https://github.com/wren))\n- Update Travis build badge and restore pypi badges [\\#603](https://github.com/jrnl-org/jrnl/pull/603) ([micahellison](https://github.com/micahellison))\n\n**Updated documentation:**\n\n- Mention lack of Day One support and relevant history in readme [\\#608](https://github.com/jrnl-org/jrnl/pull/608) ([micahellison](https://github.com/micahellison))\n- Add a code of conduct file \\(rather than adding to contributing\\) [\\#604](https://github.com/jrnl-org/jrnl/pull/604) ([wren](https://github.com/wren))\n- Update docs to reflect merging jrnl-plus fork back upstream [\\#601](https://github.com/jrnl-org/jrnl/pull/601) ([micahellison](https://github.com/micahellison))\n- Add instructions for VS Code [\\#544](https://github.com/jrnl-org/jrnl/pull/544) ([emceeaich](https://github.com/emceeaich))\n\n## v1.9 (2014-07-21)\n\n* __1.9.5__ Multi-word tags for DayOne Journals\n* __1.9.4__ Fixed: Order of journal entries in file correct after --edit'ing\n* __1.9.3__ Fixed: Tags at the beginning of lines\n* __1.9.2__ Fixed: Tag search ignores email-addresses (thanks to @mjhoffman65)\n* __1.9.1__ Fixed: Dates in the future can be parsed as well.\n* __1.9.0__ Improved: Greatly improved date parsing. Also added an `-on` option for filtering\n\n## v1.8 (2014-05-22)\n\n* __1.8.7__ Fixed: -from and -to filters are inclusive (thanks to @grplyler)\n* __1.8.6__ Improved: Tags like @C++ and @OS/2 work, too (thanks to @chaitan94)\n* __1.8.5__ Fixed: file names when exporting to individual files contain full year (thanks to @jdevera)\n* __1.8.4__ Improved: using external editors (thanks to @chrissexton)\n* __1.8.3__ Fixed: export to text files and improves help (thanks to @igniteflow and @mpe)\n* __1.8.2__ Better integration with environment variables (thanks to @ajaam and @matze)\n* __1.8.1__ Minor bug fixes\n* __1.8.0__ Official support for python 3.4\n\n## v1.7 (2013-12-22)\n\n* __1.7.22__ Fixed an issue with writing files when exporting entries containing non-ascii characters.\n* __1.7.21__ jrnl now uses PKCS#7 padding.\n* __1.7.20__ Minor fixes when parsing DayOne journals\n* __1.7.19__ Creates full path to journal during installation if it doesn't exist yet\n* __1.7.18__ Small update to parsing regex\n* __1.7.17__ Fixes writing new lines between entries\n* __1.7.16__ Even more unicode fixes!\n* __1.7.15__ More unicode fixes\n* __1.7.14__ Fix for trailing whitespaces (eg. when writing markdown code block)\n* __1.7.13__ Fix for UTF-8 in DayOne journals\n* __1.7.12__ Fixes a bug where filtering by tags didn't work for DayOne journals\n* __1.7.11__ `-ls` will list all available journals (Thanks @jtan189)\n* __1.7.10__ Supports `-3` as a shortcut for `-n 3` and updates to tzlocal 1.1\n* __1.7.9__ Fix a logic bug so that jrnl -h and jrnl -v are possible even if jrnl not configured yet.\n* __1.7.8__ Upgrade to parsedatetime 1.2\n* __1.7.7__ Cleaned up imports, better unicode support\n* __1.7.6__ Python 3 port for slugify\n* __1.7.5__ Colorama is only needed on Windows. Smaller fixes\n* __1.7.3__ Touches temporary files before opening them to allow more external editors.\n* __1.7.2__ Dateutil added to requirements.\n* __1.7.1__ Fixes issues with parsing time information in entries.\n* __1.7.0__ Edit encrypted or DayOne journals with `jrnl --edit`.\n\n\n## v1.6 (2013-11-05)\n\n* __1.6.6__ -v prints the current version, also better strings for windows users. Furthermore, jrnl/jrnl.py moved to jrnl/cli.py\n* __1.6.5__ Allows composing multi-line entries on the command line or importing files\n* __1.6.4__ Fixed a bug that caused creating encrypted journals to fail\n* __1.6.3__ New, pretty, _useful_ documentation!\n* __1.6.2__ Starring entries now works for plain-text journals too!\n* __1.6.1__ Attempts to fix broken config files automatically\n* __1.6.0__ Passwords are now saved in the key-chain. The `password` field in `.jrnl_config` is soft-deprecated.\n\n## v1.5 (2013-08-06)\n\n* __1.5.7__ The `~` in journal config paths will now expand properly to e.g. `/Users/maebert`\n* __1.5.6__ Fixed: Fixed a bug where on OS X, the timezone could only be accessed on administrator accounts.\n* __1.5.5__ Fixed: Detects DayOne journals stored in `~/Library/Mobile Data` as well.\n* __1.5.4__ DayOne journals can now handle tags\n* __1.5.3__ Fixed: DayOne integration with older DayOne Journals\n* __1.5.2__ Soft-deprecated `-to` for filtering by time and introduces `-until` instead.\n* __1.5.1__ Fixed: Fixed a bug introduced in 1.5.0 that caused the entire journal to be printed after composing an entry\n* __1.5.0__ Exporting, encrypting and displaying tags now takes your filter options into account. So you could export everything before May 2012: `jrnl -to 'may 2012' --export json`. Or encrypt all entries tagged with `@work` into a new journal: `jrnl @work --encrypt work_journal.txt`. Or display all tags of posts where Bob is also tagged: `jrnl @bob --tags`\n\n## v1.4 (2013-07-22)\n\n* __1.4.2__ Fixed: Tagging works again\n* __1.4.0__ Unifies encryption between Python 2 and 3. If you have problems reading encrypted journals afterwards, first decrypt your journal with the __old__ jrnl version (install with `pip install jrnl==1.3.1`, then `jrnl --decrypt`), upgrade jrnl (`pip install jrnl --upgrade`) and encrypt it again (`jrnl --encrypt`).\n\n## v1.3 (2013-07-17)\n\n* __1.3.2__ Everything that is not direct output of jrnl will be written stderr to improve integration\n* __1.3.0__ Export to multiple files\n* __1.3.0__ Feature to export to given output file\n\n## v1.2 (2013-07-15)\n\n* __1.2.0__ Fixed: Timezone support for DayOne\n\n\n## v1.1 (2013-06-09)\n\n* __1.1.1__ Fixed: Unicode and Python3 issues resolved.\n* __1.1.0__\n    * JSON export exports tags as well.\n    * Nicer error message when there is a syntactical error in your config file.\n    * Unicode support\n\n## v1.0 (2013-03-04)\n\n* __1.0.5__ Backwards compatibility with `parsedatetime` 0.8.7\n* __1.0.4__\n    * Python 2.6 compatibility\n    * Better utf-8 support\n    * Python 3 compatibility\n    * Respects the `XDG_CONFIG_HOME` environment variable for storing your configuration file (Thanks [evaryont](https://github.com/evaryont))\n\n* __1.0.3__\n    * Removed clint in favour of colorama\n    * Fixed: Fixed a bug where showing tags failed when no tags are defined.\n    * Fixed: Improvements to config parsing (Thanks [alapolloni](https://github.com/alapolloni))\n    * Fixed: Fixes readline support on Windows\n    * Fixed: Smaller fixes and typos\n* __1.0.1__ (March 12, 2013) Fixed: Requires parsedatetime 1.1.2 or newer\n* __1.0.0__\n    * Integrates seamlessly with DayOne\n    * Each journal can have individual settings\n    * Fixed: A bug where jrnl would not go into compose mode\n    * Fixed: A bug where jrnl would not add entries without timestamp\n    * Fixed: Support for parsedatetime 1.x\n\n## v0.3 (2012-05-24)\n\n* __0.3.2__ Converts `\\n` to new lines (if using directly on a command line, make sure to wrap your entry with quotes).\n* __0.3.1__\n    * Supports deleting of last entry.\n    * Fixed: Fixes a bug where --encrypt or --decrypt without a target file would not work.\n    * Supports a config option for setting word wrap.\n    * Supports multiple journal files.\n* __0.3.0__\n    * Fixed: Dates such as \"May 3\" will now be interpreted as being in the past if the current day is at least 28 days in the future\n    * Fixed: Bug where composed entry is lost when the journal file fails to load\n    * Changed directory structure and install scripts (removing the necessity to make an alias from `jrnl` to `jrnl.py`)\n\n## v0.2 (2012-04-16)\n\n* __0.2.4__\n    * Fixed: Parsing of new lines in journal files and entries\n    * Adds support for encrypting and decrypting into new files\n* __0.2.3__\n    * Adds a `-short` option that will only display the titles of entries (or, when filtering by tags, the context of the tag)\n    * Adds tag export\n    * Adds coloured highlight of tags (by default, highlights all tags - when filtering by tags, only highlights search tags)\n    * `.jrnl_config` will get automatically updated when updating jrnl to a new version\n* __0.2.2__\n    * Adds --encrypt and --decrypt to encrypt / decrypt existing journal files\n    * Adds markdown export (kudos to dedan)\n* __0.2.1__ Submitted to [PyPi](http://pypi.python.org/pypi/jrnl/0.2.1).\n* __0.2.0__\n    * Encrypts using CBC\n    * Fixed: `key` has been renamed to `password` in config to avoid confusion. (The key use to encrypt and decrypt a journal is the SHA256-hash of the password.)\n\n## v0.1 (2012-04-13)\n\n\n* __0.1.1__\n    * Fixed: Removed unnecessary print commands\n    * Created the documentation\n* __0.1.0__\n    * Supports encrypted journals using AES encryption\n    * Support external editors for composing entries\n* __0.0.2__\n    * Filtering by tags and dates\n    * Fixed: Now using dedicated classes for Journals and entries\n\n## v0.0 (2012-03-29)\n\n* __0.0.1__ Composing entries works. That's pretty much it.\n\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by [emailing the maintainers](mailto:maintainers@jrnl.sh).\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Contributing\n\nSee \"[Contributing](docs/contributing.md)\" in the `docs` directory.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "### GNU GENERAL PUBLIC LICENSE\n\nVersion 3, 29 June 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc.\n<https://fsf.org/>\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n### Preamble\n\nThe GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nthe GNU General Public License is intended to guarantee your freedom\nto share and change all versions of a program--to make sure it remains\nfree software for all its users. We, the Free Software Foundation, use\nthe GNU General Public License for most of our software; it applies\nalso to any other work released this way by its authors. You can apply\nit to your programs, too.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nTo protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights. Therefore, you\nhave certain responsibilities if you distribute copies of the\nsoftware, or if you modify it: responsibilities to respect the freedom\nof others.\n\nFor example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received. You must make sure that they, too, receive\nor can get the source code. And you must show them these terms so they\nknow their rights.\n\nDevelopers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\nFor the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software. For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\nSome devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the\nmanufacturer can do so. This is fundamentally incompatible with the\naim of protecting users' freedom to change the software. The\nsystematic pattern of such abuse occurs in the area of products for\nindividuals to use, which is precisely where it is most unacceptable.\nTherefore, we have designed this version of the GPL to prohibit the\npractice for those products. If such problems arise substantially in\nother domains, we stand ready to extend this provision to those\ndomains in future versions of the GPL, as needed to protect the\nfreedom of users.\n\nFinally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish\nto avoid the special danger that patents applied to a free program\ncould make it effectively proprietary. To prevent this, the GPL\nassures that patents cannot be used to render the program non-free.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n### TERMS AND CONDITIONS\n\n#### 0. Definitions.\n\n\"This License\" refers to version 3 of the GNU General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds\nof works, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of\nan exact copy. The resulting work is called a \"modified version\" of\nthe earlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user\nthrough a computer network, with no transfer of a copy, is not\nconveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\" to\nthe extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n#### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work for\nmaking modifications to it. \"Object code\" means any non-source form of\na work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users can\nregenerate automatically from other parts of the Corresponding Source.\n\nThe Corresponding Source for a work in source code form is that same\nwork.\n\n#### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not convey,\nwithout conditions so long as your license otherwise remains in force.\nYou may convey covered works to others for the sole purpose of having\nthem make modifications exclusively for you, or provide you with\nfacilities for running those works, provided that you comply with the\nterms of this License in conveying all material for which you do not\ncontrol copyright. Those thus making or running the covered works for\nyou must do so exclusively on your behalf, under your direction and\ncontrol, on terms that prohibit them from making any copies of your\ncopyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under the\nconditions stated below. Sublicensing is not allowed; section 10 makes\nit unnecessary.\n\n#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such\ncircumvention is effected by exercising rights under this License with\nrespect to the covered work, and you disclaim any intention to limit\noperation or modification of the work as a means of enforcing, against\nthe work's users, your or third parties' legal rights to forbid\ncircumvention of technological measures.\n\n#### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n#### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these\nconditions:\n\n-   a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n-   b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under\n    section 7. This requirement modifies the requirement in section 4\n    to \"keep intact all notices\".\n-   c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy. This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged. This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n-   d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n#### 6. Conveying Non-Source Forms.\n\nYou may convey a covered work in object code form under the terms of\nsections 4 and 5, provided that you also convey the machine-readable\nCorresponding Source under the terms of this License, in one of these\nways:\n\n-   a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n-   b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the Corresponding\n    Source from a network server at no charge.\n-   c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source. This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n-   d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge. You need not require recipients to copy the\n    Corresponding Source along with the object code. If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source. Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n-   e) Convey the object code using peer-to-peer transmission,\n    provided you inform other peers where the object code and\n    Corresponding Source of the work are being offered to the general\n    public at no charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal,\nfamily, or household purposes, or (2) anything designed or sold for\nincorporation into a dwelling. In determining whether a product is a\nconsumer product, doubtful cases shall be resolved in favor of\ncoverage. For a particular product received by a particular user,\n\"normally used\" refers to a typical or common use of that class of\nproduct, regardless of the status of the particular user or of the way\nin which the particular user actually uses, or expects or is expected\nto use, the product. A product is a consumer product regardless of\nwhether the product has substantial commercial, industrial or\nnon-consumer uses, unless such uses represent the only significant\nmode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to\ninstall and execute modified versions of a covered work in that User\nProduct from a modified version of its Corresponding Source. The\ninformation must suffice to ensure that the continued functioning of\nthe modified object code is in no case prevented or interfered with\nsolely because modification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or\nupdates for a work that has been modified or installed by the\nrecipient, or for the User Product in which it has been modified or\ninstalled. Access to a network may be denied when the modification\nitself materially and adversely affects the operation of the network\nor violates the rules and protocols for communication across the\nnetwork.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n#### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders\nof that material) supplement the terms of this License with terms:\n\n-   a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n-   b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n-   c) Prohibiting misrepresentation of the origin of that material,\n    or requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n-   d) Limiting the use for publicity purposes of names of licensors\n    or authors of the material; or\n-   e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n-   f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions\n    of it) with contractual assumptions of liability to the recipient,\n    for any liability that these contractual assumptions directly\n    impose on those licensors and authors.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions; the\nabove requirements apply either way.\n\n#### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your license\nfrom a particular copyright holder is reinstated (a) provisionally,\nunless and until the copyright holder explicitly and finally\nterminates your license, and (b) permanently, if the copyright holder\nfails to notify you of the violation by some reasonable means prior to\n60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n#### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or run\na copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n#### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n#### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims owned\nor controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within the\nscope of its coverage, prohibits the exercise of, or is conditioned on\nthe non-exercise of one or more of the rights that are specifically\ngranted under this License. You may not convey a covered work if you\nare a party to an arrangement with a third party that is in the\nbusiness of distributing software, under which you make payment to the\nthird party based on the extent of your activity of conveying the\nwork, and under which the third party grants, to any of the parties\nwho would receive the covered work from you, a discriminatory patent\nlicense (a) in connection with copies of the covered work conveyed by\nyou (or copies made from those copies), or (b) primarily for and in\nconnection with specific products or compilations that contain the\ncovered work, unless you entered into that arrangement, or that patent\nlicense was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n#### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under\nthis License and any other pertinent obligations, then as a\nconsequence you may not convey it at all. For example, if you agree to\nterms that obligate you to collect a royalty for further conveying\nfrom those to whom you convey the Program, the only way you could\nsatisfy both those terms and this License would be to refrain entirely\nfrom conveying the Program.\n\n#### 13. Use with the GNU Affero General Public License.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n#### 14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions\nof the GNU General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in\ndetail to address new problems or concerns.\n\nEach version is given a distinguishing version number. If the Program\nspecifies that a certain numbered version of the GNU General Public\nLicense \"or any later version\" applies to it, you have the option of\nfollowing the terms and conditions either of that numbered version or\nof any later version published by the Free Software Foundation. If the\nProgram does not specify a version number of the GNU General Public\nLicense, you may choose any version ever published by the Free\nSoftware Foundation.\n\nIf the Program specifies that a proxy can decide which future versions\nof the GNU General Public License can be used, that proxy's public\nstatement of acceptance of a version permanently authorizes you to\nchoose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n#### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT\nWARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND\nPERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE\nDEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR\nCORRECTION.\n\n#### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR\nCONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES\nARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT\nNOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR\nLOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM\nTO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER\nPARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n\n#### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n### How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these\nterms.\n\nTo do so, attach the following notices to the program. It is safest to\nattach them to the start of each source file to most effectively state\nthe exclusion of warranty; and each file should have at least the\n\"copyright\" line and a pointer to where the full notice is found.\n\n        <one line to give the program's name and a brief idea of what it does.>\n        Copyright (C) <year>  <name of author>\n\n        This program is free software: you can redistribute it and/or modify\n        it under the terms of the GNU General Public License as published by\n        the Free Software Foundation, either version 3 of the License, or\n        (at your option) any later version.\n\n        This program is distributed in the hope that it will be useful,\n        but WITHOUT ANY WARRANTY; without even the implied warranty of\n        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n        GNU General Public License for more details.\n\n        You should have received a copy of the GNU General Public License\n        along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper\nmail.\n\nIf the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n        <program>  Copyright (C) <year>  <name of author>\n        This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n        This is free software, and you are welcome to redistribute it\n        under certain conditions; type `show c' for details.\n\nThe hypothetical commands \\`show w' and \\`show c' should show the\nappropriate parts of the General Public License. Of course, your\nprogram's commands might be different; for a GUI interface, you would\nuse an \"about box\".\n\nYou should also get your employer (if you work as a programmer) or\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary. For more information on this, and how to apply and follow\nthe GNU GPL, see <https://www.gnu.org/licenses/>.\n\nThe GNU General Public License does not permit incorporating your\nprogram into proprietary programs. If your program is a subroutine\nlibrary, you may consider it more useful to permit linking proprietary\napplications with the library. If this is what you want to do, use the\nGNU Lesser General Public License instead of this License. But first,\nplease read <https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n<p align=\"center\">\n<a href=\"https://jrnl.sh\">\n<img align=\"center\" src=\"https://raw.githubusercontent.com/jrnl-org/jrnl/develop/docs_theme/assets/readme-header.png\"/>\n</a>\n</p>\n\njrnl\n [![Testing](https://github.com/jrnl-org/jrnl/workflows/Testing/badge.svg)](https://github.com/jrnl-org/jrnl/actions?query=workflow%3ATesting)\n [![Downloads](https://pepy.tech/badge/jrnl)](https://pepy.tech/project/jrnl)\n [![Version](http://img.shields.io/pypi/v/jrnl.svg?style=flat)](https://pypi.python.org/pypi/jrnl/)\n [![Homebrew](https://img.shields.io/homebrew/v/jrnl?style=flat-square)](https://formulae.brew.sh/formula/jrnl)\n [![Gitter](https://img.shields.io/gitter/room/jrnl-org/jrnl)](https://gitter.im/jrnl-org/jrnl)\n [![Changelog](https://img.shields.io/badge/changelog-on%20github-green)](https://github.com/jrnl-org/jrnl/blob/develop/CHANGELOG.md)\n====\n\n_To get help, [submit an issue](https://github.com/jrnl-org/jrnl/issues/new/choose) on\nGitHub._\n\n`jrnl` is a simple journal application for the command line.\n\nYou can use it to easily create, search, and view journal entries. Journals are\nstored as human-readable plain text, and can also be encrypted using  [AES\nencryption](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard).\n\n## In a Nutshell\n\nTo make a new entry, just enter\n\n``` sh\njrnl yesterday: Called in sick. Used the time to clean the house and write my\nbook.\n```\n\n`yesterday:` is  interpreted by `jrnl` as a timestamp. Everything until the\nfirst sentence ending (either `.`, `?`, or `!`) is interpreted as the title, and\nthe rest as the body. In your journal file, the result will look like this:\n\n    [2012-03-29 09:00] Called in sick.\n    Used the time to clean the house and write my book.\n\nIf you just call `jrnl`, you will be prompted to compose your entry - but you\ncan also configure _jrnl_ to use your external editor.\n\nFor more information, please read the\n[documentation](https://jrnl.sh).\n\n## Contributors\n\n### Maintainers\n\nOur maintainers help keep the lights on for the project:\n\n * Jonathan Wren ([wren](https://github.com/wren))\n * Micah Ellison ([micahellison](https://github.com/micahellison))\n\nPlease thank them if you like `jrnl`!\n\n### Code Contributors\n\nThis project is made with love by the many fabulous people who have contributed.\n`jrnl` couldn't exist without each and every one of you!\n\n<a href=\"https://github.com/jrnl-org/jrnl/graphs/contributors\"><img\nsrc=\"https://opencollective.com/jrnl/contributors.svg?width=890&button=false\"\n/></a>\n\nIf you'd also like to help make `jrnl` better, please see our [contributing\ndocumentation](docs/contributing.md).\n\n### Financial Backers\n\nAnother way show support is through direct financial contributions. These funds\ngo to covering our costs, and are a quick way to show your appreciation for\n`jrnl`.\n\n[Become a financial contributor](https://opencollective.com/jrnl/contribute)\nand help us sustain our community.\n\n<a href=\"https://opencollective.com/jrnl\"><img\nsrc=\"https://opencollective.com/jrnl/individuals.svg?width=890\"></a>\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security\r\n\r\nIf you've discovered a potential security issue in jrnl, please contact the maintainers at [maintainers@jrnl.sh](mailto:maintainers@jrnl.sh).\r\n\r\nYou can also feel free to [open an issue](https://github.com/jrnl-org/jrnl/issues/new/choose) (but please don't disclose the vulnerability) in case the email goes to spam.\r\n\r\nYou can find [known privacy and security issues in our documentation](https://jrnl.sh/en/stable/privacy-and-security/).\r\n"
  },
  {
    "path": "docs/advanced.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Advanced Usage\n\n## Configuration File\n\n`jrnl` has a wide variety of options that can be customized through the config file,\nincluding templates, formats, multiple journals, and more. See\nthe [configuration file reference](./reference-config-file.md) for details\nor read on for some common use cases.\n\n### Multiple journal files\n\nYou can configure `jrnl`to use with multiple journals (eg.\n`private` and `work`) by defining more journals in your [config file](./reference-config-file.md),\nfor example:\n\n``` yaml\njournals:\n  default: ~/journal.txt\n  work: ~/work.txt\n```\n\nThe `default` journal gets created the first time you start `jrnl`\nNow you can access the `work` journal by using `jrnl work` instead of\n`jrnl`, eg.\n\n``` sh\njrnl work at 10am: Meeting with @Steve\njrnl work -n 3\n```\n\nwill both use `~/work.txt`, while `jrnl -n 3` will display the last\nthree entries from `~/journal.txt` (and so does `jrnl default -n 3`).\n\nYou can also override the default options for each individual journal.\nIf your `jrnl.yaml` looks like this:\n\n``` yaml\nencrypt: false\njournals:\n  default: ~/journal.txt\n  work:\n    journal: ~/work.txt\n    encrypt: true\n  food: ~/my_recipes.txt\n```\n\nYour `default` and your `food` journals won't be encrypted, however your\n`work` journal will!\n\nYou can override all options that are present at\nthe top level of `jrnl.yaml`, just make sure that at the very least\nyou specify a `journal: ...` key that points to the journal file of\nthat journal.\n\nConsider the following example configuration\n\n``` yaml\neditor: vi -c startinsert \njournals: \n  default: ~/journal.txt \n  work: \n    journal: ~/work.txt \n    encrypt: true \n    display_format: json \n    editor: code -rw \n  food:\n    display_format: markdown \n    journal: ~/recipes.txt \n```\n\nThe `work` journal is encrypted, prints to `json` by default, and is edited using an existing window of VSCode. Similarly, the `food` journal prints to markdown by default, but uses all the other defaults.\n\n### Modifying Configurations from the Command line \n\nYou can override a configuration field for the current instance of `jrnl` using `--config-override CONFIG_KEY CONFIG_VALUE` where `CONFIG_KEY` is a valid configuration field, specified in dot notation and `CONFIG_VALUE` is the (valid) desired override value. The dot notation can be used to change config keys within other keys, such as `colors.title` for the `title` key within the `colors` key.\n\nYou can specify multiple overrides as multiple calls to `--config-override`.\n\n!!! note\n    These overrides allow you to modify ***any*** field of your jrnl configuration. We trust that you know what you are doing. \n\n#### Examples: \n\n``` sh\n# Create an entry using the `stdin` prompt, for rapid logging\njrnl --config-override editor \"\"\n\n# Populate a project's log\njrnl --config-override journals.todo \"$(git rev-parse --show-toplevel)/todo.txt\" todo find my towel \n\n# Pass multiple overrides \njrnl --config-override display_format fancy --config-override linewrap 20 \\\n--config-override colors.title green\n```\n\n### Using an alternate config\n\nYou can specify an alternate configuration file for the current instance of `jrnl` using `--config-file CONFIG_FILE_PATH` where\n`CONFIG_FILE_PATH` is a path to an alternate `jrnl` configuration file. \n\n#### Examples:\n\n``` sh\n# Use personalised configuration file for personal journal entries\njrnl --config-file ~/foo/jrnl/personal-config.yaml\n\n# Use alternate configuration file for work-related entries\njrnl --config-file ~/foo/jrnl/work-config.yaml\n\n# Use default configuration file (created on first run)\njrnl\n```\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Contributing to jrnl\n\nWe welcome contributions to jrnl, whether it's through reporting bugs, improving the documentation, testing releases, engaging in discussion on features and bugs, or writing code.\n\n## Table of Contents\n * [Code of Conduct](#code-of-conduct)\n * [Reporting Bugs](#reporting-bugs)\n * [Editing Documentation](#editing-documentation)\n * [Testing](#testing)\n * [Submitting feature requests and ideas](#submitting-feature-requests-and-ideas)\n * [Developing jrnl](#developing)\n\n## Code of Conduct\n\nBefore starting, please read the [Code of Conduct](https://github.com/jrnl-org/jrnl/blob/develop/CODE_OF_CONDUCT.md).\n\n## Reporting Bugs\n\nPlease report bugs by [opening a new issue](https://github.com/jrnl-org/jrnl/issues/new/choose) and describing it as well as possible. Many bugs are specific to a particular operating system and Python version, so please include that information!\n\n## Editing Documentation\n\nIf you find a typo or a mistake in the docs, please fix it right away and send a pull request. If you're unsure what to change but still see a problem, you can [open a new issue](https://github.com/jrnl-org/jrnl/issues/new/choose) with the \"Documentation change\" type.\n\nTo edit the documentation, edit the `docs/*.md` files on the **develop** branch. You can see the result by running `poe docs-run` inside the project's root directory, then navigating your browser to [localhost:8000](http://localhost:8000).\n\n### External editors and tips and tricks\n\nIf you'd like to share a jrnl command line trick that you find useful, you may find it worthwhile to add it to the [\"Tips and Tricks\" section](tips-and-tricks.md). For advice on how to integrate a particular external editor, you can add to the [\"External Editors\" section](external-editors.md).\n\n## Testing\n\nMuch of the work of maintaining jrnl involves testing rather than coding.\n\nThe nature of jrnl means we deal with extremely sensitive data, and can't risk data loss. While jrnl does have a comprehensive automated testing suite, user testing is crucial to mitigating this risk.\n\n### Prereleases\n\n[Prereleases are deployed through PyPi much like normal releases](https://pypi.org/project/jrnl/#history). You can use [pipx](https://pypi.org/project/pipx/) to fetch them and test them. See the [changelog](https://github.com/jrnl-org/jrnl/blob/develop/CHANGELOG.md) for information on what has changed with each release.\n\n### Pull requests\n\nIf you are comfortable enough with git, feel free to fetch particular [pull requests](https://github.com/jrnl-org/jrnl/pulls), test them yourself, and report back your findings. Bonus points if you can add a screencast of how the new feature works.\n\n### Confirm bug reports\n\nThere are always [open bugs among our GitHub issues](https://github.com/jrnl-org/jrnl/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and many are specific to a particular OS, Python version, or jrnl version. A simple comment like \"Confirmed on jrnl v2.2, MacOS 10.15, Python 3.8.1\" would be extremely helpful in tracking down bugs.\n\n### Automate tests\n\nSee the develop section below for information on how to contribute new automated tests.\n\n## Submitting feature requests and ideas\n\nIf you have a feature request or idea for jrnl, please [open a new issue](https://github.com/jrnl-org/jrnl/issues/new/choose) and describe the goal of the feature, and any relevant use cases. We'll discuss the issue with you, and decide if it's a good fit for the project.\n\nWhen discussing new features, please keep in mind our design goals. jrnl strives to\n[do one thing well](https://en.wikipedia.org/wiki/Unix_philosophy). To us, that means:\n\n* being _nimble_\n* having a simple interface\n* avoiding duplicating functionality\n\n## Developing\n\n### Getting your environment set up\n\nYou will need to install [poetry](https://python-poetry.org/) to develop jrnl. It will take care of all of the project's other dependencies.\n\n### Understanding the branches\n\njrnl uses two primary branches:\n\n * `develop` - for ongoing development\n * `release` - for releases\n\nIn general, pull requests should be made on the `develop` branch.\n\n### Common development commands\n\nYou can find an inventory of commands in the `pyproject.toml`. Users can run the commands by typing `poe` followed by the name of the command ([Poe the Poet](https://github.com/nat-n/poethepoet) can be installed on its own, or as part of `poetry install`).\n\nA typical development workflow includes:\n\n * Installing dependencies:\n    * `poetry install`\n * Activate virtual environment:\n    * `poetry shell`\n * Running the source in a virtual environment:\n    * `jrnl` (with or without arguments as necessary)\n * Running tests:\n     * `poe test`\n * Formatting the code to standardize its style:\n     * `poe format`\n\n### Updating automated tests\n\nWhen resolving bugs or adding new functionality, please add tests to prevent that functionality from breaking in the future. If you notice any functionality that isn't covered in the tests, feel free to submit a test-only pull request as well.\n\nFor testing, jrnl uses [pytest](https://docs.pytest.org) for unit tests, and [pytest-bdd](https://pytest-bdd.readthedocs.io/) for integration testing. All tests are in the `tests` folder.\n\nMany tests can be created by only editing `*.feature` files with the same format as other tests. For more complicated functionality, you may need to implement steps in `tests/lib/` which are then executed by your tests in the `feature` files.\n\n### Submitting pull requests\n\nWhen you're ready, feel free to submit a pull request (PR). The jrnl maintainers generally review the pull requests every two weeks, but the continuous integration pipeline will run on automated tests on it within a matter of minutes and will report back any issues it has found with your code across a variety of environments.\n\nThe pull request template contains a checklist full of housekeeping items. Please fill them out as necessary when you submit.\n\nIf a pull request contains failing tests, it probably will not be reviewed, and it definitely will not be approved. However, if you need help resolving a failing test, please mention that in your PR.\n\n### Finding things to work on\n\nYou can search the [jrnl GitHub issues](https://github.com/jrnl-org/jrnl/issues) by [label](https://github.com/jrnl-org/jrnl/labels) for things to work on. Here are some labels worth searching:\n\n* [critical](https://github.com/jrnl-org/jrnl/labels/critical)\n* [help wanted](https://github.com/jrnl-org/jrnl/labels/help%20wanted)\n* [bug](https://github.com/jrnl-org/jrnl/labels/bug)\n* [enhancement](https://github.com/jrnl-org/jrnl/labels/enhancement)\n\nYou can also get a feel for the project's priorities by reviewing the [milestones](https://github.com/jrnl-org/jrnl/milestones).\n\n### A note for new programmers and programmers new to python\n\nAlthough jrnl has grown quite a bit since its inception, the overall complexity (for an end-user program) is fairly low, and we hope you'll find the code easy enough to understand.\n\nIf you have a question, please don't hesitate to ask! Python is known for its welcoming community and openness to novice programmers, so feel free to fork the code and play around with it! If you create something you want to share with us, please create a pull request. We never expect pull requests to be perfect, idiomatic, instantly mergeable code. We can work through it together!\n"
  },
  {
    "path": "docs/encryption.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Encryption\n\n## A Note on Security\n\nWhile `jrnl` follows best practices, total security is never possible in the\nreal world. There are a number of ways that people can at least partially\ncompromise your `jrnl` data. See the [Privacy and Security](./privacy-and-security.md) page\nfor more information.\n\n## Encrypting and Decrypting\n\nExisting plain text journal files can be encrypted using the `--encrypt`\ncommand:\n\n``` sh\njrnl --encrypt [FILENAME]\n```\n\nYou can then enter a new password, and the unencrypted file will replaced with\nthe new encrypted file.\n\nThis command also works to change the password for a journal file that is\nalready encrypted. `jrnl` will prompt you for the current password and then new\npassword.\n\nConversely,\n\n``` sh\njrnl --decrypt [FILENAME]\n```\n\nreplaces the encrypted journal file with a plain text file. You can also specify\na filename, e.g., `jrnl --decrypt plain_text_copy.txt`, to leave the original\nencrypted file untouched and create a new plain text file next to it.\n\n!!! note\n    Changing `encrypt` in your [config file](./reference-config-file.md) to\n    a different value will not encrypt or decrypt your\n    journal file. It merely says whether or not your journal\n    is encrypted. Hence manually changing\n    this option will most likely result in your journal file being\n    impossible to load. This is why the above commands are necessary.\n\n## Storing Passwords in Your Keychain\n\nNobody can recover or reset your `jrnl` password. If you lose it,\nyour data will be inaccessible forever.\n\nFor this reason, when encrypting a journal, `jrnl` asks whether you would like\nto store the password in your system's keychain. An added benefit is that you\nwill not need to enter the password when interacting with the journal file.\n\nIf you don't initially store the password in your keychain but decide to do so\nlater---or if you want to store it in one computer's keychain but not in another\ncomputer's---you can run `jrnl --encrypt` on an encrypted journal and use the\nsame password again. This will trigger the keychain storage prompt.\n\n## Manual Decryption\n\nThe easiest way to decrypt your journal is with `jrnl --decrypt`, but you could\nalso decrypt your journal manually if needed. To do this, you can use any\nprogram that supports the AES algorithm (specifically AES-CBC), and you'll need\nthe following relevant information for decryption:\n\n- **Key:** The key used for encryption is the\n    [SHA-256](https://en.wikipedia.org/wiki/SHA-2) hash of your password.\n- **Initialization vector (IV):** The IV is stored in the first 16 bytes of\n    your encrypted journal file.\n- **The actual text of the journal** (everything after the first 16 bytes in\n    the encrypted journal file) is encoded in\n    [UTF-8](https://en.wikipedia.org/wiki/UTF-8) and padded according to\n    [PKCS\\#7](https://en.wikipedia.org/wiki/PKCS_7) before being encrypted.\n\nIf you'd like an example of what this might look like in script form, please\nsee below for some examples of Python scripts that you could use to manually\ndecrypt your journal.\n\n\n\n!!! note\n    These are only examples, and are only here to illustrate that your journal files\n    will still be recoverable even if `jrnl` isn't around anymore. Please use \n    `jrnl --decrypt` if available.\n\n**Example for jrnl v2 files**:\n``` python\n#!/usr/bin/env python3\n\"\"\"\nDecrypt a jrnl v2 encrypted journal.\n\nNote: the `cryptography` module must be installed (you can do this with\nsomething like `pip3 install crytography`)\n\"\"\"\n\nimport base64\nimport getpass\nfrom pathlib import Path\n\nfrom cryptography.fernet import Fernet\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC\n\nfilepath = input(\"journal file path: \")\npassword = getpass.getpass(\"Password: \")\n\nwith open(Path(filepath), \"rb\") as f:\n    ciphertext = f.read()\n\npassword = password.encode(\"utf-8\")\nkdf = PBKDF2HMAC(\n    algorithm=hashes.SHA256(),\n    length=32,\n    salt=b\"\\xf2\\xd5q\\x0e\\xc1\\x8d.\\xde\\xdc\\x8e6t\\x89\\x04\\xce\\xf8\",\n    iterations=100_000,\n    backend=default_backend(),\n)\n\nkey = base64.urlsafe_b64encode(kdf.derive(password))\n\nprint(Fernet(key).decrypt(ciphertext).decode(\"utf-8\"))\n```\n\n**Example for jrnl v1 files**:\n``` python\n#!/usr/bin/env python3\n\"\"\"\nDecrypt a jrnl v1 encrypted journal.\n\nNote: the `pycrypto` module must be installed (you can do this with something\nlike `pip3 install pycrypto`)\n\"\"\"\n\nimport argparse\nimport getpass\nimport hashlib\n\nfrom Crypto.Cipher import AES\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"filepath\", help=\"journal file to decrypt\")\nargs = parser.parse_args()\n\npwd = getpass.getpass()\nkey = hashlib.sha256(pwd.encode(\"utf-8\")).digest()\n\nwith open(args.filepath, \"rb\") as f:\n    ciphertext = f.read()\n\ncrypto = AES.new(key, AES.MODE_CBC, ciphertext[:16])\nplain = crypto.decrypt(ciphertext[16:])\nplain = plain.strip(plain[-1:])\nplain = plain.decode(\"utf-8\")\nprint(plain)\n```\n"
  },
  {
    "path": "docs/external-editors.md",
    "content": "<!--\r\nCopyright © 2012-2023 jrnl contributors\r\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\r\n-->\r\n\r\n# External editors\r\n\r\nConfigure your preferred external editor by updating the `editor` option\r\nin your [configuration file](./reference-config-file.md#editor). If your editor is not \r\nin your operating system's `PATH` environment variable, then you will have to \r\nenter the full path of your editor.\r\n\r\nOnce it's configured, you can create an entry as a new document in your editor using the `jrnl` \r\ncommand by itself:\r\n\r\n``` text\r\njrnl\r\n```\r\n\r\nYou can specify the time and title of the entry as usual on the first line of the document. \r\n\r\nIf you want, you can skip the editor by including a quick entry with the `jrnl` command:\r\n\r\n``` text\r\njrnl yesterday: All my troubles seemed so far away.\r\n```\r\n\r\nIf you want to start the entry on the command line and continue writing in your chosen editor, \r\nuse the `--edit` flag. For example:\r\n\r\n``` text\r\njrnl yesterday: All my troubles seemed so far away. --edit\r\n```\r\n\r\n!!! note\r\n    To save and log any entry edits, save and close the file.\r\n\r\nAll editors must be [blocking processes](https://en.wikipedia.org/wiki/Blocking_(computing)) to work with jrnl. Some editors, such as [micro](https://micro-editor.github.io/), are blocking by default, though others can be made to block with additional arguments, such as many of those documented below. If jrnl opens your editor but finishes running immediately, then your editor is not a blocking process, and you may be able to correct that with one of the suggestions below.\r\n\r\nPlease see [this section](./privacy-and-security.md#editor-history) about how\r\nyour editor might leak sensitive information and how to mitigate that risk.\r\n\r\n## Sublime Text\r\n\r\nTo use [Sublime Text](https://www.sublimetext.com/), install the command line\r\ntools for Sublime Text and configure your `jrnl.yaml` like this:\r\n\r\n```yaml\r\neditor: \"subl -w\"\r\n```\r\n\r\nNote the `-w` flag to make sure `jrnl` waits for Sublime Text to close the\r\nfile before writing into the journal.\r\n\r\n## Visual Studio Code\r\n\r\n[Visual Studio Code](https://code.visualstudio.com) also requires a flag\r\nthat tells the process to wait until the file is closed before exiting:\r\n\r\n```yaml\r\neditor: \"code --wait\"\r\n```\r\n\r\nOn Windows, `code` is not added to the path by default, so you'll need to\r\nenter the full path to your `code.exe` file, or add it to the `PATH` variable.\r\n\r\n## MacVim\r\n\r\nAlso similar to Sublime Text, MacVim must be started with a flag that tells\r\nthe the process to wait until the file is closed before passing control\r\nback to journal. In the case of MacVim, this is `-f`:\r\n\r\n```yaml\r\neditor: \"mvim -f\"\r\n```\r\n\r\n## Vim/Neovim\r\n\r\nTo use any of the Vim derivatives as editor in Linux, simply set the `editor`\r\nto the executable:\r\n\r\n```yaml\r\neditor: \"vim\"\r\n# or\r\neditor: \"nvim\"\r\n```\r\n\r\n## iA Writer\r\n\r\nOn OS X, you can use the fabulous [iA\r\nWriter](http://www.iawriter.com/mac) to write entries. Configure your\r\n`jrnl.yaml` like this:\r\n\r\n```yaml\r\neditor: \"open -b pro.writer.mac -Wn\"\r\n```\r\n\r\nWhat does this do? `open -b ...` opens a file using the application\r\nidentified by the bundle identifier (a unique string for every app out\r\nthere). `-Wn` tells the application to wait until it's closed before\r\npassing back control, and to use a new instance of the application.\r\n\r\nIf the `pro.writer.mac` bundle identifier is not found on your system,\r\nyou can find the right string to use by inspecting iA Writer's\r\n`Info.plist` file in your shell:\r\n\r\n```sh\r\ngrep -A 1 CFBundleIdentifier /Applications/iA\\ Writer.app/Contents/Info.plist\r\n```\r\n\r\n## Notepad++ on Windows\r\n\r\nTo set [Notepad++](http://notepad-plus-plus.org/) as your editor, edit\r\nthe `jrnl` config file (`jrnl.yaml`) like this:\r\n\r\n```yaml\r\neditor: \"C:\\\\Program Files (x86)\\\\Notepad++\\\\notepad++.exe -multiInst -nosession\"\r\n```\r\n\r\nThe double backslashes are needed so `jrnl` can read the file path\r\ncorrectly. The `-multiInst -nosession` options will cause `jrnl` to open\r\nits own Notepad++ window.\r\n\r\n\r\n## emacs\r\n\r\nTo use `emacs` as your editor, edit the `jrnl` config file (`jrnl.yaml`) like this:\r\n\r\n```yaml\r\neditor: emacsclient -a \"\" -c\r\n```\r\n\r\nWhen you're done editing the message, save and `C-x #` to close the buffer and stop the emacsclient process.\r\n\r\n## Other editors\r\n\r\nIf you're using another editor and would like to share, feel free to [contribute documentation](./contributing.md#editing-documentation) on it.\r\n"
  },
  {
    "path": "docs/formats.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Formats\n\n`jrnl` supports a variety of alternate formats. These can be used to display your\njournal in a different manner than the `jrnl` default, and can even be used to pipe data\nfrom your journal for use in another program to create reports, or do whatever you want\nwith your `jrnl` data.\n\nAny of these formats can be used with a search (e.g. `jrnl -contains \"lorem ipsum\"\n--format json`) to display the results of that search in the given format, or can be\nused alone (e.g. `jrnl --format json`) to display all entries from the selected journal.\n\nThis page shows examples of all the built-in formats, but since `jrnl` supports adding\nmore formats through plugins, you may have more available on your system. Please see\n`jrnl --help` for a list of which formats are available on your system.\n\nAny of these formats can be used interchangeably, and are only grouped into \"display\",\n\"data\", and \"report\" formats below for convenience.\n\n## Display Formats\nThese formats are mainly intended for displaying your journal in the terminal. Even so,\nthey can still be used in the same way as any other format (like written to a file, if\nyou choose).\n\n### Pretty\n``` sh\njrnl --format pretty\n# or\njrnl -1 # any search\n```\n\nThis is the default format in `jrnl`. If no `--format` is given, `pretty` will be used.\n\nIt displays the timestamp of each entry formatted to by the user config followed by the\ntitle on the same line. Then the body of the entry is shown below.\n\nThis format is configurable through these values from your config file (see\n[Advanced Usage](./advanced.md) for more details):\n\n- `colors`\n    - `body`\n    - `date`\n    - `tags`\n    - `title`\n- `indent_character`\n- `linewrap`\n- `timeformat`\n\n**Example output**:\n``` sh\n2020-06-28 18:22 This is the first sample entry\n| This is the sample body text of the first sample entry.\n\n2020-07-01 20:00 This is the second sample entry\n| This is the sample body text of the second sample entry, but\n| this one has a @tag.\n\n2020-07-02 09:00 This is the third sample entry\n| This is the sample body text of the third sample entry.\n```\n\n### Short\n\n``` sh\njrnl --format short\n# or\njrnl --short\n```\n\nThis will shorten entries to display only the date and title. It is essentially the\n`pretty` format but without the body of each entry. This can be useful if you have long\njournal entries and only want to see a list of entries that match your search.\n\n**Example output**:\n``` sh\n2020-06-28 18:22 This is the first sample entry\n2020-07-01 20:00 This is the second sample entry\n2020-07-02 09:00 This is the third sample entry\n```\n\n### Fancy (or Boxed)\n``` sh\njrnl --format fancy\n# or\njrnl --format boxed\n```\n\nThis format outlines each entry with a border. This makes it much easier to tell where\neach entry starts and ends. It's an example of how free-form the formats can be, and also\njust looks kinda ~*~fancy~*~, if you're into that kind of thing.\n\n**Example output**:\n``` sh\n┎──────────────────────────────────────────────────────────────────────╮2020-06-28 18:22\n┃ This is the first sample entry                                       ╘═══════════════╕\n┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n┃ This is the sample body text of the first sample entry.                              │\n┖──────────────────────────────────────────────────────────────────────────────────────┘\n┎──────────────────────────────────────────────────────────────────────╮2020-07-01 20:00\n┃ This is the second sample entry                                      ╘═══════════════╕\n┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n┃ This is the sample body text of the second sample entry, but this one has a @tag.    │\n┖──────────────────────────────────────────────────────────────────────────────────────┘\n┎──────────────────────────────────────────────────────────────────────╮2020-07-02 09:00\n┃ This is the third sample entry                                       ╘═══════════════╕\n┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n┃ This is the sample body text of the third sample entry.                              │\n┖──────────────────────────────────────────────────────────────────────────────────────┘\n```\n\n## Data Formats\nThese formats are mainly intended for piping or exporting your journal to other\nprograms. Even so, they can still be used in the same way as any other format (like\nwritten to a file, or displayed in your terminal, if you want).\n\n!!! note\nYou may see boxed messages like \"2 entries found\" when using these formats, but\nthose messages are written to `stderr` instead of `stdout`, and won't be piped when\nusing the `|` operator.\n\n### JSON\n\n``` sh\njrnl --format json\n```\n\nJSON is a very handy format used by many programs and has support in nearly every\nprogramming language. There are many things you could do with JSON data. Maybe you could\nuse `jq` ([project page](https://github.com/stedolan/jq)) to filter through the fields in your journal.\nLike this:\n\n``` sh\n$ j -3 --format json | jq '.entries[].date'                                                                                                                            jrnl-GFqVlfgP-py3.8 \n\"2020-06-28\"\n\"2020-07-01\"\n\"2020-07-02\"\n```\n\nOr why not create a [beautiful timeline](http://timeline.knightlab.com/) of your journal?\n\n**Example output**:\n``` json\n{\n  \"tags\": {\n    \"@tag\": 1\n  },\n  \"entries\": [\n    {\n      \"title\": \"This is the first sample entry\",\n      \"body\": \"This is the sample body text of the first sample entry.\",\n      \"date\": \"2020-06-28\",\n      \"time\": \"18:22\",\n      \"tags\": [],\n      \"starred\": false\n    },\n    {\n      \"title\": \"This is the second sample entry\",\n      \"body\": \"This is the sample body text of the second sample entry, but this one has a @tag.\",\n      \"date\": \"2020-07-01\",\n      \"time\": \"20:00\",\n      \"tags\": [\n        \"@tag\"\n      ],\n      \"starred\": false\n    },\n    {\n      \"title\": \"This is the third sample entry\",\n      \"body\": \"This is the sample body text of the third sample entry.\",\n      \"date\": \"2020-07-02\",\n      \"time\": \"09:00\",\n      \"tags\": [],\n      \"starred\": false\n    }\n  ]\n}\n```\n\n### Markdown\n\n``` sh\njrnl --format markdown\n# or\njrnl --format md\n```\n\nMarkdown is a simple markup language that is human readable and can be used to be\nrendered to other formats (html, pdf). `jrnl`'s\n[README](https://github.com/jrnl-org/jrnl/blob/develop/README.md) for example is\nformatted in markdown, then Github adds some formatting to make it look nice.\n\nThe markdown format groups entries by date (first by year, then by month), and adds\nheader markings as needed (e.g. `#`, `##`, etc). If you already have markdown header\nmarkings in your journal, they will be incremented as necessary to make them fit under\nthese new headers (i.e. `#` will become `##`).\n\nThis format can be very useful, for example, to export a journal to a program that\nconverts markdown to html to make a website or a blog from your journal.\n\n**Example output**:\n``` markdown\n# 2020\n\n## June\n\n### 2020-06-28 18:22 This is the first sample entry\n\nThis is the sample body text of the first sample entry.\n\n## July\n\n### 2020-07-01 20:00 This is the second sample entry\n\nThis is the sample body text of the second sample entry, but this one has a @tag.\n\n### 2020-07-02 09:00 This is the third sample entry\n\nThis is the sample body text of the third sample entry.\n```\n\n### Plain Text\n\n``` sh\njrnl --format text\n# or\njrnl --format txt\n```\n\nThis outputs your journal in the same plain-text format that `jrnl` uses to store your\njournal on disk. This format is particularly useful for importing and exporting journals\nwithin `jrnl`.\n\nYou can use it, for example, to move entries from one journal to another, or to create a\nnew journal with search results from another journal.\n\n**Example output**:\n``` sh\n[2020-06-28 18:22] This is the first sample entry\nThis is the sample body text of the first sample entry.\n\n[2020-07-01 20:00] This is the second sample entry\nThis is the sample body text of the second sample entry, but this one has a @tag.\n\n[2020-07-02 09:00] This is the third sample entry\nThis is the sample body text of the third sample entry.\n```\n\n### XML\n``` sh\njrnl --format xml\n```\n\nThis outputs your journal into XML format. XML is a commonly used data format and is\nsupported by many programs and programming languages.\n\n**Example output**:\n``` xml\n<?xml version=\"1.0\" ?>\n<journal>\n        <entries>\n                <entry date=\"2020-06-28T18:22:00\" starred=\"\">This is the first sample entry This is the sample body text of the first sample entry.</entry>\n                <entry date=\"2020-07-01T20:00:00\" starred=\"\">\n                        <tag name=\"@tag\"/>\n                        This is the second sample entry This is the sample body text of the second sample entry, but this one has a @tag.\n                </entry>\n                <entry date=\"2020-07-02T09:00:00\" starred=\"\">*This is the third sample entry, and is starred This is the sample body text of the third sample entry.</entry>\n        </entries>\n        <tags>\n                <tag name=\"@tag\">1</tag>\n        </tags>\n</journal>\n```\n\n### YAML\n``` sh\njrnl --format yaml --file 'my_directory/'\n```\n\nThis outputs your journal into YAML format. YAML is a commonly used data format and is\nsupported by many programs and programming languages. [Exporting to directories](#exporting-to-directories) is the\nonly supported YAML export option and each entry will be written to a separate file.\n\n**Example file**:\n``` yaml\ntitle: This is the second sample entry\ndate: 2020-07-01 20:00\nstarred: False\ntags: tag\n\nThis is the sample body text of the second sample entry, but this one has a @tag.\n```\n\n## Report formats\nSince formats use your journal data and display it in different ways, they can also be\nused to create reports.\n\n### Tags\n\n``` sh\njrnl --format tags\n# or\njrnl --tags\n```\n\nThis format is a simple example of how formats can be used to create reports. It\ndisplays each tag, and a count of how many entries in which tag appears in your journal\n(or in the search results), sorted by most frequent.\n\nExample output:\n``` sh\n@one                 : 32\n@two                 : 17\n@three               : 4\n```\n\n## Options\n\n### Exporting with `--file`\n\nExample: `jrnl --format json --file /some/path/to/a/file.txt`\n\nBy default, `jrnl` will output entries to your terminal. But if you provide `--file`\nalong with a filename, the same output that would have been to your terminal will be\nwritten to the file instead. This is the same as piping the output to a file.\n\nSo, in bash for example, the following two statements are equivalent:\n\n``` sh\njrnl --format json --file myjournal.json\n```\n\n``` sh\njrnl --format json > myjournal.json\n```\n\n#### Exporting to directories\n\nIf the `--file` argument is a directory, jrnl will export each entry into an individual file:\n\n``` sh\njrnl --format yaml --file my_entries/\n```\n\nThe contents of `my_entries/` will then look like this:\n\n``` output\nmy_entries/\n|- 2013_06_03_a-beautiful-day.yaml\n|- 2013_06_07_dinner-with-gabriel.yaml\n|- ...\n```\n"
  },
  {
    "path": "docs/installation.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Getting started\n\n## Installation\n\nThe easiest way to install `jrnl` is using\n[pipx](https://pipx.pypa.io/stable/installation/)\nwith [Python](https://www.python.org/) 3.10+:\n\n``` sh\npipx install jrnl\n```\n\n!!! tip\n     Do not use `sudo` while installing `jrnl`. This may lead to path issues.\n\nThe first time you run `jrnl` you will be asked where your journal file\nshould be created and whether you wish to encrypt it.\n\n## Quickstart\n\nTo make a new entry, just type\n\n``` text\njrnl yesterday: Called in sick. Used the time to clean, and spent 4h on writing my book.\n```\n\nand hit return. `yesterday:` will be interpreted as a time stamp.\nEverything until the first sentence mark (`.?!:`) will be interpreted as\nthe title, the rest as the body. In your journal file, the result will\nlook like this:\n\n``` output\n2012-03-29 09:00 Called in sick.\nUsed the time to clean the house and spent 4h on writing my book.\n```\n\nIf you just call `jrnl`, you will be prompted to compose your entry -\nbut you can also [configure](advanced.md) *jrnl* to use your external editor.\n"
  },
  {
    "path": "docs/journal-types.md",
    "content": "<!--\r\nCopyright © 2012-2023 jrnl contributors\r\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\r\n-->\r\n\r\n# Journal Types\r\n`jrnl` can store your journal in a few different ways:\r\n\r\n - a single text file (encrypted or otherwise)\r\n - a folder structure organized by date containing unencrypted text files\r\n - the DayOne Classic format\r\n\r\nThere is no need to specify what type of journal you'd like to use. Instead,\r\n`jrnl` will automatically detect the journal type based on whether you're\r\nreferencing a file or a folder in your [config file](advanced.md),\r\nand if it's a folder, whether or not DayOne Classic content exists in it.\r\n\r\n## Single File\r\nThe single file format is the most flexible, as it can be [encrypted](encryption.md).\r\nTo use it, enter any path that is a file or does not already exist. You can\r\nuse any extension. `jrnl` will automatically create the file when you save\r\nyour first entry.\r\n\r\n## Folder\r\nThe folder journal format organizes your entries into subfolders for the year\r\nand month and `.txt` files for each day. If there are multiple entries in a day,\r\nthey all appear in the same `.txt` file.\r\n\r\nThe directory tree structure is in this format: `YYYY/MM/DD.txt`. For instance, if\r\nyou have an entry on May 5th, 2021 in a folder journal at `~/folderjournal`, it will\r\nbe located in: `~/folderjournal/2021/05/05.txt`\r\n\r\n!!! note\r\nCreating a new folder journal can be done in two ways:\r\n\r\n* Create a folder with the name of the journal before running `jrnl`. Otherwise, when you run `jrnl` for the first time, it will assume that you are creating a single file journal instead, and it will create a file at that path.\r\n* Create a new journal in your [config_file](advanced.md) and end the path with a ``/`` (on a POSIX system like Linux or MacOSX) or a ``\\`` (on a Windows system). The folder will be created automatically if it doesn't exist.\r\n\r\n!!! note\r\nFolder journals can't be encrypted.\r\n\r\n## Day One Classic\r\n`jrnl` supports the original data format used by DayOne. It's similar to the folder\r\njournal format, except it's identified by either of these characteristics:\r\n\r\n* the folder has a `.dayone` extension\r\n* the folder has a subfolder named `entries`\r\n\r\nThis is not to be confused with the DayOne 2.0 format, [which is very different](https://help.dayoneapp.com/en/articles/1187337-day-one-classic-is-retired).\r\n\r\n!!! note\r\nDayOne Classic journals can't be encrypted.\r\n\r\n## Changing your journal type\r\nYou can't simply modify a journal's configuration to change its type. Instead,\r\ndefine a new journal as the type you'd like, and use\r\n[piping](https://en.wikipedia.org/wiki/Redirection_(computing)#Piping)\r\nto export your old journal as `txt` to an import command on your new journal.\r\n\r\nFor instance, if you have a `projects` journal you would like to import into\r\na `new` journal, you would run the following after setting up the configuration\r\nfor your `new` journal:\r\n```\r\njrnl projects --format txt | jrnl new --import\r\n```\r\n"
  },
  {
    "path": "docs/overview.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Overview\n\n`jrnl` is a simple journal application for the command line.\n\nYou can use it to easily create, search, and view journal entries. Journals are\nstored as human-readable plain text, and can also be encrypted using [AES\nencryption](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard).\n\n`jrnl` has most of the features you need, and few of the ones you don't.\n\n## Plain Text\n\n`jrnl` stores each journal in plain text. You can store `jrnl` files anywhere,\nincluding in shared folders to keep them synchronized between devices. Journal\nfiles are compact (thousands of entries take up less than 1 MiB) and can be read\nby almost any electronic device, now and for the foreseeable future.\n\n## Tags\n\nTo make it easier to find entries later, `jrnl` includes support for inline tags\n(the default tag symbol is `@`). You can find and filter entries by using tags\nalong with other search criteria.\n\n## Support for Multiple Journals\n  \n`jrnl` includes support for the creation of multiple journals, each of which\ncan be stored as a single file or as a set of files. Entries are automatically\ntimestamped in a human-readable format that makes it easy to view multiple\nentries at a time. `jrnl` can easily find the entries you want so that you can\nread them or edit them.\n\n## Support for External Editors\n\n`jrnl` plays nicely with your favorite text editor. You may prefer to write\njournal entries in an editor. Or you may want to make changes that require a\nmore comprehensive application. `jrnl` can filter specific entries and pass them\nto the [external editor](./external-editors.md) of your choice.\n\n## Encryption\n  \n`jrnl` includes support for [AES\nencryption](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard). See the\n[encryption page](./encryption.md) for more information.\n\n## Import and Export\n\n`jrnl` makes it easy to import entries from other sources. Existing entries can\nbe exported in a variety of [formats](./formats.md).\n\n## Multi-Platform Support\n\n`jrnl` is compatible with most operating systems. You can [download](./installation.md) it using one\nof a variety of package managers, or you can build from source.\n\n## Open-Source\n\n`jrnl` is written in [Python](https://www.python.org) and maintained by a\n[friendly community](https://github.com/jrnl-org/jrnl) of open-source software\nenthusiasts.\n"
  },
  {
    "path": "docs/privacy-and-security.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Privacy and Security\n\n`jrnl` is designed with privacy and security in mind, but like any other\nprogram there are some limitations to be aware of.\n\n## Password strength\n\n`jrnl` doesn't enforce password strength requirements. Short or commonly-used\npasswords can be easily circumvented by someone with basic security skills\nto access to your encrypted `jrnl` file.\n\n## Plausible deniability\n\nYou may be able to hide the contents of your journal behind a layer of encryption,\nbut if someone has access to your configuration file, then they can figure out that\nyou have a journal, where that journal file is, and when you last edited it.\nWith a sufficient power imbalance, someone may be able to force you to unencrypt\nit through non-technical means.\n\n## Spying\n\nWhile `jrnl` can protect against unauthorized access to your journal entries while\nit isn't open, it cannot protect you against an unsafe computer/location.\nFor example:\n\n- Someone installs a keylogger, tracking what you type into your journal.\n- Someone watches your screen while you write your entry.\n- Someone installs a backdoor into `jrnl` or poisons your journal into revealing your entries.\n\n## Saved Passwords\n\nWhen creating an encrypted journal, you'll be prompted as to whether or not you\nwant to \"store the password in your keychain.\" This keychain is accessed using\nthe [Python keyring library](https://pypi.org/project/keyring/), which has different\nbehavior depending on your operating system.\n\nIn Windows, the keychain is the Windows Credential Manager (WCM), which can't be locked\nand can be accessed by any other application running under your username. If this is\na concern for you, you may not want to store your password.\n\n## Shell history\n\nSince you can enter entries from the command line, any tool that logs command\nline actions is a potential security risk. See below for how to deal with this\nproblem in various shells.\n\n### bash\n\nYou can disable history logging for jrnl by adding this line into your\n`~/.bashrc` file:\n\n``` sh\nHISTIGNORE=\"$HISTIGNORE:jrnl *\"\n```\n\nTo delete existing `jrnl` commands from `bash` history, simply delete them from\nyour bash history file. The default location of this file is `~/.bash_history`,\nbut you can run `echo \"$HISTFILE\"` to find it if needed.  Also, you can run\n`history -c` to delete all commands from your history.\n\n### zsh\n\nYou can disable history logging for jrnl by adding this to your `~/.zshrc`\nfile:\n\n``` sh\nsetopt HIST_IGNORE_SPACE\nalias jrnl=\" jrnl\"\n```\n\nTo delete existing `jrnl` commands from `zsh` history, simply remove them from\nyour zsh history file. The default location of this file is `~/.zsh_history`,\nbut you can run `echo \"$HISTFILE\"` to find it if needed. Also, you can run\n`history -c` to delete all commands from your history.\n\n### fish\n\nBy default `fish` will not log any command that starts with a space. If you\nwant to always run jrnl with a space before it, then you can add this to your\n`~/.config/fish/config.fish` file:\n\n``` sh\nabbr --add jrnl \" jrnl\"\n```\n\nTo delete existing jrnl commands from `fish` history, run `history delete --prefix 'jrnl '`.\n\n### Windows Command Prompt\n\nWindows doesn't log history to disk, but it does keep it in your command prompt\nsession. Close the command prompt or press `Alt`+`F7` to clear your history\nafter journaling.\n\n## Files in transit from editor to jrnl\n\nWhen creating or editing an entry, `jrnl` uses a unencrypted temporary file on\ndisk in order to give your editor access to your journal. After you close your\neditor, `jrnl` then deletes this temporary file.\n\nSo, if you have saved a journal entry but haven't closed your editor yet, the\nunencrypted temporary remains on your disk. If your computer were to shut off\nduring this time, or the `jrnl` process were killed unexpectedly, then the\nunencrypted temporary file will remain on your disk. You can mitigate this\nissue by only saving with your editor right before closing it. You can also\nmanually delete these files from your temporary folder. By default, they\nare named `jrnl*.jrnl`, but if you use a\n[template](reference-config-file.md#template), they will have the same\nextension as the template.\n\n## Editor history\n\nSome editors keep usage history stored on disk for future use. This can be a\nsecurity risk in the sense that sensitive information can leak via recent\nsearch patterns or editor commands.\n\n### Visual Studio Code\n\nVisual Studio Code stores the contents of saved files to allow you to restore or\nreview the contents later. You can disable this feature for all files by unchecking\nthe `workbench.localHistory.enabled` setting in the\n[Settings editor](https://code.visualstudio.com/docs/getstarted/settings#_settings-editor).\n\nAlternatively, you can disable this feature for specific files by configuring a\n[pattern](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)\nin the `workbench.localHistory.exclude` setting. To exclude unencrypted temporary files generated\nby `jrnl`, you can set the `**/jrnl*.jrnl` (unless you are using a\n[template](reference-config-file.md#template)) pattern for the `workbench.localHistory.exclude` setting\nin the [Settings editor](https://code.visualstudio.com/docs/getstarted/settings#_settings-editor).\n\n!!! note\n    On Windows, the history location is typically found at\n    `%APPDATA%\\Code\\User\\History`.\n\nVisual Studio Code also creates a copy of all unsaved files that are open.\nIt stores these copies in a backup location that's automatically cleaned when\nyou save the file. However, if your computer shuts off before you save the file,\nor the Visual Studio Code process stops unexpectedly, then an unencrypted\ntemporary file may remain on your disk. You can manually delete these files\nfrom the backup location.\n\n!!! note\n    On Windows, the backup location is typically found at\n    `%APPDATA%\\Code\\Backups`.\n\n### Vim\n\nVim stores progress data in a so called Viminfo file located at `~/.viminfo`\nwhich contains all sorts of user data including command line history, search\nstring history, search/substitute patterns, contents of register etc. Also to\nbe able to recover opened files after an unexpected application close Vim uses\nswap files.\n\nThese options as well as other leaky features can be disabled by setting the\n`editor` key in the Jrnl settings like this:\n\n``` yaml\neditor: \"vim -c 'set viminfo= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure'\"\n```\n\nTo disable all plugins and custom configurations and start Vim with the default\nconfiguration `-u NONE` can be passed on the command line as well. This will\nensure that any rogue plugins or other difficult to catch information leaks are\neliminated. The downside to this is that the editor experience will decrease\nquite a bit.\n\nTo instead let Vim automatically detect when a Jrnl file is being edited an\nautocommand can be used. Place this in your `~/.vimrc`:\n\n``` vim\nautocmd BufNewFile,BufReadPre *.jrnl setlocal viminfo= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure\n```\n\n!!! note\n    If you're using a [template](reference-config-file.md#template), you will\n    have to use the template's file extension instead of `.jrnl`.\n\nSee `:h <option>` in Vim for more information about the options mentioned.\n\n### Neovim\n\nNeovim strives to be mostly compatible with Vim and has therefore similar\nfunctionality as Vim. One difference in Neovim is that the Viminfo file is\ninstead called the ShaDa (\"shared data\") file which resides in\n`~/.local/state/nvim` (`~/.local/share/nvim` pre Neovim v0.8.0). The ShaDa file\ncan be disabled in the same way as for Vim.\n\n``` yaml\neditor: \"nvim -c 'set shada= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure'\"\n```\n\n`-u NONE` can be passed here as well to start a session with the default configs.\n\nAs for Vim above we can create an autocommand in Vimscript:\n\n``` vim\nautocmd BufNewFile,BufReadPre *.jrnl setlocal shada= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure\n```\n\nor the same but in Lua:\n\n``` lua\nvim.api.nvim_create_autocmd( {\"BufNewFile\",\"BufReadPre\" }, {\n  group = vim.api.nvim_create_augroup(\"PrivateJrnl\", {}),\n  pattern = \"*.jrnl\",\n  callback = function()\n    vim.o.shada = \"\"\n    vim.o.swapfile = false\n    vim.o.undofile = false\n    vim.o.backup = false\n    vim.o.writebackup = false\n    vim.o.shelltemp = false\n    vim.o.history = 0\n    vim.o.modeline = false\n    vim.o.secure = true\n  end,\n})\n```\n\n!!! note\n    If you're using a [template](reference-config-file.md#template), you will\n    have to use the template's file extension instead of `.jrnl`.\n\nPlease see `:h <option>` in Neovim for more information about the options mentioned.\n\n## Notice any other risks?\n\nPlease let the maintainers know by [filing an issue on GitHub](https://github.com/jrnl-org/jrnl/issues).\n"
  },
  {
    "path": "docs/reference-command-line.md",
    "content": "<!--\r\nCopyright © 2012-2023 jrnl contributors\r\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\r\n-->\r\n\r\n# Command Line Reference\r\n\r\n## Synopsis\r\n```\r\nusage: jrnl [--debug] [--help] [--version] [--list] [--encrypt] [--decrypt]\r\n            [--import] [-on DATE] [-today-in-history] [-month DATE]\r\n            [-day DATE] [-year DATE] [-from DATE] [-to DATE] [-contains TEXT]\r\n            [-and] [-starred] [-n [NUMBER]] [-not [TAG]] [--edit] [--delete]\r\n            [--format TYPE] [--tags] [--short]\r\n            [--config-override CONFIG_KEY CONFIG_VALUE]\r\n            [--config-file CONFIG_FILE_PATH]\r\n            [[...]]\r\n```\r\n\r\n## Standalone Commands\r\n\r\nThese commands will exit after they complete. You may only run one at a time.\r\n\r\n### --help\r\nShow a help message.\r\n\r\n### --version\r\nPrint version and license information.\r\n\r\n### --list\r\nList the config file location, all configured journals, and their locations.\r\n\r\n### --encrypt\r\nEncrypt a journal. See [encryption](encryption.md) for more information.\r\n\r\n### --decrypt\r\nDecrypt a journal. See [encryption](encryption.md) for more information.\r\n\r\n\r\n### --import\r\nImport entries from another journal. If any entries have the exact same content\r\nand timestamp, they will be deduplicated.\r\n\r\nOptional parameters:\r\n```sh\r\n--file FILENAME\r\n```\r\nSpecify a file to import. If not provided, `jrnl` will use STDIN as the data source.\r\n\r\n```sh\r\n--format TYPE\r\n```\r\nSpecify the format of the file that is being imported. Defaults to the same data\r\nstorage method that jrnl uses. See [formats](formats.md) for more information.\r\n\r\n## Writing new entries\r\nSee [Basic Usage](usage.md).\r\n\r\n## Searching\r\n\r\nTo find entries from your journal, use any combination of the below filters.\r\nOnly entries that match all the filters will be displayed.\r\n\r\nWhen specifying dates, you can use the same kinds of dates you use for new\r\nentries, such as `yesterday`, `today`, `Tuesday`, or `2021-08-01`.\r\n\r\n| Search Argument | Description |\r\n| --- | --- |\r\n| -on DATE | Show entries on this date |\r\n| -today-in-history | Show entries of today over the years |\r\n| -month DATE | Show entries on this month of any year |\r\n| -day DATE | Show entries on this day of any month |\r\n| -year DATE | Show entries of a specific year |\r\n| -from DATE | Show entries after, or on, this date |\r\n| -to DATE | Show entries before, or on, this date (alias: -until) |\r\n| -contains TEXT | Show entries containing specific text (put quotes around text with spaces) |\r\n| -and | Show only entries that match all conditions, like saying \"x AND y\" (default: OR) |\r\n| -starred | Show only starred entries (marked with *) |\r\n| -tagged | Show only tagged entries (marked with the [configured tagsymbols](reference-config-file.md#tagsymbols)) |\r\n| -n [NUMBER] | Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same effect) |\r\n| -not [TAG] | Exclude entries with this tag |\r\n| -not -starred | Exclude entries that are starred |\r\n| -not -tagged | Exclude entries that are tagged |\r\n\r\n## Searching Options\r\nThese help you do various tasks with the selected entries from your search.\r\nIf used on their own (with no search), they will act on your entire journal.\r\n\r\n### --edit\r\nOpens the selected entries in your configured editor. It will fail if the\r\n`editor` key is not set in your config file.\r\n\r\nOnce you begin editing, you can add multiple entries and delete entries\r\nby modifying the text in your editor. When your editor closes, jrnl reads\r\nthe temporary file you were editing and makes the changes to your journal.\r\n\r\n### --delete\r\nInteractively deletes selected entries. You'll be asked to confirm deletion of\r\neach entry.\r\n\r\n### --change-time DATE\r\nInteractively changes the time of the selected entries to the date specified,\r\nor to right now if no date is specified. You'll be asked to confirm each entry,\r\nunless you are using this with `--edit` on a single entry.\r\n\r\n### --format TYPE\r\nDisplay selected entries in an alternate format. See [formats](formats.md).\r\n\r\n#### Optional parameters\r\n```sh\r\n--file FILENAME\r\n```\r\nWrite output to file instead of STDOUT. In most shells, the\r\nsame effect can be achieved using `>`.\r\n\r\n### --tags\r\n\r\nAlias for '--format tags'. Returns a list of all tags and the number of times\r\nthey occur within the searched entries. If there are no tags found, `jrnl` will output a message saying so.\r\n\r\n### --short\r\nOnly shows the date and titles of the searched entries.\r\n\r\n## Configuration arguments\r\n\r\n### --config-override CONFIG_KEY CONFIG_VALUE\r\n\r\nOverride configured key-value pair with CONFIG_KV_PAIR for this command invocation only. To access config keys that aren't at the top level, separate the keys with a dot, such as `colors.title` to access the `title` key within the `colors` key. Read [advanced usage](./advanced.md) for examples.\r\n\r\n### --config-file CONFIG_FILE_PATH\r\n\r\nUse the config file at CONFIG_FILE_PATH for this command invocation only.\r\nRead [advanced usage](./advanced.md) for examples.\r\n\r\n## Other Arguments\r\n\r\n### --debug\r\nPrints information useful for troubleshooting while `jrnl` executes.\r\n\r\n### --diagnostic\r\n\r\nPrints diagnostic information useful for [reporting issues](https://github.com/jrnl-org/jrnl/issues).\r\n"
  },
  {
    "path": "docs/reference-config-file.md",
    "content": "<!--\r\nCopyright © 2012-2023 jrnl contributors\r\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\r\n-->\r\n\r\n# Configuration File Reference\r\n\r\n`jrnl` stores its information in a YAML configuration file.\r\n\r\n!!! note\r\n    Backup your journal and config file before editing. Changes to the config file\r\n    can have destructive effects on your journal!\r\n\r\n## Config location\r\nYou can find your configuration file location by running:\r\n`jrnl --list`\r\n\r\nBy default, the configuration file is `~/.config/jrnl/jrnl.yaml`.\r\nIf you have the `XDG_CONFIG_HOME` variable set, the configuration\r\nfile will be saved as `$XDG_CONFIG_HOME/jrnl/jrnl.yaml`.\r\n\r\n!!! note\r\n    On Windows, the configuration file is typically found at\r\n    `%USERPROFILE%\\.config\\jrnl\\jrnl.yaml`.\r\n\r\n\r\n## Config format\r\nThe configuration file is a [YAML](https://yaml.org/) file and can be edited with\r\na text editor.\r\n\r\n## Config keys\r\n\r\n### journals\r\n\r\nDescribes each journal used by `jrnl`. Each indented key after this key is\r\nthe name of a journal.\r\n\r\nIf a journal key has a value, that value will be interpreted as the path\r\nto the journal. Otherwise, the journal needs the additional indented key\r\n`journal` to specify its path.\r\n\r\nExample:\r\n\r\n```yaml\r\njournals:\r\n  default:\r\n    journal: /home/user/journals\r\n```\r\n\r\nAll keys below can be specified for each journal at the same level as the\r\n`journal` key. If a key conflicts with a top-level key, the journal-specific\r\nkey will be used instead.\r\n\r\n### editor\r\nIf set, executes this command to launch an external editor for\r\nwriting and editing your entries. The path to a temporary file\r\nis passed after it, and `jrnl` processes the file once\r\nthe editor returns control to `jrnl`.\r\n\r\nSome editors require special options to work properly, since they must be\r\nblocking processes to work with `jrnl`. See [External Editors](external-editors.md)\r\nfor details.\r\n\r\n### encrypt\r\nIf `true`, encrypts your journal using AES. Do not change this\r\nvalue for journals that already have data in them.\r\n\r\n### template\r\nThe path to a text file to use as a template for new entries. Only works when you\r\nhave the `editor` field configured. If you use a template, the editor's\r\n[temporary files](privacy-and-security.md#files-in-transit-from-editor-to-jrnl)\r\nwill have the same extension as the template.\r\n\r\n### tagsymbols\r\nSymbols to be interpreted as tags.\r\n\r\n!!! note\r\n    Although it seems intuitive to use the `#`\r\n    character for tags, there's a drawback: on most shells, this is\r\n    interpreted as a meta-character starting a comment. This means that if\r\n    you type\r\n\r\n    > `jrnl Implemented endless scrolling on the #frontend of our website.`\r\n\r\n    your bash will chop off everything after the `#` before passing it to\r\n      `jrnl`. To avoid this, wrap your input into quotation marks like\r\n      this:\r\n\r\n    > `jrnl \"Implemented endless scrolling on the #frontend of our website.\"`\r\n\r\n  Or use the built-in prompt or an external editor to compose your\r\n  entries.\r\n\r\n### default_hour and default_minute\r\nEntries will be created at this time if you supply a date but no specific time (for example, `last thursday`).\r\n\r\n### timeformat\r\nDefines how to format the timestamps as they are stored in your journal.\r\nSee the [python docs](http://docs.python.org/library/time.html#time.strftime) for reference.\r\n\r\nDo not change this for an existing journal, since that might lead\r\nto data loss.\r\n\r\n!!! note\r\n    `jrnl` doesn't support the `%z` or `%Z` time zone identifiers.\r\n\r\n### highlight\r\nIf `true`, tags will be highlighted in cyan.\r\n\r\n### linewrap\r\nControls the width of the output. Set to `false` if you don't want to\r\nwrap long lines. Set to `auto` to let `jrnl` automatically determine\r\nthe terminal width.\r\n\r\n### colors\r\nA dictionary that controls the colors used to display journal entries.\r\nIt has four subkeys, which are: `body`, `date`, `tags`, and `title`.\r\n\r\nCurrent valid values are: `BLACK`, `RED`, `GREEN`, `YELLOW`, `BLUE`,\r\n`MAGENTA`, `CYAN`, `WHITE`, and `NONE`.\r\n\r\n`colorama.Fore` is used for colorization, and you can find the [docs here](https://github.com/tartley/colorama#colored-output).\r\n\r\nTo disable colored output, set the value to `NONE`.\r\n\r\n### display_format\r\nSpecifies formatter to use by default. See [formats](formats.md).\r\n\r\n### version\r\n`jrnl` automatically updates this field to the version that it is running.\r\nThere is no need to change this field manually.\r\n\r\n"
  },
  {
    "path": "docs/tips-and-tricks.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Tips and Tricks\n\nThis page contains tips and tricks for using `jrnl`, often in conjunction\nwith other tools, including external editors.\n\n## Co-occurrence of tags\n\nIf I want to find out how often I mentioned my flatmates Alberto and\nMelo in the same entry, I run\n\n```sh\njrnl @alberto --tags | grep @melo\n```\n\nAnd will get something like `@melo: 9`, meaning there are 9 entries\nwhere both `@alberto` and `@melo` are tagged. How does this work? First,\n`jrnl @alberto` will filter the journal to only entries containing the\ntag `@alberto`, and then the `--tags` option will print out how often\neach tag occurred in this filtered journal. Finally, we pipe this to\n`grep` which will only display the line containing `@melo`.\n\n## Combining filters\n\nYou can do things like\n\n```sh\njrnl @fixed -starred -n 10 -to \"jan 2013\" --short\n```\n\nTo get a short summary of the 10 most recent, favourite entries before\nJanuary 1, 2013 that are tagged with `@fixed`.\n\n## Statistics\n\nHow much did I write last year?\n\n```sh\njrnl -from \"jan 1 2013\" -to \"dec 31 2013\" | wc -w\n```\n\nWill give you the number of words you wrote in 2013. How long is my\naverage entry?\n\n```sh\nexpr $(jrnl --export text | wc -w) / $(jrnl --short | wc -l)\n```\n\nThis will first get the total number of words in the journal and divide\nit by the number of entries (this works because `jrnl --short` will\nprint exactly one line per entry).\n\n## Importing older files\n\nIf you want to import a file as an entry to `jrnl`, you can just do `jrnl < entry.ext`. But what if you want the modification date of the file to\nbe the date of the entry in `jrnl`? Try this\n\n```sh\necho `stat -f %Sm -t '%d %b %Y at %H:%M: ' entry.txt` `cat entry.txt` | jrnl\n```\n\nThe first part will format the modification date of `entry.txt`, and\nthen combine it with the contents of the file before piping it to jrnl.\nIf you do that often, consider creating a function in your `.bashrc` or\n`.bash_profile`\n\n```sh\njrnlimport () {\n  echo `stat -f %Sm -t '%d %b %Y at %H:%M: ' $1` `cat $1` | jrnl\n}\n```\n\n## Using Templates\n\n!!! note\n    Templates require an [external editor](./advanced.md) be configured.\n\nTemplates are text files that are used for creating structured journals.\nThere are three ways you can use templates:\n\n### 1. Use the `--template` command line argument and the default $XDG_DATA_HOME/jrnl/templates directory\n\n`$XDG_DATA_HOME/jrnl/templates` is created by default to store your templates! Create a template (like `default.md`) in this directory and pass `--template FILE_IN_DIR`.\n\n```sh\njrnl --template default.md\n```\n\n### 2. Use the `--template` command line argument with a local / absolute path\n\nYou can create a template file with any text. Here is an example:\n\n```sh\n# /tmp/template.txt\nMy Personal Journal\nTitle:\n\nBody:\n```\n\nThen, pass the absolute or relative path to the template file as an argument, and your external\neditor will open and have your template pre-populated.\n\n```sh\njrnl --template /tmp/template.md\n```\n\n### 3. Set a default template file in `jrnl.yaml`\n\nIf you want a template by default, change the value of `template` in the [config file](./reference-config-file.md)\nfrom `false` to the template file's path, wrapped in double quotes:\n\n```sh\n...\ntemplate: \"/path/to/template.txt\"\n...\n```\n\n!!! tip\n    To read your journal entry or to verify the entry saved, you can use this\n    command: `jrnl -n 1` (Check out [Formats](./formats.md) for more options).\n\n```sh\njrnl -n 1\n```\n\n## Prompts on shell reload\n\nIf you'd like to be prompted each time you refresh your shell, you can include\nthis in your `.bash_profile`:\n\n```sh\nfunction log_question()\n{\n   echo $1\n   read\n   jrnl today: ${1}. $REPLY\n}\nlog_question 'What did I achieve today?'\nlog_question 'What did I make progress with?'\n```\n\nWhenever your shell is reloaded, you will be prompted to answer each of the\nquestions in the example above. Each answer will be logged as a separate\njournal entry at the `default_hour` and `default_minute` listed in your\n`jrnl.yaml` [config file](../advanced/#configuration-file).\n\n## Display random entry\n\nYou can use this to select one title at random and then display the whole\nentry. The invocation of `cut` needs to match the format of the timestamp.\nFor timestamps that have a space between data and time components, select\nfields 1 and 2 as shown. For timestamps that have no whitespace, select\nonly field 1.\n\n```sh\njrnl -on \"$(jrnl --short | shuf -n 1 | cut -d' ' -f1,2)\"\n```\n\n\n## Launch a terminal for rapid logging\nYou can use this to launch a terminal that is the `jrnl` stdin prompt so you can start typing away immediately.\n\n```bash\njrnl --config-override editor \"\"\n```\n\nBind this to a keyboard shortcut.\n\nMap `Super+Alt+J` to launch the terminal with `jrnl` prompt\n\n- **xbindkeys**\nIn your `.xbindkeysrc`\n\n```ini\nMod4+Mod1+j\n alacritty -t floating-jrnl -e jrnl --config-override editor \"\",\n```\n\n- **I3 WM** Launch a floating terminal with the `jrnl` prompt\n\n```ini\nbindsym Mod4+Mod1+j exec --no-startup-id alacritty -t floating-jrnl -e jrnl --config-override editor \"\"\nfor_window[title=\"floating *\"] floating enable\n```\n## Visualize Formatted Markdown in the CLI\n\nOut of the box, `jrnl` can output journal entries in Markdown. To visualize it, you can pipe to [mdless](https://github.com/ttscoff/mdless), which is a [less](https://en.wikipedia.org/wiki/Less_(Unix))-like tool that allows you to visualize your Markdown text with formatting and syntax highlighting from the CLI. You can use this in any shell that supports piping.\n\nThe simplest way to visualize your Markdown output with `mdless` is as follows:\n```sh\njrnl --export md | mdless\n```\n\nThis will render your Markdown output in the whole screen.\n\nFortunately, `mdless` has an option that allows you to adjust the screen width by using the `-w` option as follows:\n\n```sh\njrnl --export md | mdless -w 70\n```\n\nIf you want Markdown to be your default display format, you can define this in your config file as follows:\n\n```yaml\ndisplay_format: md\n# or\ndisplay_format: markdown\n```\n\nFor more information on how `jrnl` outputs your entries in Markdown, please visit the [Formats](./formats.md) section.\n\n\n## Jump to end of buffer (with vi)\n\nTo cause vi to jump to the end of the last line of the entry you edit, in your config file set:\n\n```yaml\neditor: vi + -c \"call cursor('.',strwidth(getline('.')))\"\n```\n"
  },
  {
    "path": "docs/usage.md",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n# Basic Usage #\n\n`jrnl` has two modes: **composing** and **viewing**. Whenever you don't enter\nany arguments that start with a dash (`-`) or double-dash (`--`), you're in\ncomposing mode, meaning that you can write your entry on the command line.\n\nWe intentionally break a convention on command line arguments: all arguments\nstarting with a _single dash_ (`-`) will _filter_ your journal before viewing\nit. Filter arguments can be combined arbitrarily. Arguments with a _double dash_\n(`--`) will _control_ how your journal is displayed or exported. Control\narguments are mutually exclusive (i.e., you can only specify one way to display\nor export your journal at a time).\n\nFor a list of commands, enter `jrnl --help`.\n\n## Composing Entries ##\n\nComposing mode is entered by either starting `jrnl` without any arguments --\nwhich will launch an external editor -- or by just writing an entry on the\ncommand line:\n\n```text\njrnl today at 3am: I just met Steve Buscemi in a bar! What a nice guy.\n```\n\n!!! note\n    Most shells contain a certain number of reserved characters, such as `#` and\n    `*`. These characters, as well as unbalanced single or double quotation\n    marks, parentheses, and others, likely will cause problems. Although\n    reserved characters can be escaped using `\\`, this is not ideal for\n    long-form writing. The solution: first enter `jrnl` and hit `return`. You\n    can then enter the text of your journal entry. Alternatively, you can [use\n    an external editor](./advanced.md).\n\nYou can also import an entry directly from a file:\n\n```sh\njrnl < my_entry.txt\n```\n\n### Specifying Date and Time ###\n\nIf you don't specify a date and time (e.g., `jrnl finished writing letter to brother`), `jrnl` will create an entry using the current date and time. For retrospective entries, you can use a timestamp to tell `jrnl` where to put the entry. Timestamps can be entered using a variety of formats. Here are some that work:\n\n- at 6am\n- yesterday\n- last monday\n- sunday at noon\n- 2 march 2012\n- 7 apr\n- 5/20/1998 at 23:42\n- 2020-05-22T15:55-04:00\n\nIf you don't use a timestamp, `jrnl` will create an entry using the current\ntime. If you use a date only (no time), `jrnl` will use the default time\nspecified in your [configuration file](./reference-config-file.md#default_hour-and-default_minute).\nBehind the scenes, `jrnl` reorganizes entries in chronological order.\n\n### Using Tags ###\n\n`jrnl` supports tags. The default tag symbol is `@` (largely because `#` is a\nreserved character). You can specify your own tag symbol in the\n[configuration file](./reference-config-file.md#tagsymbols). To use tags, preface the\ndesired tag with the symbol:\n\n```sh\njrnl Had a wonderful day at the @beach with @Tom and @Anna.\n```\n\nAlthough you can use capitals while tagging an entry, searches by tag are\ncase-insensitive.\n\nThere is no limit to how many tags you can use in an entry.\n\n### Starring Entries ###\n\nTo mark an entry as a favorite, simply \"star\" it using an asterisk (`*`):\n\n```sh\njrnl last sunday *: Best day of my life.\n```\n\nIf you don't want to add a date (i.e., you want the date to be entered as\n_now_), the following options are equivalent:\n\n- `jrnl *: Best day of my life.`\n- `jrnl *Best day of my life.`\n- `jrnl Best day of my life.*`\n\n!!! note\n    Make sure that the asterisk (`*`) is **not** surrounded by whitespaces.\n    `jrnl Best day of my life! *` will not work because the `*` character has a\n    special meaning in most shells.\n\n## Viewing and Searching Entries ##\n\n`jrnl` can display entries in a variety of ways.\n\nTo view all entries, enter:\n```sh\njrnl -to today\n```\n\n`jrnl` provides several filtering commands, prefaced by a single dash (`-`), that\nallow you to find a more specific range of entries. For example,\n\n```sh\njrnl -n 10\n```\n\nlists the ten most recent entries. `jrnl -10` is even more concise and works the\nsame way. If you want to see all of the entries you wrote from the beginning of\nlast year until the end of this past March, you would enter\n\n```sh\njrnl -from \"last year\" -to march\n```\n\nFilter criteria that use more than one word require surrounding quotes (`\"\"`).\n\nTo see entries on a particular date, use `-on`:\n```sh\njrnl -on yesterday\n```\n\n### Text Search ###\n\nThe `-contains` command displays all entries containing the text you enter after it.\nThis may be helpful when you're searching for entries and you can't remember if you\ntagged any words when you wrote them.\n\nYou may realize that you use a word a lot and want to turn it into a tag in all\nof your previous entries.\n\n```sh\njrnl -contains \"dogs\" --edit\n```\n\nopens your external editor so that you can add a tag symbol (`@` by default) to\nall instances of the word \"dogs.\"\n\n### Filtering by Tag ###\n\nYou can filter your journal entries by tag. For example,\n\n```sh\njrnl @pinkie @WorldDomination\n```\n\ndisplays all entries in which either `@pinkie` or `@WorldDomination`\noccurred. Tag filters can be combined with other filters:\n\n```sh\njrnl -n 5 @pinkie -and @WorldDomination\n```\n\ndisplays the last five entries containing _both_ `@pinkie` _and_\n`@worldDomination`. You can change which symbols you'd like to use for tagging\nin the [configuration file](./reference-config-file.md#tagsymbols).\n\n!!! note\n    Entering `jrnl @pinkie @WorldDomination` will display entries in which both\n    tags are present because, although no command line arguments are given, all\n    of the input strings look like tags. `jrnl` will assume you want to filter\n    by tag, rather than create a new entry that consists only of tags.\n\nTo view a list of all tags in the journal, enter:\n\n```sh\njrnl --tags\n```\n\n### Viewing Starred Entries ###\n\nTo display only your favorite (starred) entries, enter\n\n```sh\njrnl -starred\n```\n\n## Editing Entries ##\n\nYou can edit entries after writing them. This is particularly useful when your\njournal file is encrypted. To use this feature, you need to have an external\neditor configured in your [configuration file](./reference-config-file.md#editor). You\ncan also edit only the entries that match specific search criteria. For example,\n\n```sh\njrnl -to 1950 @texas -and @history --edit\n```\n\nopens your external editor displaying all entries tagged with `@texas` and\n`@history` that were written before 1950. After making changes, save and close\nthe file, and only those entries will be modified (and encrypted, if\napplicable).\n\nIf you are using multiple journals, it's easy to edit specific entries from\nspecific journals. Simply prefix the filter string with the name of the journal.\nFor example,\n\n```sh\njrnl work -n 1 --edit\n```\n\nopens the most recent entry in the 'work' journal in your external editor.\n\n## Deleting Entries ##\n\nThe `--delete` command opens an interactive interface for deleting entries. The\ndate and title of each entry in the journal are presented one at a time, and you\ncan choose whether to keep or delete each entry.\n\nIf no filters are specified, `jrnl` will ask you to keep or delete each entry in\nthe entire journal, one by one. If there are a lot of entries in the journal, it\nmay be more efficient to filter entries before passing the `--delete` command.\n\nHere's an example. Say you have a journal into which you've imported the last 12\nyears of blog posts. You use the `@book` tag a lot, and for some reason you want\nto delete some, but not all, of the entries in which you used that tag, but only\nthe ones you wrote at some point in 2004 or earlier. You're not sure which\nentries you want to keep, and you want to look through them before deciding.\nThis is what you might enter:\n\n```sh\njrnl -to 2004 @book --delete\n```\n\n`jrnl` will show you only the relevant entries, and you can choose the ones you\nwant to delete.\n\nYou may want to delete _all_ of the entries containing `@book` that you wrote in\n2004 or earlier. If there are dozens or hundreds, the easiest way would be to\nuse an external editor. Open an editor with the entries you want to delete...\n\n```sh\njrnl -to 2004 @book --edit\n```\n\n...select everything, delete it, save and close, and all of those entries are\nremoved from the journal.\n\n## Listing Journals ##\n\nTo list all of your journals:\n\n```sh\njrnl --list\n```\n\nThe journals displayed correspond to those specified in the `jrnl`\n[configuration file](./reference-config-file.md#journals).\n"
  },
  {
    "path": "docs_theme/assets/colors.css",
    "content": "/*\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n*/\n\n:root {\n  /* For dark bg */\n  --white: #fcfcfc;\n  --off-white: #f4f0ff;\n  --purple: #7e57c2;\n  --light-purple: #cf93e6;\n  --blue: #61aeee;\n  --green: #a6e22e;\n  --orange: #fd971f;\n  --red: #eb5567;\n  --pink: #d57699;\n  --yellow: #e2b93d;\n\n  /* For light bg */\n  --black: #404040;\n  --teal: #2a8068;\n  --dark-blue: #356eb7;\n  --mid-purple: #846392;\n  --bright-purple: #af27ad;\n  --dark-purple: #604385;\n  --darkest-purple: #251A32;\n  --grey: #3b3b4a;\n\n  --black-shadow: #0000001A;\n  --blacker-shadow: #00000059;\n\n  /* Special cases */\n  --terminal: #1b1c2e;\n}\n"
  },
  {
    "path": "docs_theme/assets/highlight.css",
    "content": "/*\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n\nAtom One Dark With support for ReasonML by Gidi Morris, based off work by\nDaniel Gamage\n\nOriginal One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n*/\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  line-height: 1.3em;\n  color: var(--off-white);\n  background: #383e49;\n  border-radius: 5px;\n  font-size: 0.9rem;\n  line-height: 1.3rem;\n}\n.hljs-keyword,\n.hljs-operator {\n  color: var(--pink);\n}\n.hljs-pattern-match {\n  color: var(--pink);\n}\n.hljs-pattern-match .hljs-constructor {\n  color: var(--blue);\n}\n.hljs-function {\n  color: var(--blue);\n}\n.hljs-function .hljs-params {\n  color: var(--green);\n}\n.hljs-function .hljs-params .hljs-typing {\n  color: var(--orange);\n}\n.hljs-module-access .hljs-module {\n  color: var(--purple);\n}\n.hljs-constructor {\n  color: var(--yellow);\n}\n.hljs-constructor .hljs-string {\n  color: var(--green);\n}\n.hljs-comment,\n.hljs-quote {\n  color: var(--light-purple);\n  font-style: italic;\n}\n.hljs-doctag,\n.hljs-formula {\n  color: var(--purple);\n}\n.hljs-section,\n.hljs-name,\n.hljs-selector-tag,\n.hljs-deletion,\n.hljs-subst {\n  color: var(--yellow);\n}\n.hljs-literal {\n  color: var(--blue);\n}\n.hljs-string,\n.hljs-regexp,\n.hljs-addition,\n.hljs-attribute,\n.hljs-meta-string {\n  color: var(--green);\n}\n.hljs-built_in,\n.hljs-class .hljs-title {\n  color: var(--orange);\n}\n.hljs-attr,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-type,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo,\n.hljs-number {\n  color: var(--orange);\n}\n\n.rst-content a tt,\n.rst-content a tt,\n.rst-content a code {\n  color: var(--blue);\n}\n\n.hljs-number,\n.hljs-literal,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-tag .hljs-attr {\n  color: var(--blue);\n}\n\n.hljs-tag {\n  color: var(--pink)\n}\n\n.hljs-symbol,\n.hljs-bullet,\n.hljs-link,\n.hljs-meta,\n.hljs-selector-id,\n.hljs-title {\n  color: var(--blue);\n}\n.hljs-emphasis {\n  font-style: italic;\n}\n.hljs-strong {\n  font-weight: bold;\n}\n.hljs-link {\n  text-decoration: underline;\n}\n\n.rst-content .note .admonition-title {\n  background: var(--dark-blue);\n}\n\n.rst-content .note.admonition {\n  background: var(--light-blue);\n}\n\n.rst-content .tip .admonition-title {\n  background: var(--teal);\n}\n\n.rst-content .tip .admonition {\n  background: var(--light-blue);\n}\n\n/* hack to bypass a11y issue with conflicting highlight.css files */\ncode.language-xml span.hljs-meta span.hljs-string {\n  color: var(--green) !important;\n}\n\n"
  },
  {
    "path": "docs_theme/assets/index.css",
    "content": "/*\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n*/\n\n/* reset */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:\"\\201C\" \"\\201D\" \"\\2018\" \"\\2019\"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=\"button\"],input[type=\"reset\"],input[type=\"submit\"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=\"checkbox\"],input[type=\"radio\"]{box-sizing:border-box;padding:0}input[type=\"search\"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=\"search\"]::-webkit-search-cancel-button,input[type=\"search\"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}\n\nbody {\n  background-color: var(--white);\n  font-family: \"Open Sans\", \"Helvetica Neue\", sans-serif;\n  font-weight: 300;\n}\n\n.icon {\n  background-image: url(\"../img/sprites.svg\");\n  width: 32px;\n  height: 32px;\n  display: inline-block;\n  font-size: 40px;\n  background-size: 200px 80px;\n}\n\nh3 {\n  font-weight: 400;\n}\n\n.icon.secure {\n  background-position: 0em 0em;\n}\n\n.icon.future {\n  background-position: -1em 0em;\n}\n\n.icon.search {\n  background-position: -2em 0em;\n}\n\n.icon.nli {\n  background-position: -3em 0em;\n}\n\n.icon.share {\n  background-position: 0em -1em;\n}\n\n.icon.sync {\n  background-position: 0 -1em;\n}\n\n.icon.dayone {\n  background-position: -1em -1em;\n}\n\n.icon.github {\n  background-position: -2em -1em;\n}\n\n.icon.search {\n  background-position: -2em 0;\n}\n\n.icon.folders {\n  background-position: -3em -1em;\n}\n\n.icon.twitter {\n  background-position: -4em -1em;\n}\n\nheader {\n  background-color: var(--mid-purple);\n  background-image: linear-gradient(211deg, var(--mid-purple) 0%, var(--dark-purple) 100%);\n  color: var(--white);\n  border: 0px solid transparent;\n  display: relative;\n  padding-top: 150px;\n  overflow: visible;\n}\n\n#terminal {\n  background: var(--terminal);\n  max-width: 520px;\n  box-shadow: 0 -2px 16px 0 var(--black-shadow);\n  border-radius: 6px;\n  min-height: 120px;\n  margin: 0px auto;\n  position: relative;\n  transform: translateY(75px);\n  color: var(--off-white);\n  font-family: \"Monaco\", \"Courier New\";\n  font-size: 18px;\n  padding: 45px 20px 0px 20px;\n  line-height: 165%;\n}\n\n#terminal b {\n  font-weight: normal;\n  color: var(--off-white);\n}\n\n#terminal i {\n  font-style: normal;\n  color: var(--light-purple);\n}\n\n#terminal:before {\n  content: \"\";\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  display: inline-block;\n  width: 15px;\n  height: 15px;\n  border-radius: 50%;\n  background: var(--grey);\n  box-shadow: 25px 0 0 var(--grey), 50px 0 0 var(--grey);\n}\n\n#typed:before {\n  content: \"$ \";\n  color: var(--mid-purple);\n}\n\n#twitter {\n  display: block;\n  position: absolute;\n  text-decoration: none;\n  top: 20px;\n  right: 20px;\n  border: 1px solid var(--white);\n  padding: 5px 10px;\n  color: var(--white);\n  border-radius: 3px;\n}\n\n#twitter .icon {\n  transform: scale(0.5);\n  vertical-align: -18%;\n  margin: 0;\n  padding: 0;\n}\n\n#twitter:hover,\n#twitter:active {\n  text-decoration: none;\n  box-shadow: 0 2px 25px 0 var(--blacker-shadow);\n  transition: all .5s ease;\n}\n\n#title {\n  max-width: 630px;\n  margin: 0 auto;\n  padding: 0px 20px;\n}\n\n#prompt {\n  max-width: 700px;\n  margin: 25px auto 100px auto;\n  padding: 0px 20px;\n}\n\nheader img {\n  float: left;\n  margin-right: 30px;\n}\n\nh1 {\n  color: var(--white);\n  font-weight: 300;\n}\n\na,\na:visited {\n  color: var(--dark-purple);\n}\n\na:hover {\n  color: var(--bright-purple);\n}\n\nnav {\n  text-align: center;\n}\n\nnav a#twitter-nav {\n  display: none;\n}\n\nnav a, nav a:visited {\n  color: var(--dark-purple);\n  font-size: 20px;\n  line-height: 2.5em;\n  margin: 0 40px;\n  font-weight: 400;\n  text-decoration: none;\n}\n\nnav a:hover,\nnav a:visited:hover {\n  color: var(--bright-purple);\n  text-decoration: underline;\n}\n\n\nnav a.cta {\n  display: inline-block;\n  color: var(--white);\n  background-color: var(--mid-purple);\n  background-image: linear-gradient(259deg, var(--mid-purple) 0%, var(--dark-purple) 100%);\n  box-shadow: 0 2px 8px 0 var(--blacker-shadow);\n  border-radius: 50px;\n  padding: 0 2em;\n  white-space: nowrap;\n  transition: all 0.1s ease;\n  text-decoration: none;\n}\n\nnav a.cta:hover {\n  text-decoration: none;\n  background-color: var(--mid-purple);\n  background-image: linear-gradient(259deg, var(--bright-purple) 0%, var(--dark-purple) 100%);\n  box-shadow: 0 4px 16px 0 var(--black-shadow);\n  color: var(--off-white);\n}\n\nmain {\n  padding: 60px 0 0 0;\n}\n\n.flex {\n  display: flex;\n  margin: 0 auto;\n  max-width: 920px;\n  flex-wrap: wrap;\n  padding: 20px 20px;\n  padding-top: 30px;\n  justify-content: space-between;\n}\n\n.flex section {\n  /*margin: 20px;*/\n  margin-top: 40px;\n  width: 32%;\n}\n\n.flex section:first-child {\n  margin-left: 0px;\n}\n.flex section:last-child {\n  margin-right: 0px;\n}\n\n.flex section i {\n  float: left;\n  left: 0;\n  display: block;\n  margin: 0px auto 10px auto;\n}\n\n.flex section h3 {\n  margin-top: 0;\n  font-size: 18px;\n  color: var(--dark-purple);\n  margin-bottom: 0.5em;\n  font-weight: 300;\n  margin-left: 40px;\n}\n\n.flex section p {\n  padding-left: 40px;\n  color: var(--grey);\n  font-size: 16px;\n  margin: 0;\n}\n\nfooter {\n  color: var(--grey);\n  max-width: 700px;\n  margin: 70px auto 20px;\n  padding: 0 20px 20px 20px;\n  font-size: 16px;\n  text-align: center;\n}\n\n@media screen and (max-width: 680px) {\n  .flex {\n    display: block;\n    padding: 0;\n  }\n  .flex section {\n    width: 100%;\n  }\n\n  main {\n    padding: 20px;\n    margin: 0;\n    width: calc(100% - 40px);\n  }\n\n  nav a,\n  nav a#twitter-nav {\n    display: inline-block;\n    margin: 0px 10px;\n  }\n\n  nav a.cta {\n    display: block;\n    margin: 20px;\n  }\n\n  header #twitter {\n    display: none;\n  }\n\n  header #logo {\n    display: block;\n    float: none;\n    margin: 0px auto;\n  }\n\n  header #title br {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "docs_theme/assets/theme.css",
    "content": "/*\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n*/\n\n/* ------------------------------------------------------------ */\n/* Overrides for jrnl theme                                     */\n/* ------------------------------------------------------------ */\n\nbody.wy-body-for-nav,\nsection.wy-nav-content-wrap {\n  background-color: var(--white);\n  color: var(--black);\n}\n\n.rst-content pre {\n  background-color: transparent;\n  border: none;\n  margin: 1em -1em;\n}\n\n.rst-content code {\n  color: var(--darkest-purple);\n  background-color: var(--off-white);\n  font-size: 15px;\n}\n\n.rst-content pre code {\n  color: var(--off-white);\n  background: var(--darkest-purple);\n  padding: 1em 1.5em;\n  border-radius: 15px;\n  border: none;\n  font-size: 16px;\n  line-height: 1.5em;\n  font-weight: 200 !important;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, sans-serif;\n  font-weight: 600;\n  margin-top: 2rem;\n  margin-bottom: 0.2rem;\n}\n\nh2 {\n  font-size: 1.2em;\n  margin-top: 40px;\n}\n\nh3 {\n  font-size:  1.1em;\n}\n\nh4 {\n  font-size:  1em;\n}\n\np,\ntd,\ntr,\ndiv,\nli {\n  font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, sans-serif;\n  font-weight: 00;\n  font-size: 18px;\n  line-height: 1.5em;\n}\n\np {\n  margin: 1em 0em;\n}\n\n/* No-one likes lines that are 400 characters long. */\ndiv.rst-content {\n  max-width: 54em;\n}\n\n\n.wy-side-nav-search,\n.wy-menu-vertical li.current,\n.wy-menu-vertical li.toctree-l1.current > a,\n.wy-menu-vertical li.toctree-l2.current > a,\n.wy-menu-vertical li.toctree-l3.current > a {\n  background-color: transparent;\n  border: none;\n}\n\n.wy-menu-vertical li.toctree-l2.current li.toctree-l3,\n.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a {\n  background: transparent;\n}\n\n.wy-menu-vertical li.toctree-l4,\n.wy-menu-vertical li.toctree-l5,\n.wy-menu-vertical li.toctree-l6,\n.wy-menu-vertical li.toctree-l7 {\n  display: none;\n}\n\n.wy-nav-top {\n  background-color: var(--mid-purple);\n  background-image: linear-gradient(-211deg, var(--mid-purple) 0%, var(--dark-purple) 100%);\n}\n\n.wy-nav-top .fa-bars {\n  line-height: 50px;\n}\n\n.wy-side-nav-search a.icon-home {\n  width: 100%;\n  max-width: 250px;\n  background-size: 100%;\n}\n\n.wy-side-nav-search a.icon-home:before {\n  display: block;\n  width: 84px;\n  height: 70px;\n  content: \"\";\n  background: url(../img/logo_white.svg) center center no-repeat;\n  margin: 10px auto;\n}\n\n.wy-menu-vertical a,\n.wy-menu-vertical li ul li a {\n  font-size: 16px;\n  color: var(--white);\n  line-height: 2em;\n}\n\n.wy-menu-vertical a:hover,\n.wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a:hover,\n.wy-menu-vertical li.current a:hover {\n  background-color: var(--black-shadow);\n  color: var(--white);\n}\n\n.wy-menu-vertical li.on a {\n  transition: all .25s ease;\n  background: var(--dark-purple);\n  color: var(--white);\n  position: relative;\n}\n\n.wy-menu-vertical li.toctree-l1.current > a {\n  background: var(--darkest-purple);\n  border: none !important;\n  pointer-events: none;\n}\n\n.wy-menu-vertical li.on a,\n.wy-menu-vertical li.current a {\n  border-right: none;\n}\n\n.wy-menu-vertical li.on a,\n.wy-menu-vertical li > a.current:after {\n  position: absolute;\n  right: 0em;\n  z-index: 999;\n  content: \"\";\n  width: 0;\n  height: 0;\n  border-top: 1em solid transparent;\n  border-bottom: 1em solid transparent;\n  border-right: 1em solid var(--white);\n}\n\n.toctree-expand:before {\n  display: none !important;\n}\n\n.rst-versions,\n.rst-versions .rst-current-version {\n  display: none;\n}\n\n.wy-menu-vertical p.caption {\n  margin-top: 2em;\n}\n\n.wy-menu-vertical span {\n  color: var(--white);\n  font-size: 1.2em;\n  font-weight: 600;\n}\n\n.wy-menu-vertical li a {\n  color: var(--white) !important;\n  font-weight: 300;\n}\n\n\n.wy-nav-side {\n  background-color: var(--mid-purple);\n  font-weight: 300;\n  height: 100%;\n}\n\n\nfooter {\n  display: none;\n}\n\n.wy-side-nav-search input[type=text],\n.mkdocs-search input[type=text],\nform .search-query {\n  background-color: var(--off-white);\n  border: none;\n  margin-bottom: 1em;\n  color: var(--black);\n  font-weight: 500;\n  box-shadow: none;\n}\n\n.wy-side-nav-search input[type=text]::placeholder,\n.mkdocs-search input[type=text]::placeholder,\nform .search-query::placeholder {\n  color: var(--dark-purple);\n}\n\n.wy-side-nav-search > a:hover {\n  background: transparent;\n}\n\n.wy-menu-vertical li.current ul {\n  background-color: var(--mid-purple);\n}\n\n\n/* ------------------------------------------------------------ */\n/* Logo: ;                                                      */\n/* ------------------------------------------------------------ */\n\n.logo {\n  width: 128px;\n  height: 128px;\n  vertical-align: middle;\n  margin-right: 1em;\n}\n\n/* ------------------------------------------------------------ */\n/* Code blocks in callouts                                      */\n/* ------------------------------------------------------------ */\n\ndiv.admonition {\n  border-radius: 5px;\n  margin: 1em -1em;\n}\n\ndiv.admonition p.admonition-title {\n  border-top-left-radius: 5px;\n  border-top-right-radius: 5px;\n}\n\ndiv.admonition>p {\n  padding: 0em .5em;\n}\n\n\ndiv.admonition div.highlight {\n  background: none !important;\n}\n\n/* ------------------------------------------------------------ */\n/* Fancy ordered lists.                                         */\n/* ------------------------------------------------------------ */\n\nol {\n  counter-reset: li;\n  margin-left: 0px;\n  padding: 0;\n}\n\nol li {\n  list-style: none !important;\n  margin-bottom: 1.5em;\n  margin-left: 3em !important;\n}\n\nol>li:before {\n  content: counter(li);\n  counter-increment: li;\n  background-color: var(--sidebar);\n  border-radius: 50%;\n  display: block;\n  float: left;\n  margin-left: -3em;\n  margin-top: -.3em;\n  width: 2em;\n  height: 2em;\n  color: var(--dark-purple);\n  text-align: center;\n  line-height: 2em;\n  font-weight: 600;\n}\n\n\n/* ------------------------------------------------------------ */\n/* Accessibility-related changes                                */\n/* ------------------------------------------------------------ */\n.rst-content div[role=\"main\"] a,\n.rst-content div[role=\"main\"] a:visited {\n  color: var(--mid-purple);\n  text-decoration: underline;\n}\n\n.rst-content div[role=\"main\"]  a:hover {\n  color: var(--bright-purple);\n}\n\n.rst-content div[role=\"navigation\"] a,\n.rst-content div[role=\"navigation\"] a:visited {\n  color: var(--mid-purple);\n}\n\n.mkdocs-search {\n  display: flex;\n  margin-top: 20px;\n}\n\n.wy-side-nav-search input[type=\"text\"],\n.mkdocs-search input[type=text] {\n  border-radius: 50px 0 0 50px;\n  height: 32px;\n  border-right: none;\n  margin: 0;\n}\n\n.mkdocs-search button {\n  background-color: var(--off-white);\n  border: none;\n  box-shadow: none;\n  color: var(--mid-purple);\n  border-radius: 0 50px 50px 0;\n  height: 32px;\n  width: 2.5em;\n  overflow: hidden;\n}\n\n.mkdocs-search {\n  border-radius: 50px;\n}\n\n.mkdocs-search:focus-within {\n  box-shadow: 0 2px 25px 0 var(--blacker-shadow);\n  transition: all .5s ease;\n}\n\n.rst-content div[role=\"main\"] .mkdocs-search input[type=\"text\"] {\n  border-right: none;\n  font-size: 100%;\n  height: 48px;\n  margin: 0;\n}\n\n.rst-content div[role=\"main\"] .mkdocs-search button {\n  border-left: none;\n  font-size: 100%;\n  height: 48px;\n}\n\n.rst-content div[role=\"main\"] .mkdocs-search button:before {\n  font-size: 140%;\n  position: relative;\n  left: -7px;\n  top: -1px;\n}\n\n.search-results {\n  margin-top: 0;\n}\n"
  },
  {
    "path": "docs_theme/breadcrumbs.html",
    "content": "<!--\nCopied from https://github.com/mkdocs/mkdocs/blob/master/mkdocs/themes/readthedocs/breadcrumbs.html\nThen lightly modified for accessibility\n-->\n\n<div role=\"navigation\" aria-label=\"breadcrumbs navigation\">\n  <ul class=\"wy-breadcrumbs\">\n    <li><a href=\"{{ nav.homepage.url|url }}\" class=\"icon icon-home\" aria-label=\"{% trans %}Docs{% endtrans %}\"></a> &raquo;</li>\n    {%- if page %}\n      {%- for doc in page.ancestors[::-1] %}\n        {%- if doc.link %}\n          <li><a href=\"{{ doc.link|e }}\">{{ doc.title }}</a> &raquo;</li>\n        {%- else %}\n          <li>{{ doc.title }} &raquo;</li>\n        {%- endif %}\n      {%- endfor %}\n      <li>{{ page.title }}</li>\n    {%- endif %}\n    <li class=\"wy-breadcrumbs-aside\">\n      {%- block repo %}\n      {%- if page and page.edit_url %}\n        {%- if config.repo_name|lower == 'github' %}\n          <a href=\"{{ page.edit_url }}\" class=\"icon icon-github\"> {% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>\n        {%- elif config.repo_name|lower == 'bitbucket' %}\n          <a href=\"{{ page.edit_url }}\" class=\"icon icon-bitbucket\"> {% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>\n        {%- elif config.repo_name|lower == 'gitlab' %}\n          <a href=\"{{ page.edit_url }}\" class=\"icon icon-gitlab\"> {% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>\n        {%- elif config.repo_name %}\n          <a href=\"{{ page.edit_url }}\">{% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>\n        {%- else %}\n          <a href=\"{{ page.edit_url }}\">{% trans %}Edit{% endtrans %}</a>\n        {%- endif %}\n      {%- endif %}\n      {%- endblock %}\n    </li>\n  </ul>\n  {%- if config.theme.prev_next_buttons_location|lower in ['top', 'both']\n        and page and (page.next_page or page.previous_page) %}\n    <div class=\"rst-breadcrumbs-buttons\" role=\"navigation\" aria-label=\"{% trans %}Breadcrumb Navigation{% endtrans %}\">\n      {%- if page.previous_page %}\n        <a href=\"{{ page.previous_page.url|url }}\" class=\"btn btn-neutral float-left\" title=\"{{ page.previous_page.title }}\"><span class=\"icon icon-circle-arrow-left\" aria-hidden=\"true\"></span> {% trans %}Previous{% endtrans %}</a>\n      {%- endif %}\n      {%- if page.next_page %}\n        <a href=\"{{ page.next_page.url|url }}\" class=\"btn btn-neutral float-right\" title=\"{{ page.next_page.title }}\">{% trans %}Next{% endtrans %} <span class=\"icon icon-circle-arrow-right\" aria-hidden=\"true\"></span></a>\n      {%- endif %}\n    </div>\n  {%- endif %}\n  <hr/>\n</div>\n"
  },
  {
    "path": "docs_theme/index.html",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>jrnl - The Command Line Journal</title>\n    <meta name=\"description\" content=\"Collect your thoughts and notes without leaving the command line.\">\n    <meta name=\"image\" content=\"https://jrnl.sh/en/stable/img/banner_og.png\">\n    <meta itemprop=\"name\" content=\"jrnl - The Command Line Journal\">\n    <meta itemprop=\"description\" content=\"Collect your thoughts and notes without leaving the command line.\">\n    <meta itemprop=\"image\" content=\"https://jrnl.sh/en/stable/img/banner_og.png\">\n    <meta name=\"twitter:card\" content=\"summary\">\n    <meta name=\"twitter:title\" content=\"jrnl - The Command Line Journal\">\n    <meta name=\"twitter:description\" content=\"Collect your thoughts and notes without leaving the command line.\">\n    <meta name=\"twitter:creator\" content=\"jrnl\">\n    <meta name=\"twitter:image:src\" content=\"https://jrnl.sh/en/stable/img/banner_twitter.png\">\n    <meta name=\"og:title\" content=\"jrnl - The Command Line Journal\">\n    <meta name=\"og:description\" content=\"Collect your thoughts and notes without leaving the command line.\">\n    <meta name=\"og:image\" content=\"https://jrnl.sh/en/stable/img/banner_og.png\">\n    <meta name=\"og:url\" content=\"https://jrnl.sh\">\n    <meta name=\"og:site_name\" content=\"jrnl - The Command Line Journal\">\n    <meta name=\"og:type\" content=\"website\">\n    <meta name=\"viewport\" content=\"width=device-width\">\n    <link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400' rel='stylesheet' type='text/css'>\n    <link rel=\"stylesheet\" href=\"assets/colors.css\">\n    <link rel=\"stylesheet\" href=\"assets/index.css\">\n    <link rel=\"shortcut icon\" href=\"img/favicon.ico\">\n    <script type=\"application/ld+json\">\n    {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"SoftwareApplication\",\n        \"applicationCategory\": \"https://schema.org/LifestyleApplication\",\n        \"name\": \"jrnl\",\n        \"description\": \"Collect your thoughts and notes without leaving the command line.\",\n        \"operatingSystem\": [\"macOS\", \"Windows\", \"Linux\"],\n        \"thumbnailUrl\": \"https://jrnl.sh/en/stable/img/banner_og.png\",\n        \"installUrl\": \"https://jrnl.sh/en/stable/installation\",\n        \"softwareVersion\": \"2.5\"\n    }\n    </script>\n</head>\n\n<body>\n    <header>\n        <aside>\n            <a id=\"twitter\" 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</a>\n        </aside>\n        <div id=\"title\">\n            <img id=\"logo\" src=\"img/jrnl_white.svg\" width=\"90px\" height=\"98px\" title=\"jrnl\" alt=\"jrnl logo\" />\n            <h1>Collect your thoughts and notes <br />without leaving the command line.</h1>\n        </div>\n        <div id=\"prompt\">\n            <div id=\"terminal\">\n                <div id=\"typed\"></div>\n            </div>\n        </div>\n    </header>\n    <main>\n        <nav>\n            <a href=\"overview\">Documentation</a>\n            <a href=\"installation\" class=\"cta\">Get Started</a>\n            <a href=\"http://github.com/jrnl-org/jrnl\" title=\"View on Github\">Fork on GitHub</a>\n            <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>\n        </nav>\n        <div class=\"flex\">\n            <section>\n                <i class=\"icon nli\"></i>\n                <h3>Human friendly.</h3>\n                <p>jrnl has a natural-language interface so you don't have to remember cryptic shortcuts when you're writing down your thoughts.</p>\n            </section>\n            <section>\n                <i class=\"icon future\"></i>\n                <h3>Future-proof.</h3>\n                <p>Your journals are stored in plain-text files that will still be readable in 50 years when your fancy proprietary apps will have gone the way of the dodo.</p>\n            </section>\n            <section>\n                <i class=\"icon secure\"></i>\n                <h3>Secure.</h3>\n                <p>Encrypt your journals with industry-strength AES encryption. Nobody will be able to read your dirty secrets&mdash;not even you, if you lose your password!</p>\n            </section>\n            <section>\n                <i class=\"icon sync\"></i>\n                <h3>Accessible anywhere.</h3>\n                <p>Sync your journal files with other tools like Dropbox to capture your thoughts wherever you are.</p>\n            </section>\n            <section>\n                <i class=\"icon github\"></i>\n                <h3>Free &amp; Open Source.</h3>\n                <p>jrnl is made by a bunch of really friendly and remarkably amazing people. Maybe even <a href=\"https://www.github.com/jrnl-org/jrnl\" title=\"Fork jrnl on GitHub\">you</a>?</p>\n            </section>\n            <section>\n                <i class=\"icon folders\"></i>\n                <h3>For work and play.</h3>\n                <p>Effortlessly access several journals for all parts of your life.</p>\n            </section>\n        </div>\n    </main>\n    <footer>\n        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.\n    </footer>\n    <script src=\"https://unpkg.com/typed.js@2.1.0/dist/typed.umd.js\"></script>\n    <script>\n    new Typed(\"#typed\", {\n        strings: [\n            \"jrnl Started writing my memoirs on the command line. 🎉🔥💻🔥🎉\",\n            \"jrnl yesterday 2pm: used jrnl to keep track of accomplished tasks. The done.txt for my todo.txt\",\n            \"jrnl <b>-from</b> 2019 <b>-until</b> may<br /><i>`(displays all entries from January 2019 to last May)`</i>\",\n            \"jrnl A day on the beach with @beth and @frank. Tagging them so I can easily look this up later.\",\n            \"jrnl <b>--tags</b><br /><i>`@frank    7<br />@beth    5</i>`\",\n            \"jrnl <b>--format</b> json<br /><i>`(Outputs your entire journal as json)</i>`\",\n            \"jrnl <b>--encrypt</b><br /><i>`(AES encryption. Don't lose your password!)</i>`\"\n        ],\n        typeSpeed: 20, // less is faster\n        backSpeed: 10,\n        backDelay: 2500,\n        loop: true,\n        showCursor: false\n    });\n    </script>\n</body>\n\n</html>\n"
  },
  {
    "path": "docs_theme/index.js",
    "content": "var typed2 =\n"
  },
  {
    "path": "docs_theme/main.html",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n{% extends \"base.html\" %}\n\n{%- block search_button %}\n  {% if 'search' in config['plugins'] %}\n    <div role=\"search\">\n      <form id =\"rtd-search-form\" class=\"wy-form mkdocs-search\" action=\"{{ base_url }}/search.html\" method=\"get\">\n        <input type=\"text\" name=\"q\" placeholder=\"Search docs\" title=\"Type search term here\" />\n        <button class=\"icon icon-search\" aria-label=\"submit\"></button>\n      </form>\n    </div>\n  {% endif %}\n{%- endblock %}\n"
  },
  {
    "path": "docs_theme/requirements.txt",
    "content": "mkdocs>=1.4\njinja2==3.1.6\n"
  },
  {
    "path": "docs_theme/search.html",
    "content": "<!--\nCopyright © 2012-2023 jrnl contributors\nLicense: https://www.gnu.org/licenses/gpl-3.0.html\n-->\n\n{% extends \"main.html\" %}\n\n{% block content %}\n\n<div role=\"search\">\n  <form id =\"content_search\" class=\"wy-form mkdocs-search\" action=\"{{ base_url }}/search.html\" method=\"get\">\n\n    <span role=\"status\" aria-live=\"polite\" class=\"ui-helper-hidden-accessible\"></span>\n    <input\n      name=\"q\"\n      id=\"mkdocs-search-query\"\n      type=\"text\"\n      class=\"search_input search-query ui-autocomplete-input\"\n      placeholder=\"Search the Docs\"\n      autocomplete=\"off\"\n      autofocus\n      title=\"Type search term here\"\n    >\n    <button class=\"icon icon-search\" aria-label=\"submit\"></button>\n  </form>\n</div>\n\n  <h1 id=\"search\">Results</h1>\n\n  <div id=\"mkdocs-search-results\" class=\"search-results\">\n    Searching...\n  </div>\n\n{% endblock %}\n"
  },
  {
    "path": "issue_template.md",
    "content": "# Stop\n\nPlease don't file a blank issue.\n\nFill out one of the templates from the link below and we'll be better able to\nhelp you.\n\nhttps://github.com/jrnl-org/jrnl/issues/new/choose\n"
  },
  {
    "path": "jrnl/__init__.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\ntry:\n    from jrnl.__version__ import __version__\nexcept ImportError:\n    __version__ = \"source\"\n__title__ = \"jrnl\"\n"
  },
  {
    "path": "jrnl/__main__.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport sys\n\nfrom jrnl.main import run\n\nif __name__ == \"__main__\":\n    sys.exit(run())\n"
  },
  {
    "path": "jrnl/__version__.py",
    "content": "__version__ = \"v4.3\"\n"
  },
  {
    "path": "jrnl/args.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport argparse\nimport re\nimport textwrap\n\nfrom jrnl.commands import postconfig_decrypt\nfrom jrnl.commands import postconfig_encrypt\nfrom jrnl.commands import postconfig_import\nfrom jrnl.commands import postconfig_list\nfrom jrnl.commands import preconfig_diagnostic\nfrom jrnl.commands import preconfig_version\nfrom jrnl.output import deprecated_cmd\nfrom jrnl.plugins import EXPORT_FORMATS\nfrom jrnl.plugins import IMPORT_FORMATS\nfrom jrnl.plugins import util\n\n\nclass WrappingFormatter(argparse.RawTextHelpFormatter):\n    \"\"\"Used in help screen\"\"\"\n\n    def _split_lines(self, text: str, width: int) -> list[str]:\n        text = text.split(\"\\n\\n\")\n        text = map(lambda t: self._whitespace_matcher.sub(\" \", t).strip(), text)\n        text = map(lambda t: textwrap.wrap(t, width=56), text)\n        text = [item for sublist in text for item in sublist]\n        return text\n\n\nclass IgnoreNoneAppendAction(argparse._AppendAction):\n    \"\"\"\n    Pass -not without a following string and avoid appending\n    a None value to the excluded list\n    \"\"\"\n\n    def __call__(self, parser, namespace, values, option_string=None):\n        if values is not None:\n            super().__call__(parser, namespace, values, option_string)\n\n\ndef parse_not_arg(\n    args: list[str], parsed_args: argparse.Namespace, parser: argparse.ArgumentParser\n) -> argparse.Namespace:\n    \"\"\"\n    It's possible to use -not as a precursor to -starred and -tagged\n    to reverse their behaviour, however this requires some extra logic\n    to parse, and to ensure we still do not allow passing an empty -not\n    \"\"\"\n\n    parsed_args.exclude_starred = False\n    parsed_args.exclude_tagged = False\n\n    if \"-not-starred\" in \"\".join(args):\n        parsed_args.starred = False\n        parsed_args.exclude_starred = True\n    if \"-not-tagged\" in \"\".join(args):\n        parsed_args.tagged = False\n        parsed_args.exclude_tagged = True\n    if \"-not\" in args and not any(\n        [parsed_args.exclude_starred, parsed_args.exclude_tagged, parsed_args.excluded]\n    ):\n        parser.error(\"argument -not: expected 1 argument\")\n\n    return parsed_args\n\n\ndef parse_args(args: list[str] = []) -> argparse.Namespace:\n    \"\"\"\n    Argument parsing that is doable before the config is available.\n    Everything else goes into \"text\" for later parsing.\n    \"\"\"\n    parser = argparse.ArgumentParser(\n        formatter_class=WrappingFormatter,\n        add_help=False,\n        description=\"Collect your thoughts and notes without leaving the command line\",\n        epilog=textwrap.dedent(\n            \"\"\"\n        We gratefully thank all contributors!\n        Come see the whole list of code and financial contributors at https://github.com/jrnl-org/jrnl\n        And special thanks to Bad Lip Reading for the Yoda joke in the Writing section above :)\"\"\"  # noqa: E501\n        ),\n    )\n\n    optional = parser.add_argument_group(\"Optional Arguments\")\n    optional.add_argument(\n        \"--debug\",\n        dest=\"debug\",\n        action=\"store_true\",\n        help=\"Print information useful for troubleshooting\",\n    )\n\n    standalone = parser.add_argument_group(\n        \"Standalone Commands\",\n        \"These commands will exit after they complete. You may only run one at a time.\",\n    )\n    standalone.add_argument(\"--help\", action=\"help\", help=\"Show this help message\")\n    standalone.add_argument(\"-h\", action=\"help\", help=argparse.SUPPRESS)\n    standalone.add_argument(\n        \"--version\",\n        action=\"store_const\",\n        const=preconfig_version,\n        dest=\"preconfig_cmd\",\n        help=\"Print version information\",\n    )\n    standalone.add_argument(\n        \"-v\",\n        action=\"store_const\",\n        const=preconfig_version,\n        dest=\"preconfig_cmd\",\n        help=argparse.SUPPRESS,\n    )\n    standalone.add_argument(\n        \"--diagnostic\",\n        action=\"store_const\",\n        const=preconfig_diagnostic,\n        dest=\"preconfig_cmd\",\n        help=argparse.SUPPRESS,\n    )\n    standalone.add_argument(\n        \"--list\",\n        action=\"store_const\",\n        const=postconfig_list,\n        dest=\"postconfig_cmd\",\n        help=\"\"\"\n        List all configured journals.\n\n        Optional parameters:\n\n        --format [json or yaml]\n        \"\"\",\n    )\n    standalone.add_argument(\n        \"--ls\",\n        action=\"store_const\",\n        const=postconfig_list,\n        dest=\"postconfig_cmd\",\n        help=argparse.SUPPRESS,\n    )\n    standalone.add_argument(\n        \"-ls\",\n        action=\"store_const\",\n        const=lambda **kwargs: deprecated_cmd(\n            \"-ls\", \"--list or --ls\", callback=postconfig_list, **kwargs\n        ),\n        dest=\"postconfig_cmd\",\n        help=argparse.SUPPRESS,\n    )\n    standalone.add_argument(\n        \"--encrypt\",\n        help=\"Encrypt selected journal with a password\",\n        action=\"store_const\",\n        metavar=\"TYPE\",\n        const=postconfig_encrypt,\n        dest=\"postconfig_cmd\",\n    )\n    standalone.add_argument(\n        \"--decrypt\",\n        help=\"Decrypt selected journal and store it in plain text\",\n        action=\"store_const\",\n        metavar=\"TYPE\",\n        const=postconfig_decrypt,\n        dest=\"postconfig_cmd\",\n    )\n    standalone.add_argument(\n        \"--import\",\n        action=\"store_const\",\n        metavar=\"TYPE\",\n        const=postconfig_import,\n        dest=\"postconfig_cmd\",\n        help=f\"\"\"\n        Import entries from another journal.\n\n        Optional parameters:\n\n        --file FILENAME (default: uses stdin)\n\n        --format [{util.oxford_list(IMPORT_FORMATS)}] (default: jrnl)\n        \"\"\",\n    )\n    standalone.add_argument(\n        \"--file\",\n        metavar=\"FILENAME\",\n        dest=\"filename\",\n        help=argparse.SUPPRESS,\n        default=None,\n    )\n    standalone.add_argument(\"-i\", dest=\"filename\", help=argparse.SUPPRESS)\n\n    compose_msg = \"\"\"\n    To add a new entry into your journal, simply write it on the command line:\n\n        jrnl yesterday: I was walking and I found this big log.\n\n    The date and the following colon (\"yesterday:\") are optional. If you leave\n    them out, \"now\" will be used:\n\n        jrnl Then I rolled the log over.\n\n    Also, you can mark extra special entries (\"star\" them) with an asterisk:\n\n        jrnl *And underneath was a tiny little stick.\n\n    Please note that asterisks might be a special character in your shell, so you\n    might have to escape them. When in doubt about escaping, put quotes around\n    your entire entry:\n\n        jrnl \"saturday at 2am: *Then I was like 'That log had a child!'\" \"\"\"\n\n    composing = parser.add_argument_group(\n        \"Writing\", textwrap.dedent(compose_msg).strip()\n    )\n    composing.add_argument(\"text\", metavar=\"\", nargs=\"*\")\n    composing.add_argument(\n        \"--template\",\n        dest=\"template\",\n        help=\"Path to template file. Can be a local path, absolute path, or a path \"\n        \"relative to $XDG_DATA_HOME/jrnl/templates/\",\n    )\n\n    read_msg = (\n        \"To find entries from your journal, use any combination of the below filters.\"\n    )\n    reading = parser.add_argument_group(\"Searching\", textwrap.dedent(read_msg))\n    reading.add_argument(\n        \"-on\", dest=\"on_date\", metavar=\"DATE\", help=\"Show entries on this date\"\n    )\n    reading.add_argument(\n        \"-today-in-history\",\n        dest=\"today_in_history\",\n        action=\"store_true\",\n        help=\"Show entries of today over the years\",\n    )\n    reading.add_argument(\n        \"-month\",\n        dest=\"month\",\n        metavar=\"DATE\",\n        help=\"Show entries on this month of any year\",\n    )\n    reading.add_argument(\n        \"-day\",\n        dest=\"day\",\n        metavar=\"DATE\",\n        help=\"Show entries on this day of any month\",\n    )\n    reading.add_argument(\n        \"-year\",\n        dest=\"year\",\n        metavar=\"DATE\",\n        help=\"Show entries of a specific year\",\n    )\n    reading.add_argument(\n        \"-from\",\n        dest=\"start_date\",\n        metavar=\"DATE\",\n        help=\"Show entries after, or on, this date\",\n    )\n    reading.add_argument(\n        \"-to\",\n        dest=\"end_date\",\n        metavar=\"DATE\",\n        help=\"Show entries before, or on, this date (alias: -until)\",\n    )\n    reading.add_argument(\"-until\", dest=\"end_date\", help=argparse.SUPPRESS)\n    reading.add_argument(\n        \"-contains\",\n        dest=\"contains\",\n        action=\"append\",\n        metavar=\"TEXT\",\n        help=\"Show entries containing specific text (put quotes around text with \"\n        \"spaces)\",\n    )\n    reading.add_argument(\n        \"-and\",\n        dest=\"strict\",\n        action=\"store_true\",\n        help='Show only entries that match all conditions, like saying \"x AND y\" '\n        \"(default: OR)\",\n    )\n    reading.add_argument(\n        \"-starred\",\n        dest=\"starred\",\n        action=\"store_true\",\n        help=\"Show only starred entries (marked with *)\",\n    )\n    reading.add_argument(\n        \"-tagged\",\n        dest=\"tagged\",\n        action=\"store_true\",\n        help=\"Show only entries that have at least one tag\",\n    )\n    reading.add_argument(\n        \"-n\",\n        dest=\"limit\",\n        default=None,\n        metavar=\"NUMBER\",\n        help=\"Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same \"\n        \"effect)\",\n        nargs=\"?\",\n        type=int,\n    )\n    reading.add_argument(\n        \"-not\",\n        dest=\"excluded\",\n        nargs=\"?\",\n        default=[],\n        metavar=\"TAG/FLAG\",\n        action=IgnoreNoneAppendAction,\n        help=(\n            \"If passed a string, will exclude entries with that tag. \"\n            \"Can be also used before -starred or -tagged flags, to exclude \"\n            \"starred or tagged entries respectively.\"\n        ),\n    )\n\n    search_options_msg = (\n        \"    \"  # Preserves indentation\n        \"\"\"\n        These help you do various tasks with the selected entries from your search.\n        If used on their own (with no search), they will act on your entire journal\"\"\"\n    )\n    exporting = parser.add_argument_group(\n        \"Searching Options\", textwrap.dedent(search_options_msg)\n    )\n    exporting.add_argument(\n        \"--edit\",\n        dest=\"edit\",\n        help=\"Opens the selected entries in your configured editor\",\n        action=\"store_true\",\n    )\n    exporting.add_argument(\n        \"--delete\",\n        dest=\"delete\",\n        action=\"store_true\",\n        help=\"Interactively deletes selected entries\",\n    )\n    exporting.add_argument(\n        \"--change-time\",\n        dest=\"change_time\",\n        nargs=\"?\",\n        metavar=\"DATE\",\n        const=\"now\",\n        help=\"Change timestamp for selected entries (default: now)\",\n    )\n    exporting.add_argument(\n        \"--format\",\n        metavar=\"TYPE\",\n        dest=\"export\",\n        choices=EXPORT_FORMATS,\n        help=f\"\"\"\n        Display selected entries in an alternate format.\n\n        TYPE can be: {util.oxford_list(EXPORT_FORMATS)}.\n\n        Optional parameters:\n\n        --file FILENAME Write output to file instead of stdout\n        \"\"\",\n        default=False,\n    )\n    exporting.add_argument(\n        \"--export\",\n        metavar=\"TYPE\",\n        dest=\"export\",\n        choices=EXPORT_FORMATS,\n        help=argparse.SUPPRESS,\n    )\n    exporting.add_argument(\n        \"--tags\",\n        dest=\"tags\",\n        action=\"store_true\",\n        help=\"Alias for '--format tags'. Returns a list of all tags and number of \"\n        \"occurrences\",\n    )\n    exporting.add_argument(\n        \"--short\",\n        dest=\"short\",\n        action=\"store_true\",\n        help=\"Show only titles or line containing the search tags\",\n    )\n    exporting.add_argument(\n        \"-s\",\n        dest=\"short\",\n        action=\"store_true\",\n        help=argparse.SUPPRESS,\n    )\n    exporting.add_argument(\n        \"-o\",\n        dest=\"filename\",\n        help=argparse.SUPPRESS,\n    )\n\n    config_overrides = parser.add_argument_group(\n        \"Config file override\",\n        textwrap.dedent(\"Apply a one-off override of the config file option\"),\n    )\n    config_overrides.add_argument(\n        \"--config-override\",\n        dest=\"config_override\",\n        action=\"append\",\n        type=str,\n        nargs=2,\n        default=[],\n        metavar=\"CONFIG_KV_PAIR\",\n        help=\"\"\"\n        Override configured key-value pair with CONFIG_KV_PAIR for this command invocation only.\n\n        Examples: \\n\n        \\t - Use a different editor for this jrnl entry, call: \\n\n            \\t jrnl --config-override editor \"nano\" \\n\n        \\t - Override color selections\\n\n           \\t jrnl --config-override colors.body blue --config-override colors.title green\n        \"\"\",  # noqa: E501\n    )\n    config_overrides.add_argument(\n        \"--co\",\n        dest=\"config_override\",\n        action=\"append\",\n        type=str,\n        nargs=2,\n        default=[],\n        help=argparse.SUPPRESS,\n    )\n\n    alternate_config = parser.add_argument_group(\n        \"Specifies alternate config to be used\",\n        textwrap.dedent(\"Applies alternate config for current session\"),\n    )\n\n    alternate_config.add_argument(\n        \"--config-file\",\n        dest=\"config_file_path\",\n        type=str,\n        default=\"\",\n        help=\"\"\"\n        Overrides default (created when first installed) config file for this command only.\n\n        Examples: \\n\n        \\t - Use a work config file for this jrnl entry, call: \\n\n            \\t jrnl --config-file /home/user1/work_config.yaml\n        \\t - Use a personal config file stored on a thumb drive: \\n\n            \\t jrnl --config-file /media/user1/my-thumb-drive/personal_config.yaml\n        \"\"\",  # noqa: E501\n    )\n\n    alternate_config.add_argument(\n        \"--cf\", dest=\"config_file_path\", type=str, default=\"\", help=argparse.SUPPRESS\n    )\n\n    # Handle '-123' as a shortcut for '-n 123'\n    num = re.compile(r\"^-(\\d+)$\")\n    args = [num.sub(r\"-n \\1\", arg) for arg in args]\n    parsed_args = parser.parse_intermixed_args(args)\n    parsed_args = parse_not_arg(args, parsed_args, parser)\n\n    return parsed_args\n"
  },
  {
    "path": "jrnl/color.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport re\nfrom string import punctuation\nfrom string import whitespace\nfrom typing import TYPE_CHECKING\n\nimport colorama\n\nfrom jrnl.os_compat import on_windows\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n\nif on_windows():\n    colorama.init()\n\n\ndef colorize(string: str, color: str, bold: bool = False) -> str:\n    \"\"\"Returns the string colored with colorama.Fore.color. If the color set by\n    the user is \"NONE\" or the color doesn't exist in the colorama.Fore attributes,\n    it returns the string without any modification.\"\"\"\n    color_escape = getattr(colorama.Fore, color.upper(), None)\n    if not color_escape:\n        return string\n    elif not bold:\n        return color_escape + string + colorama.Fore.RESET\n    else:\n        return colorama.Style.BRIGHT + color_escape + string + colorama.Style.RESET_ALL\n\n\ndef highlight_tags_with_background_color(\n    entry: \"Entry\", text: str, color: str, is_title: bool = False\n) -> str:\n    \"\"\"\n    Takes a string and colorizes the tags in it based upon the config value for\n    color.tags, while colorizing the rest of the text based on `color`.\n    :param entry: Entry object, for access to journal config\n    :param text: Text to be colorized\n    :param color: Color for non-tag text, passed to colorize()\n    :param is_title: Boolean flag indicating if the text is a title or not\n    :return: Colorized str\n    \"\"\"\n\n    def colorized_text_generator(fragments):\n        \"\"\"Efficiently generate colorized tags / text from text fragments.\n        Taken from @shobrook. Thanks, buddy :)\n        :param fragments: List of strings representing parts of entry (tag or word).\n        :rtype: List of tuples\n        :returns [(colorized_str, original_str)]\"\"\"\n        for part in fragments:\n            if part and part[0] not in config[\"tagsymbols\"]:\n                yield colorize(part, color, bold=is_title), part\n            elif part:\n                yield colorize(part, config[\"colors\"][\"tags\"], bold=True), part\n\n    config = entry.journal.config\n    if config[\"highlight\"]:  # highlight tags\n        text_fragments = re.split(entry.tag_regex(config[\"tagsymbols\"]), text)\n\n        # Colorizing tags inside of other blocks of text\n        final_text = \"\"\n        previous_piece = \"\"\n        for colorized_piece, piece in colorized_text_generator(text_fragments):\n            # If this piece is entirely punctuation or whitespace or the start\n            # of a line or the previous piece was a tag or this piece is a tag,\n            # then add it to the final text without a leading space.\n            if (\n                all(char in punctuation + whitespace for char in piece)\n                or previous_piece.endswith(\"\\n\")\n                or (previous_piece and previous_piece[0] in config[\"tagsymbols\"])\n                or piece[0] in config[\"tagsymbols\"]\n            ):\n                final_text += colorized_piece\n            else:\n                # Otherwise add a leading space and then append the piece.\n                final_text += \" \" + colorized_piece\n\n            previous_piece = piece\n        return final_text.lstrip()\n    else:\n        return text\n"
  },
  {
    "path": "jrnl/commands.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\n\"\"\"\nFunctions in this file are standalone commands. All standalone commands are split into\ntwo categories depending on whether they require the config to be loaded to be able to\nrun.\n\n1. \"preconfig\" commands don't require the config at all, and can be run before the\n   config has been loaded.\n2. \"postconfig\" commands require to config to have already been loaded, parsed, and\n   scoped before they can be run.\n\nAlso, please note that all (non-builtin) imports should be scoped to each function to\navoid any possible overhead for these standalone commands.\n\"\"\"\n\nimport argparse\nimport logging\nimport platform\nimport sys\n\nfrom jrnl.config import cmd_requires_valid_journal_name\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\n\n\ndef preconfig_diagnostic(_) -> None:\n    from jrnl import __title__\n    from jrnl import __version__\n\n    print(\n        f\"{__title__}: {__version__}\\n\"\n        f\"Python: {sys.version}\\n\"\n        f\"OS: {platform.system()} {platform.release()}\"\n    )\n\n\ndef preconfig_version(_) -> None:\n    import textwrap\n\n    from jrnl import __title__\n    from jrnl import __version__\n\n    output = f\"\"\"\n    {__title__} {__version__}\n\n    Copyright © 2012-2023 jrnl contributors\n\n    This is free software, and you are welcome to redistribute it under certain\n    conditions; for details, see: https://www.gnu.org/licenses/gpl-3.0.html\n    \"\"\"\n\n    output = textwrap.dedent(output).strip()\n\n    print(output)\n\n\ndef postconfig_list(args: argparse.Namespace, config: dict, **_) -> int:\n    from jrnl.output import list_journals\n\n    print(list_journals(config, args.export))\n\n    return 0\n\n\n@cmd_requires_valid_journal_name\ndef postconfig_import(args: argparse.Namespace, config: dict, **_) -> int:\n    from jrnl.journals import open_journal\n    from jrnl.plugins import get_importer\n\n    # Requires opening the journal\n    journal = open_journal(args.journal_name, config)\n\n    format = args.export if args.export else \"jrnl\"\n\n    if (importer := get_importer(format)) is None:\n        raise JrnlException(\n            Message(\n                MsgText.ImporterNotFound,\n                MsgStyle.ERROR,\n                {\"format\": format},\n            )\n        )\n\n    importer.import_(journal, args.filename)\n\n    return 0\n\n\n@cmd_requires_valid_journal_name\ndef postconfig_encrypt(\n    args: argparse.Namespace, config: dict, original_config: dict\n) -> int:\n    \"\"\"\n    Encrypt a journal in place, or optionally to a new file\n    \"\"\"\n    from jrnl.config import update_config\n    from jrnl.install import save_config\n    from jrnl.journals import open_journal\n\n    # Open the journal\n    journal = open_journal(args.journal_name, config)\n\n    if hasattr(journal, \"can_be_encrypted\") and not journal.can_be_encrypted:\n        raise JrnlException(\n            Message(\n                MsgText.CannotEncryptJournalType,\n                MsgStyle.ERROR,\n                {\n                    \"journal_name\": args.journal_name,\n                    \"journal_type\": journal.__class__.__name__,\n                },\n            )\n        )\n\n    # If journal is encrypted, create new password\n    logging.debug(\"Clearing encryption method...\")\n\n    if journal.config[\"encrypt\"] is True:\n        logging.debug(\"Journal already encrypted. Re-encrypting...\")\n        print(f\"Journal {journal.name} is already encrypted. Create a new password.\")\n        journal.encryption_method.clear()\n    else:\n        journal.config[\"encrypt\"] = True\n        journal.encryption_method = None\n\n    journal.write(args.filename)\n\n    print_msg(\n        Message(\n            MsgText.JournalEncryptedTo,\n            MsgStyle.NORMAL,\n            {\"path\": args.filename or journal.config[\"journal\"]},\n        )\n    )\n\n    # Update the config, if we encrypted in place\n    if not args.filename:\n        update_config(\n            original_config, {\"encrypt\": True}, args.journal_name, force_local=True\n        )\n        save_config(original_config)\n\n    return 0\n\n\n@cmd_requires_valid_journal_name\ndef postconfig_decrypt(\n    args: argparse.Namespace, config: dict, original_config: dict\n) -> int:\n    \"\"\"Decrypts to file. If filename is not set, we encrypt the journal file itself.\"\"\"\n    from jrnl.config import update_config\n    from jrnl.install import save_config\n    from jrnl.journals import open_journal\n\n    journal = open_journal(args.journal_name, config)\n\n    logging.debug(\"Clearing encryption method...\")\n    journal.config[\"encrypt\"] = False\n    journal.encryption_method = None\n\n    journal.write(args.filename)\n    print_msg(\n        Message(\n            MsgText.JournalDecryptedTo,\n            MsgStyle.NORMAL,\n            {\"path\": args.filename or journal.config[\"journal\"]},\n        )\n    )\n\n    # Update the config, if we decrypted in place\n    if not args.filename:\n        update_config(\n            original_config, {\"encrypt\": False}, args.journal_name, force_local=True\n        )\n        save_config(original_config)\n\n    return 0\n"
  },
  {
    "path": "jrnl/config.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport argparse\nimport logging\nimport os\nfrom typing import Any\nfrom typing import Callable\n\nimport colorama\nfrom rich.pretty import pretty_repr\nfrom ruamel.yaml import YAML\nfrom ruamel.yaml import constructor\n\nfrom jrnl import __version__\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import list_journals\nfrom jrnl.output import print_msg\nfrom jrnl.path import get_config_path\nfrom jrnl.path import get_default_journal_path\n\n# Constants\nDEFAULT_JOURNAL_KEY = \"default\"\n\nYAML_SEPARATOR = \": \"\nYAML_FILE_ENCODING = \"utf-8\"\n\n\ndef make_yaml_valid_dict(input: list) -> dict:\n    \"\"\"\n\n    Convert a two-element list of configuration key-value pair into a flat dict.\n\n    The dict is created through the yaml loader, with the assumption that\n    \"input[0]: input[1]\" is valid yaml.\n\n    :param input: list of configuration keys in dot-notation and their respective values\n    :type input: list\n    :return: A single level dict of the configuration keys in dot-notation and their\n        respective desired values\n    :rtype: dict\n    \"\"\"\n\n    assert len(input) == 2\n\n    # yaml compatible strings are of the form Key:Value\n    yamlstr = YAML_SEPARATOR.join(input)\n\n    runtime_modifications = YAML(typ=\"safe\").load(yamlstr)\n\n    return runtime_modifications\n\n\ndef save_config(config: dict, alt_config_path: str | None = None) -> None:\n    \"\"\"Supply alt_config_path if using an alternate config through --config-file.\"\"\"\n    config[\"version\"] = __version__\n\n    yaml = YAML(typ=\"safe\")\n    yaml.default_flow_style = False  # prevents collapsing of tree structure\n\n    with open(\n        alt_config_path if alt_config_path else get_config_path(),\n        \"w\",\n        encoding=YAML_FILE_ENCODING,\n    ) as f:\n        yaml.dump(config, f)\n\n\ndef get_default_config() -> dict[str, Any]:\n    return {\n        \"version\": __version__,\n        \"journals\": {\"default\": {\"journal\": get_default_journal_path()}},\n        \"editor\": os.getenv(\"VISUAL\") or os.getenv(\"EDITOR\") or \"\",\n        \"encrypt\": False,\n        \"template\": False,\n        \"default_hour\": 9,\n        \"default_minute\": 0,\n        \"timeformat\": \"%F %r\",\n        \"tagsymbols\": \"#@\",\n        \"highlight\": True,\n        \"linewrap\": 79,\n        \"indent_character\": \"|\",\n        \"colors\": {\n            \"body\": \"none\",\n            \"date\": \"none\",\n            \"tags\": \"none\",\n            \"title\": \"none\",\n        },\n    }\n\n\ndef get_default_colors() -> dict[str, Any]:\n    return {\n        \"body\": \"none\",\n        \"date\": \"black\",\n        \"tags\": \"yellow\",\n        \"title\": \"cyan\",\n    }\n\n\ndef scope_config(config: dict, journal_name: str) -> dict:\n    if journal_name not in config[\"journals\"]:\n        return config\n    config = config.copy()\n    journal_conf = config[\"journals\"].get(journal_name)\n    if isinstance(journal_conf, dict):\n        # We can override the default config on a by-journal basis\n        logging.debug(\n            \"Updating configuration with specific journal overrides:\\n%s\",\n            pretty_repr(journal_conf),\n        )\n        config.update(journal_conf)\n    else:\n        # But also just give them a string to point to the journal file\n        config[\"journal\"] = journal_conf\n\n    logging.debug(\"Scoped config:\\n%s\", pretty_repr(config))\n    return config\n\n\ndef verify_config_colors(config: dict) -> bool:\n    \"\"\"\n    Ensures the keys set for colors are valid colorama.Fore attributes, or \"None\"\n    :return: True if all keys are set correctly, False otherwise\n    \"\"\"\n    all_valid_colors = True\n    for key, color in config[\"colors\"].items():\n        upper_color = color.upper()\n        if upper_color == \"NONE\":\n            continue\n        if not getattr(colorama.Fore, upper_color, None):\n            print_msg(\n                Message(\n                    MsgText.InvalidColor,\n                    MsgStyle.NORMAL,\n                    {\n                        \"key\": key,\n                        \"color\": color,\n                    },\n                )\n            )\n            all_valid_colors = False\n    return all_valid_colors\n\n\ndef load_config(config_path: str) -> dict:\n    \"\"\"Tries to load a config file from YAML.\"\"\"\n    try:\n        with open(config_path, encoding=YAML_FILE_ENCODING) as f:\n            yaml = YAML(typ=\"safe\")\n            yaml.allow_duplicate_keys = False\n            return yaml.load(f)\n    except constructor.DuplicateKeyError as e:\n        print_msg(\n            Message(\n                MsgText.ConfigDoubleKeys,\n                MsgStyle.WARNING,\n                {\n                    \"error_message\": e,\n                },\n            )\n        )\n        with open(config_path, encoding=YAML_FILE_ENCODING) as f:\n            yaml = YAML(typ=\"safe\")\n            yaml.allow_duplicate_keys = True\n            return yaml.load(f)\n\n\ndef is_config_json(config_path: str) -> bool:\n    with open(config_path, \"r\", encoding=\"utf-8\") as f:\n        config_file = f.read()\n    return config_file.strip().startswith(\"{\")\n\n\ndef update_config(\n    config: dict, new_config: dict, scope: str | None, force_local: bool = False\n) -> None:\n    \"\"\"Updates a config dict with new values - either global if scope is None\n    or config['journals'][scope] is just a string pointing to a journal file,\n    or within the scope\"\"\"\n    if scope and isinstance(config[\"journals\"][scope], dict):\n        config[\"journals\"][scope].update(new_config)\n    elif scope and force_local:  # Convert to dict\n        config[\"journals\"][scope] = {\"journal\": config[\"journals\"][scope]}\n        config[\"journals\"][scope].update(new_config)\n    else:\n        config.update(new_config)\n\n\ndef get_journal_name(args: argparse.Namespace, config: dict) -> argparse.Namespace:\n    args.journal_name = DEFAULT_JOURNAL_KEY\n\n    # The first arg might be a journal name\n    if args.text:\n        potential_journal_name = args.text[0]\n        if potential_journal_name[-1] == \":\":\n            potential_journal_name = potential_journal_name[0:-1]\n\n        if potential_journal_name in config[\"journals\"]:\n            args.journal_name = potential_journal_name\n            args.text = args.text[1:]\n\n    logging.debug(\"Using journal name: %s\", args.journal_name)\n    return args\n\n\ndef cmd_requires_valid_journal_name(func: Callable) -> Callable:\n    def wrapper(args: argparse.Namespace, config: dict, original_config: dict):\n        validate_journal_name(args.journal_name, config)\n        func(args=args, config=config, original_config=original_config)\n\n    return wrapper\n\n\ndef validate_journal_name(journal_name: str, config: dict) -> None:\n    if journal_name not in config[\"journals\"]:\n        raise JrnlException(\n            Message(\n                MsgText.NoNamedJournal,\n                MsgStyle.ERROR,\n                {\n                    \"journal_name\": journal_name,\n                    \"journals\": list_journals(config),\n                },\n            ),\n        )\n"
  },
  {
    "path": "jrnl/controller.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\nimport sys\nfrom typing import TYPE_CHECKING\n\nfrom jrnl import install\nfrom jrnl import plugins\nfrom jrnl import time\nfrom jrnl.config import DEFAULT_JOURNAL_KEY\nfrom jrnl.config import get_config_path\nfrom jrnl.config import get_journal_name\nfrom jrnl.config import scope_config\nfrom jrnl.editor import get_text_from_editor\nfrom jrnl.editor import get_text_from_stdin\nfrom jrnl.editor import read_template_file\nfrom jrnl.exception import JrnlException\nfrom jrnl.journals import open_journal\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\nfrom jrnl.output import print_msgs\nfrom jrnl.override import apply_overrides\n\nif TYPE_CHECKING:\n    from argparse import Namespace\n\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\ndef run(args: \"Namespace\"):\n    \"\"\"\n    Flow:\n    1. Run standalone command if it doesn't need config (help, version, etc), then exit\n    2. Load config\n    3. Run standalone command if it does need config (encrypt, decrypt, etc), then exit\n    4. Load specified journal\n    5. Start append mode, or search mode\n    6. Perform actions with results from search mode (if needed)\n    7. Profit\n    \"\"\"\n\n    # Run command if possible before config is available\n    if callable(args.preconfig_cmd):\n        return args.preconfig_cmd(args)\n\n    # Load the config, and extract journal name\n    config = install.load_or_install_jrnl(args.config_file_path)\n    original_config = config.copy()\n\n    # Apply config overrides\n    config = apply_overrides(args, config)\n\n    args = get_journal_name(args, config)\n    config = scope_config(config, args.journal_name)\n\n    # Run post-config command now that config is ready\n    if callable(args.postconfig_cmd):\n        return args.postconfig_cmd(\n            args=args, config=config, original_config=original_config\n        )\n\n    # --- All the standalone commands are now done --- #\n\n    # Get the journal we're going to be working with\n    journal = open_journal(args.journal_name, config)\n\n    kwargs = {\n        \"args\": args,\n        \"config\": config,\n        \"journal\": journal,\n        \"old_entries\": journal.entries,\n    }\n\n    if _is_append_mode(**kwargs):\n        append_mode(**kwargs)\n        return\n\n    # If not append mode, then we're in search mode (only 2 modes exist)\n    search_mode(**kwargs)\n    entries_found_count = len(journal)\n    _print_entries_found_count(entries_found_count, args)\n\n    # Actions\n    _perform_actions_on_search_results(**kwargs)\n\n    if entries_found_count != 0 and _has_action_args(args):\n        _print_changed_counts(journal)\n    else:\n        # display only occurs if no other action occurs\n        _display_search_results(**kwargs)\n\n\ndef _perform_actions_on_search_results(**kwargs):\n    args = kwargs[\"args\"]\n\n    # Perform actions (if needed)\n    if args.change_time:\n        _change_time_search_results(**kwargs)\n\n    if args.delete:\n        _delete_search_results(**kwargs)\n\n    # open results in editor (if `--edit` was used)\n    if args.edit:\n        _edit_search_results(**kwargs)\n\n\ndef _is_append_mode(args: \"Namespace\", config: dict, **kwargs) -> bool:\n    \"\"\"Determines if we are in append mode (as opposed to search mode)\"\"\"\n    # Are any search filters present? If so, then search mode.\n    append_mode = (\n        not _has_search_args(args)\n        and not _has_action_args(args)\n        and not _has_display_args(args)\n    )\n\n    # Might be writing and want to move to editor part of the way through\n    if args.edit and args.text:\n        append_mode = True\n\n    # If the text is entirely tags, then we are also searching (not writing)\n    if append_mode and args.text and _has_only_tags(config[\"tagsymbols\"], args.text):\n        append_mode = False\n\n    return append_mode\n\n\ndef append_mode(args: \"Namespace\", config: dict, journal: \"Journal\", **kwargs) -> None:\n    \"\"\"\n    Gets input from the user to write to the journal\n    0. Check for a template passed as an argument, or in the global config\n    1. Check for input from cli\n    2. Check input being piped in\n    3. Open editor if configured (prepopulated with template if available)\n    4. Use stdin.read as last resort\n    6. Write any found text to journal, or exit\n    \"\"\"\n    logging.debug(\"Append mode: starting\")\n\n    template_text = _get_template(args, config)\n\n    if args.text:\n        logging.debug(f\"Append mode: cli text detected: {args.text}\")\n        raw = \" \".join(args.text).strip()\n        if args.edit:\n            raw = _write_in_editor(config, raw)\n    elif not sys.stdin.isatty():\n        logging.debug(\"Append mode: receiving piped text\")\n        raw = sys.stdin.read()\n    else:\n        raw = _write_in_editor(config, template_text)\n\n    if template_text is not None and raw == template_text:\n        logging.error(\"Append mode: raw text was the same as the template\")\n        raise JrnlException(Message(MsgText.NoChangesToTemplate, MsgStyle.NORMAL))\n\n    if not raw or raw.isspace():\n        logging.error(\"Append mode: couldn't get raw text or entry was empty\")\n        raise JrnlException(Message(MsgText.NoTextReceived, MsgStyle.NORMAL))\n\n    logging.debug(\n        f\"Append mode: appending raw text to journal '{args.journal_name}': {raw}\"\n    )\n    journal.new_entry(raw)\n    if args.journal_name != DEFAULT_JOURNAL_KEY:\n        print_msg(\n            Message(\n                MsgText.JournalEntryAdded,\n                MsgStyle.NORMAL,\n                {\"journal_name\": args.journal_name},\n            )\n        )\n    journal.write()\n    logging.debug(\"Append mode: completed journal.write()\")\n\n\ndef _get_template(args, config) -> str:\n    # Read template file and pass as raw text into the composer\n    logging.debug(\n        \"Get template:\\n\"\n        f\"--template: {args.template}\\n\"\n        f\"from config: {config.get('template')}\"\n    )\n    template_path = args.template or config.get(\"template\")\n\n    template_text = None\n\n    if template_path:\n        template_text = read_template_file(template_path)\n\n    return template_text\n\n\ndef search_mode(args: \"Namespace\", journal: \"Journal\", **kwargs) -> None:\n    \"\"\"\n    Search for entries in a journal, and return the\n    results. If no search args, then return all results\n    \"\"\"\n    logging.debug(\"Search mode: starting\")\n\n    # If no search args, then return all results (don't filter anything)\n    if not _has_search_args(args) and not _has_display_args(args) and not args.text:\n        logging.debug(\"Search mode: has no search args\")\n        return\n\n    logging.debug(\"Search mode: has search args\")\n    _filter_journal_entries(args, journal)\n\n\ndef _write_in_editor(config: dict, prepopulated_text: str | None = None) -> str:\n    if config[\"editor\"]:\n        logging.debug(\"Append mode: opening editor\")\n        raw = get_text_from_editor(config, prepopulated_text)\n    else:\n        raw = get_text_from_stdin()\n\n    return raw\n\n\ndef _filter_journal_entries(args: \"Namespace\", journal: \"Journal\", **kwargs) -> None:\n    \"\"\"Filter journal entries in-place based upon search args\"\"\"\n    if args.on_date:\n        args.start_date = args.end_date = args.on_date\n\n    if args.today_in_history:\n        now = time.parse(\"now\")\n        args.day = now.day\n        args.month = now.month\n\n    journal.filter(\n        tags=args.text,\n        month=args.month,\n        day=args.day,\n        year=args.year,\n        start_date=args.start_date,\n        end_date=args.end_date,\n        strict=args.strict,\n        starred=args.starred,\n        tagged=args.tagged,\n        exclude=args.excluded,\n        exclude_starred=args.exclude_starred,\n        exclude_tagged=args.exclude_tagged,\n        contains=args.contains,\n    )\n    journal.limit(args.limit)\n\n\ndef _print_entries_found_count(count: int, args: \"Namespace\") -> None:\n    logging.debug(f\"count: {count}\")\n    if count == 0:\n        if args.edit or args.change_time:\n            print_msg(Message(MsgText.NothingToModify, MsgStyle.WARNING))\n        elif args.delete:\n            print_msg(Message(MsgText.NothingToDelete, MsgStyle.WARNING))\n        else:\n            print_msg(Message(MsgText.NoEntriesFound, MsgStyle.NORMAL))\n        return\n    elif args.limit and args.limit == count:\n        # Don't show count if the user expects a limited number of results\n        logging.debug(\"args.limit is true-ish\")\n        return\n\n    logging.debug(\"Printing general summary\")\n    my_msg = (\n        MsgText.EntryFoundCountSingular if count == 1 else MsgText.EntryFoundCountPlural\n    )\n    print_msg(Message(my_msg, MsgStyle.NORMAL, {\"num\": count}))\n\n\ndef _other_entries(journal: \"Journal\", entries: list[\"Entry\"]) -> list[\"Entry\"]:\n    \"\"\"Find entries that are not in journal\"\"\"\n    return [e for e in entries if e not in journal.entries]\n\n\ndef _edit_search_results(\n    config: dict, journal: \"Journal\", old_entries: list[\"Entry\"], **kwargs\n) -> None:\n    \"\"\"\n    1. Send the given journal entries to the user-configured editor\n    2. Print out stats on any modifications to journal\n    3. Write modifications to journal\n    \"\"\"\n    if not config[\"editor\"]:\n        raise JrnlException(\n            Message(\n                MsgText.EditorNotConfigured,\n                MsgStyle.ERROR,\n                {\"config_file\": get_config_path()},\n            )\n        )\n\n    # separate entries we are not editing\n    other_entries = _other_entries(journal, old_entries)\n\n    # Send user to the editor\n    try:\n        edited = get_text_from_editor(config, journal.editable_str())\n    except JrnlException as e:\n        if e.has_message_text(MsgText.NoTextReceived):\n            raise JrnlException(\n                Message(MsgText.NoEditsReceivedJournalNotDeleted, MsgStyle.WARNING)\n            )\n        else:\n            raise e\n\n    journal.parse_editable_str(edited)\n\n    # Put back entries we separated earlier, sort, and write the journal\n    journal.entries += other_entries\n    journal.sort()\n    journal.write()\n\n\ndef _print_changed_counts(journal: \"Journal\", **kwargs) -> None:\n    stats = journal.get_change_counts()\n    msgs = []\n\n    if stats[\"added\"] > 0:\n        my_msg = (\n            MsgText.JournalCountAddedSingular\n            if stats[\"added\"] == 1\n            else MsgText.JournalCountAddedPlural\n        )\n        msgs.append(Message(my_msg, MsgStyle.NORMAL, {\"num\": stats[\"added\"]}))\n\n    if stats[\"deleted\"] > 0:\n        my_msg = (\n            MsgText.JournalCountDeletedSingular\n            if stats[\"deleted\"] == 1\n            else MsgText.JournalCountDeletedPlural\n        )\n        msgs.append(Message(my_msg, MsgStyle.NORMAL, {\"num\": stats[\"deleted\"]}))\n\n    if stats[\"modified\"] > 0:\n        my_msg = (\n            MsgText.JournalCountModifiedSingular\n            if stats[\"modified\"] == 1\n            else MsgText.JournalCountModifiedPlural\n        )\n        msgs.append(Message(my_msg, MsgStyle.NORMAL, {\"num\": stats[\"modified\"]}))\n\n    if not msgs:\n        msgs.append(Message(MsgText.NoEditsReceived, MsgStyle.NORMAL))\n\n    print_msgs(msgs)\n\n\ndef _get_predit_stats(journal: \"Journal\") -> dict[str, int]:\n    return {\"count\": len(journal)}\n\n\ndef _delete_search_results(\n    journal: \"Journal\", old_entries: list[\"Entry\"], **kwargs\n) -> None:\n    entries_to_delete = journal.prompt_action_entries(MsgText.DeleteEntryQuestion)\n\n    journal.entries = old_entries\n\n    if entries_to_delete:\n        journal.delete_entries(entries_to_delete)\n\n        journal.write()\n\n\ndef _change_time_search_results(\n    args: \"Namespace\",\n    journal: \"Journal\",\n    old_entries: list[\"Entry\"],\n    no_prompt: bool = False,\n    **kwargs,\n) -> None:\n    # separate entries we are not editing\n    # @todo if there's only 1, don't prompt\n    entries_to_change = journal.prompt_action_entries(MsgText.ChangeTimeEntryQuestion)\n\n    if entries_to_change:\n        date = time.parse(args.change_time)\n        journal.entries = old_entries\n        journal.change_date_entries(date, entries_to_change)\n\n        journal.write()\n\n\ndef _display_search_results(args: \"Namespace\", journal: \"Journal\", **kwargs) -> None:\n    if len(journal) == 0:\n        return\n\n    # Get export format from config file if not provided at the command line\n    args.export = args.export or kwargs[\"config\"].get(\"display_format\")\n\n    if args.tags:\n        print(plugins.get_exporter(\"tags\").export(journal))\n\n    elif args.short or args.export == \"short\":\n        print(journal.pprint(short=True))\n\n    elif args.export == \"pretty\":\n        print(journal.pprint())\n\n    elif args.export:\n        exporter = plugins.get_exporter(args.export)\n        print(exporter.export(journal, args.filename))\n    else:\n        print(journal.pprint())\n\n\ndef _has_search_args(args: \"Namespace\") -> bool:\n    \"\"\"Looking for arguments that filter a journal\"\"\"\n    return any(\n        (\n            args.contains,\n            args.tagged,\n            args.excluded,\n            args.exclude_starred,\n            args.exclude_tagged,\n            args.end_date,\n            args.today_in_history,\n            args.month,\n            args.day,\n            args.year,\n            args.limit,\n            args.on_date,\n            args.starred,\n            args.start_date,\n            args.strict,  # -and\n        )\n    )\n\n\ndef _has_action_args(args: \"Namespace\") -> bool:\n    return any(\n        (\n            args.change_time,\n            args.delete,\n            args.edit,\n        )\n    )\n\n\ndef _has_display_args(args: \"Namespace\") -> bool:\n    return any(\n        (\n            args.tags,\n            args.short,\n            args.export,  # --format\n        )\n    )\n\n\ndef _has_only_tags(tag_symbols: str, args_text: str) -> bool:\n    return all(word[0] in tag_symbols for word in \" \".join(args_text).split())\n"
  },
  {
    "path": "jrnl/editor.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\nimport os\nimport subprocess\nimport sys\nimport tempfile\nfrom pathlib import Path\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.os_compat import on_windows\nfrom jrnl.os_compat import split_args\nfrom jrnl.output import print_msg\nfrom jrnl.path import absolute_path\nfrom jrnl.path import get_templates_path\n\n\ndef get_text_from_editor(config: dict, template: str = \"\") -> str:\n    suffix = \".jrnl\"\n    if config[\"template\"]:\n        template_filename = Path(config[\"template\"]).name\n        suffix = \"-\" + template_filename\n    filehandle, tmpfile = tempfile.mkstemp(prefix=\"jrnl\", text=True, suffix=suffix)\n    os.close(filehandle)\n\n    with open(tmpfile, \"w\", encoding=\"utf-8\") as f:\n        if template:\n            f.write(template)\n\n    try:\n        subprocess.call(split_args(config[\"editor\"]) + [tmpfile])\n    except FileNotFoundError:\n        raise JrnlException(\n            Message(\n                MsgText.EditorMisconfigured,\n                MsgStyle.ERROR,\n                {\"editor_key\": config[\"editor\"]},\n            )\n        )\n\n    with open(tmpfile, \"r\", encoding=\"utf-8\") as f:\n        raw = f.read()\n    os.remove(tmpfile)\n\n    if not raw:\n        raise JrnlException(Message(MsgText.NoTextReceived, MsgStyle.NORMAL))\n\n    return raw\n\n\ndef get_text_from_stdin() -> str:\n    print_msg(\n        Message(\n            MsgText.WritingEntryStart,\n            MsgStyle.TITLE,\n            {\n                \"how_to_quit\": (\n                    MsgText.HowToQuitWindows if on_windows() else MsgText.HowToQuitLinux\n                )\n            },\n        )\n    )\n\n    try:\n        raw = sys.stdin.read()\n    except KeyboardInterrupt:\n        logging.error(\"Append mode: keyboard interrupt\")\n        raise JrnlException(\n            Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR_ON_NEW_LINE),\n            Message(MsgText.JournalNotSaved, MsgStyle.WARNING),\n        )\n\n    return raw\n\n\ndef get_template_path(template_path: str, jrnl_template_dir: str) -> str:\n    actual_template_path = os.path.join(jrnl_template_dir, template_path)\n    if not os.path.exists(actual_template_path):\n        logging.debug(\n            f\"Couldn't open {actual_template_path}. \"\n            \"Treating template path like a local / abs path.\"\n        )\n        actual_template_path = absolute_path(template_path)\n\n    return actual_template_path\n\n\ndef read_template_file(template_path: str) -> str:\n    \"\"\"\n    Reads the template file given a template path in this order:\n\n        * Check $XDG_DATA_HOME/jrnl/templates/template_path.\n        * Check template_arg as an absolute / relative path.\n\n    If a file is found, its contents are returned as a string.\n    If not, a JrnlException is raised.\n    \"\"\"\n\n    jrnl_template_dir = get_templates_path()\n    actual_template_path = get_template_path(template_path, jrnl_template_dir)\n\n    try:\n        with open(actual_template_path, encoding=\"utf-8\") as f:\n            template_data = f.read()\n            return template_data\n    except FileNotFoundError:\n        raise JrnlException(\n            Message(\n                MsgText.CantReadTemplate,\n                MsgStyle.ERROR,\n                {\n                    \"template_path\": template_path,\n                    \"actual_template_path\": actual_template_path,\n                    \"jrnl_template_dir\": str(jrnl_template_dir) + os.sep,\n                },\n            )\n        )\n"
  },
  {
    "path": "jrnl/encryption/BaseEncryption.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\nfrom abc import ABC\nfrom abc import abstractmethod\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\n\n\nclass BaseEncryption(ABC):\n    def __init__(self, journal_name: str, config: dict):\n        logging.debug(\"start\")\n        self._encoding: str = \"utf-8\"\n        self._journal_name: str = journal_name\n        self._config: dict = config\n\n    def clear(self) -> None:\n        pass\n\n    def encrypt(self, text: str) -> bytes:\n        logging.debug(\"encrypting\")\n        return self._encrypt(text)\n\n    def decrypt(self, text: bytes) -> str:\n        logging.debug(\"decrypting\")\n        if (result := self._decrypt(text)) is None:\n            raise JrnlException(\n                Message(MsgText.DecryptionFailedGeneric, MsgStyle.ERROR)\n            )\n\n        return result\n\n    @abstractmethod\n    def _encrypt(self, text: str) -> bytes:\n        \"\"\"\n        This is needed because self.decrypt might need\n        to perform actions (e.g. prompt for password)\n        before actually encrypting.\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def _decrypt(self, text: bytes) -> str | None:\n        \"\"\"\n        This is needed because self.decrypt might need\n        to perform actions (e.g. prompt for password)\n        before actually decrypting.\n        \"\"\"\n        pass\n"
  },
  {
    "path": "jrnl/encryption/BaseKeyEncryption.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom .BaseEncryption import BaseEncryption\n\n\nclass BaseKeyEncryption(BaseEncryption):\n    pass\n"
  },
  {
    "path": "jrnl/encryption/BasePasswordEncryption.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\n\nfrom jrnl.encryption.BaseEncryption import BaseEncryption\nfrom jrnl.exception import JrnlException\nfrom jrnl.keyring import get_keyring_password\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.prompt import create_password\nfrom jrnl.prompt import prompt_password\n\n\nclass BasePasswordEncryption(BaseEncryption):\n    def __init__(self, *args, **kwargs) -> None:\n        super().__init__(*args, **kwargs)\n        logging.debug(\"start\")\n        self._attempts: int = 0\n        self._max_attempts: int = 3\n        self._password: str = \"\"\n        self._check_keyring: bool = True\n\n    @property\n    def check_keyring(self) -> bool:\n        return self._check_keyring\n\n    @check_keyring.setter\n    def check_keyring(self, value: bool) -> None:\n        self._check_keyring = value\n\n    @property\n    def password(self) -> str | None:\n        return self._password\n\n    @password.setter\n    def password(self, value: str) -> None:\n        self._password = value\n\n    def clear(self):\n        self.password = None\n        self.check_keyring = False\n\n    def encrypt(self, text: str) -> bytes:\n        logging.debug(\"encrypting\")\n        if not self.password:\n            if self.check_keyring and (\n                keyring_pw := get_keyring_password(self._journal_name)\n            ):\n                self.password = keyring_pw\n\n            if not self.password:\n                self.password = create_password(self._journal_name)\n\n        return self._encrypt(text)\n\n    def decrypt(self, text: bytes) -> str:\n        logging.debug(\"decrypting\")\n        if not self.password:\n            if self.check_keyring and (\n                keyring_pw := get_keyring_password(self._journal_name)\n            ):\n                self.password = keyring_pw\n\n            if not self.password:\n                self._prompt_password()\n\n        while (result := self._decrypt(text)) is None:\n            self._prompt_password()\n\n        return result\n\n    def _prompt_password(self) -> None:\n        if self._attempts >= self._max_attempts:\n            raise JrnlException(\n                Message(MsgText.PasswordMaxTriesExceeded, MsgStyle.ERROR)\n            )\n\n        first_try = self._attempts == 0\n        self.password = prompt_password(first_try=first_try)\n        self._attempts += 1\n"
  },
  {
    "path": "jrnl/encryption/Jrnlv1Encryption.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport hashlib\nimport logging\n\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import padding\nfrom cryptography.hazmat.primitives.ciphers import Cipher\nfrom cryptography.hazmat.primitives.ciphers import algorithms\nfrom cryptography.hazmat.primitives.ciphers import modes\n\nfrom jrnl.encryption.BasePasswordEncryption import BasePasswordEncryption\n\n\nclass Jrnlv1Encryption(BasePasswordEncryption):\n    def __init__(self, *args, **kwargs) -> None:\n        super().__init__(*args, **kwargs)\n        logging.debug(\"start\")\n\n    def _encrypt(self, _: str) -> bytes:\n        raise NotImplementedError\n\n    def _decrypt(self, text: bytes) -> str | None:\n        logging.debug(\"decrypting\")\n        iv, cipher = text[:16], text[16:]\n        password = self._password or \"\"\n        decryption_key = hashlib.sha256(password.encode(self._encoding)).digest()\n        decryptor = Cipher(\n            algorithms.AES(decryption_key), modes.CBC(iv), default_backend()\n        ).decryptor()\n        try:\n            plain_padded = decryptor.update(cipher) + decryptor.finalize()\n            if plain_padded[-1] in (\" \", 32):\n                # Ancient versions of jrnl. Do not judge me.\n                return plain_padded.decode(self._encoding).rstrip(\" \")\n            else:\n                unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()\n                plain = unpadder.update(plain_padded) + unpadder.finalize()\n                return plain.decode(self._encoding)\n        except ValueError:\n            return None\n"
  },
  {
    "path": "jrnl/encryption/Jrnlv2Encryption.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport base64\nimport logging\n\nfrom cryptography.fernet import Fernet\nfrom cryptography.fernet import InvalidToken\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC\n\nfrom .BasePasswordEncryption import BasePasswordEncryption\n\n\nclass Jrnlv2Encryption(BasePasswordEncryption):\n    def __init__(self, *args, **kwargs) -> None:\n        # Salt is hard-coded\n        self._salt: bytes = b\"\\xf2\\xd5q\\x0e\\xc1\\x8d.\\xde\\xdc\\x8e6t\\x89\\x04\\xce\\xf8\"\n        self._key: bytes = b\"\"\n\n        super().__init__(*args, **kwargs)\n        logging.debug(\"start\")\n\n    @property\n    def password(self):\n        return self._password\n\n    @password.setter\n    def password(self, value: str | None):\n        self._password = value\n        self._make_key()\n\n    def _make_key(self) -> None:\n        if self._password is None:\n            # Password was removed after being set\n            self._key = None\n            return\n        password = self.password.encode(self._encoding)\n        kdf = PBKDF2HMAC(\n            algorithm=hashes.SHA256(),\n            length=32,\n            salt=self._salt,\n            iterations=100_000,\n            backend=default_backend(),\n        )\n        key = kdf.derive(password)\n        self._key = base64.urlsafe_b64encode(key)\n\n    def _encrypt(self, text: str) -> bytes:\n        logging.debug(\"encrypting\")\n        return Fernet(self._key).encrypt(text.encode(self._encoding))\n\n    def _decrypt(self, text: bytes) -> str | None:\n        logging.debug(\"decrypting\")\n        try:\n            return Fernet(self._key).decrypt(text).decode(self._encoding)\n        except (InvalidToken, IndexError):\n            return None\n"
  },
  {
    "path": "jrnl/encryption/NoEncryption.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\n\nfrom jrnl.encryption.BaseEncryption import BaseEncryption\n\n\nclass NoEncryption(BaseEncryption):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        logging.debug(\"start\")\n\n    def _encrypt(self, text: str) -> bytes:\n        logging.debug(\"encrypting\")\n        return text.encode(self._encoding)\n\n    def _decrypt(self, text: bytes) -> str:\n        logging.debug(\"decrypting\")\n        return text.decode(self._encoding)\n"
  },
  {
    "path": "jrnl/encryption/__init__.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom enum import Enum\nfrom importlib import import_module\nfrom typing import TYPE_CHECKING\nfrom typing import Type\n\nif TYPE_CHECKING:\n    from .BaseEncryption import BaseEncryption\n\n\nclass EncryptionMethods(str, Enum):\n    def __str__(self) -> str:\n        return self.value\n\n    NONE = \"NoEncryption\"\n    JRNLV1 = \"Jrnlv1Encryption\"\n    JRNLV2 = \"Jrnlv2Encryption\"\n\n\ndef determine_encryption_method(config: str | bool) -> Type[\"BaseEncryption\"]:\n    ENCRYPTION_METHODS = {\n        True: EncryptionMethods.JRNLV2,  # the default\n        False: EncryptionMethods.NONE,\n        \"jrnlv1\": EncryptionMethods.JRNLV1,\n        \"jrnlv2\": EncryptionMethods.JRNLV2,\n    }\n\n    key = config\n    if isinstance(config, str):\n        key = config.lower()\n\n    my_class = ENCRYPTION_METHODS[key]\n\n    return getattr(import_module(f\"jrnl.encryption.{my_class}\"), my_class)\n"
  },
  {
    "path": "jrnl/exception.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.output import print_msg\n\nif TYPE_CHECKING:\n    from jrnl.messages import Message\n    from jrnl.messages import MsgText\n\n\nclass JrnlException(Exception):\n    \"\"\"Common exceptions raised by jrnl.\"\"\"\n\n    def __init__(self, *messages: \"Message\"):\n        self.messages = messages\n\n    def print(self) -> None:\n        for msg in self.messages:\n            print_msg(msg)\n\n    def has_message_text(self, message_text: \"MsgText\"):\n        return any([m.text == message_text for m in self.messages])\n"
  },
  {
    "path": "jrnl/install.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport contextlib\nimport glob\nimport logging\nimport os\nimport sys\n\nfrom rich.pretty import pretty_repr\n\nfrom jrnl import __version__\nfrom jrnl.config import DEFAULT_JOURNAL_KEY\nfrom jrnl.config import get_config_path\nfrom jrnl.config import get_default_colors\nfrom jrnl.config import get_default_config\nfrom jrnl.config import get_default_journal_path\nfrom jrnl.config import load_config\nfrom jrnl.config import save_config\nfrom jrnl.config import verify_config_colors\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\nfrom jrnl.path import absolute_path\nfrom jrnl.path import expand_path\nfrom jrnl.path import home_dir\nfrom jrnl.prompt import yesno\nfrom jrnl.upgrade import is_old_version\n\n\ndef upgrade_config(config_data: dict, alt_config_path: str | None = None) -> None:\n    \"\"\"Checks if there are keys missing in a given config dict, and if so, updates the\n    config file accordingly. This essentially automatically ports jrnl installations\n    if new config parameters are introduced in later versions. Also checks for\n    existence of and difference in version number between config dict\n    and current jrnl version, and if so, update the config file accordingly.\n    Supply alt_config_path if using an alternate config through --config-file.\"\"\"\n    default_config = get_default_config()\n    missing_keys = set(default_config).difference(config_data)\n    if missing_keys:\n        for key in missing_keys:\n            config_data[key] = default_config[key]\n\n    different_version = config_data[\"version\"] != __version__\n    if different_version:\n        config_data[\"version\"] = __version__\n\n    if missing_keys or different_version:\n        save_config(config_data, alt_config_path)\n        config_path = alt_config_path if alt_config_path else get_config_path()\n        print_msg(\n            Message(\n                MsgText.ConfigUpdated, MsgStyle.NORMAL, {\"config_path\": config_path}\n            )\n        )\n\n\ndef find_default_config() -> str:\n    config_path = (\n        get_config_path()\n        if os.path.exists(get_config_path())\n        else os.path.join(home_dir(), \".jrnl_config\")\n    )\n    return config_path\n\n\ndef find_alt_config(alt_config: str) -> str:\n    if not os.path.exists(alt_config):\n        raise JrnlException(\n            Message(\n                MsgText.AltConfigNotFound, MsgStyle.ERROR, {\"config_file\": alt_config}\n            )\n        )\n\n    return alt_config\n\n\ndef load_or_install_jrnl(alt_config_path: str) -> dict:\n    \"\"\"\n    If jrnl is already installed, loads and returns a default config object.\n    If alternate config is specified via --config-file flag, it will be used.\n    Else, perform various prompts to install jrnl.\n    \"\"\"\n    config_path = (\n        find_alt_config(alt_config_path) if alt_config_path else find_default_config()\n    )\n\n    if os.path.exists(config_path):\n        logging.debug(\"Reading configuration from file %s\", config_path)\n        config = load_config(config_path)\n\n        if config is None:\n            raise JrnlException(\n                Message(\n                    MsgText.CantParseConfigFile,\n                    MsgStyle.ERROR,\n                    {\n                        \"config_path\": config_path,\n                    },\n                )\n            )\n\n        if is_old_version(config_path):\n            from jrnl import upgrade\n\n            upgrade.upgrade_jrnl(config_path)\n\n        upgrade_config(config, alt_config_path)\n        verify_config_colors(config)\n\n    else:\n        logging.debug(\"Configuration file not found, installing jrnl...\")\n        config = install()\n\n    logging.debug('Using configuration:\\n\"%s\"', pretty_repr(config))\n    return config\n\n\ndef install() -> dict:\n    _initialize_autocomplete()\n\n    # Where to create the journal?\n    default_journal_path = get_default_journal_path()\n    user_given_path = print_msg(\n        Message(\n            MsgText.InstallJournalPathQuestion,\n            MsgStyle.PROMPT,\n            params={\n                \"default_journal_path\": default_journal_path,\n            },\n        ),\n        get_input=True,\n    )\n    journal_path = absolute_path(user_given_path or default_journal_path)\n    default_config = get_default_config()\n    default_config[\"journals\"][DEFAULT_JOURNAL_KEY][\"journal\"] = journal_path\n\n    # If the folder doesn't exist, create it\n    path = os.path.split(journal_path)[0]\n    with contextlib.suppress(OSError):\n        os.makedirs(path)\n\n    # Encrypt it?\n    encrypt = yesno(Message(MsgText.EncryptJournalQuestion), default=False)\n    if encrypt:\n        default_config[\"encrypt\"] = True\n        print_msg(Message(MsgText.JournalEncrypted, MsgStyle.NORMAL))\n\n    # Use colors?\n    use_colors = yesno(Message(MsgText.UseColorsQuestion), default=True)\n    if use_colors:\n        default_config[\"colors\"] = get_default_colors()\n\n    save_config(default_config)\n\n    print_msg(\n        Message(\n            MsgText.InstallComplete,\n            MsgStyle.NORMAL,\n            params={\"config_path\": get_config_path()},\n        )\n    )\n\n    return default_config\n\n\ndef _initialize_autocomplete() -> None:\n    # readline is not included in Windows Active Python and perhaps some other distss\n    if sys.modules.get(\"readline\"):\n        import readline\n\n        readline.set_completer_delims(\" \\t\\n;\")\n        readline.parse_and_bind(\"tab: complete\")\n        readline.set_completer(_autocomplete_path)\n\n\ndef _autocomplete_path(text: str, state: int) -> list[str | None]:\n    expansions = glob.glob(expand_path(text) + \"*\")\n    expansions = [e + \"/\" if os.path.isdir(e) else e for e in expansions]\n    expansions.append(None)\n    return expansions[state]\n"
  },
  {
    "path": "jrnl/journals/DayOneJournal.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport contextlib\nimport datetime\nimport fnmatch\nimport os\nimport platform\nimport plistlib\nimport re\nimport socket\nimport time\nimport uuid\nimport zoneinfo\nfrom pathlib import Path\nfrom xml.parsers.expat import ExpatError\n\nimport tzlocal\n\nfrom jrnl import __title__\nfrom jrnl import __version__\n\nfrom .Entry import Entry\nfrom .Journal import Journal\n\n\nclass DayOne(Journal):\n    \"\"\"A special Journal handling DayOne files\"\"\"\n\n    # InvalidFileException was added to plistlib in Python3.4\n    PLIST_EXCEPTIONS = (\n        (ExpatError, plistlib.InvalidFileException)\n        if hasattr(plistlib, \"InvalidFileException\")\n        else ExpatError\n    )\n\n    def __init__(self, **kwargs):\n        self.entries = []\n        self._deleted_entries = []\n        self.can_be_encrypted = False\n        super().__init__(**kwargs)\n\n    def open(self) -> \"DayOne\":\n        filenames = []\n        for root, dirnames, f in os.walk(self.config[\"journal\"]):\n            for filename in fnmatch.filter(f, \"*.doentry\"):\n                filenames.append(os.path.join(root, filename))\n        self.entries = []\n        for filename in filenames:\n            with open(filename, \"rb\") as plist_entry:\n                try:\n                    dict_entry = plistlib.load(plist_entry, fmt=plistlib.FMT_XML)\n                except self.PLIST_EXCEPTIONS:\n                    pass\n                else:\n                    try:\n                        timezone = zoneinfo.ZoneInfo(dict_entry[\"Time Zone\"])\n                    except KeyError:\n                        timezone_name = str(tzlocal.get_localzone())\n                        timezone = zoneinfo.ZoneInfo(timezone_name)\n                    date = dict_entry[\"Creation Date\"]\n                    # convert the date to UTC rather than keep messing with\n                    # timezones\n                    if timezone.key != \"UTC\":\n                        date = date.replace(fold=1) + timezone.utcoffset(date)\n\n                    entry = Entry(\n                        self,\n                        date,\n                        text=dict_entry[\"Entry Text\"],\n                        starred=dict_entry[\"Starred\"],\n                    )\n                    entry.uuid = dict_entry[\"UUID\"]\n                    entry._tags = [\n                        self.config[\"tagsymbols\"][0] + tag.lower()\n                        for tag in dict_entry.get(\"Tags\", [])\n                    ]\n                    if entry._tags:\n                        entry._tags.sort()\n\n                    \"\"\"Extended DayOne attributes\"\"\"\n                    # just ignore it if the keys don't exist\n                    with contextlib.suppress(KeyError):\n                        entry.creator_device_agent = dict_entry[\"Creator\"][\n                            \"Device Agent\"\n                        ]\n                        entry.creator_host_name = dict_entry[\"Creator\"][\"Host Name\"]\n                        entry.creator_os_agent = dict_entry[\"Creator\"][\"OS Agent\"]\n                        entry.creator_software_agent = dict_entry[\"Creator\"][\n                            \"Software Agent\"\n                        ]\n                        entry.location = dict_entry[\"Location\"]\n                        entry.weather = dict_entry[\"Weather\"]\n\n                    entry.creator_generation_date = dict_entry.get(\"Creator\", {}).get(\n                        \"Generation Date\", date\n                    )\n\n                    self.entries.append(entry)\n        self.sort()\n        return self\n\n    def write(self) -> None:\n        \"\"\"Writes only the entries that have been modified into plist files.\"\"\"\n        for entry in self.entries:\n            if entry.modified:\n                utc_time = datetime.datetime.utcfromtimestamp(\n                    time.mktime(entry.date.timetuple())\n                )\n\n                if not hasattr(entry, \"uuid\"):\n                    entry.uuid = uuid.uuid1().hex\n                if not hasattr(entry, \"creator_device_agent\"):\n                    entry.creator_device_agent = \"\"  # iPhone/iPhone5,3\n                if not hasattr(entry, \"creator_generation_date\"):\n                    entry.creator_generation_date = utc_time\n                if not hasattr(entry, \"creator_host_name\"):\n                    entry.creator_host_name = socket.gethostname()\n                if not hasattr(entry, \"creator_os_agent\"):\n                    entry.creator_os_agent = \"{}/{}\".format(\n                        platform.system(), platform.release()\n                    )\n                if not hasattr(entry, \"creator_software_agent\"):\n                    entry.creator_software_agent = \"{}/{}\".format(\n                        __title__, __version__\n                    )\n\n                fn = (\n                    Path(self.config[\"journal\"])\n                    / \"entries\"\n                    / (entry.uuid.upper() + \".doentry\")\n                )\n\n                entry_plist = {\n                    \"Creation Date\": utc_time,\n                    \"Starred\": entry.starred if hasattr(entry, \"starred\") else False,\n                    \"Entry Text\": entry.title + \"\\n\" + entry.body,\n                    \"Time Zone\": str(tzlocal.get_localzone()),\n                    \"UUID\": entry.uuid.upper(),\n                    \"Tags\": [\n                        tag.strip(self.config[\"tagsymbols\"]).replace(\"_\", \" \")\n                        for tag in entry.tags\n                    ],\n                    \"Creator\": {\n                        \"Device Agent\": entry.creator_device_agent,\n                        \"Generation Date\": entry.creator_generation_date,\n                        \"Host Name\": entry.creator_host_name,\n                        \"OS Agent\": entry.creator_os_agent,\n                        \"Software Agent\": entry.creator_software_agent,\n                    },\n                }\n                if hasattr(entry, \"location\"):\n                    entry_plist[\"Location\"] = entry.location\n                if hasattr(entry, \"weather\"):\n                    entry_plist[\"Weather\"] = entry.weather\n\n                # plistlib expects a binary object\n                with fn.open(mode=\"wb\") as f:\n                    plistlib.dump(entry_plist, f, fmt=plistlib.FMT_XML, sort_keys=False)\n\n        for entry in self._deleted_entries:\n            filename = os.path.join(\n                self.config[\"journal\"], \"entries\", entry.uuid + \".doentry\"\n            )\n            os.remove(filename)\n\n    def editable_str(self) -> str:\n        \"\"\"Turns the journal into a string of entries that can be edited\n        manually and later be parsed with eslf.parse_editable_str.\"\"\"\n        return \"\\n\".join([f\"{str(e)}\\n# {e.uuid}\\n\" for e in self.entries])\n\n    def _update_old_entry(self, entry: Entry, new_entry: Entry) -> None:\n        for attr in (\"title\", \"body\", \"date\", \"tags\"):\n            old_attr = getattr(entry, attr)\n            new_attr = getattr(new_entry, attr)\n            if old_attr != new_attr:\n                entry.modified = True\n                setattr(entry, attr, new_attr)\n\n    def _get_and_remove_uuid_from_entry(self, entry: Entry) -> Entry:\n        uuid_regex = \"^ *?# ([a-zA-Z0-9]+) *?$\"\n        m = re.search(uuid_regex, entry.body, re.MULTILINE)\n        entry.uuid = m.group(1) if m else None\n\n        # remove the uuid from the body\n        entry.body = re.sub(uuid_regex, \"\", entry.body, flags=re.MULTILINE, count=1)\n        entry.body = entry.body.rstrip()\n\n        return entry\n\n    def parse_editable_str(self, edited: str) -> None:\n        \"\"\"Parses the output of self.editable_str and updates its entries.\"\"\"\n        # Method: create a new list of entries from the edited text, then match\n        # UUIDs of the new entries against self.entries, updating the entries\n        # if the edited entries differ, and deleting entries from self.entries\n        # if they don't show up in the edited entries anymore.\n        entries_from_editor = self._parse(edited)\n\n        for entry in entries_from_editor:\n            entry = self._get_and_remove_uuid_from_entry(entry)\n            if entry._tags:\n                entry._tags.sort()\n\n        # Remove deleted entries\n        edited_uuids = [e.uuid for e in entries_from_editor]\n        self._deleted_entries = [e for e in self.entries if e.uuid not in edited_uuids]\n        self.entries[:] = [e for e in self.entries if e.uuid in edited_uuids]\n\n        for entry in entries_from_editor:\n            for old_entry in self.entries:\n                if entry.uuid == old_entry.uuid:\n                    if old_entry._tags:\n                        tags_not_in_body = [\n                            tag for tag in old_entry._tags if (tag not in entry._body)\n                        ]\n                        if tags_not_in_body:\n                            entry._tags.extend(tags_not_in_body.sort())\n                    self._update_old_entry(old_entry, entry)\n                    break\n"
  },
  {
    "path": "jrnl/journals/Entry.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport datetime\nimport logging\nimport os\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.color import colorize\nfrom jrnl.color import highlight_tags_with_background_color\nfrom jrnl.output import wrap_with_ansi_colors\n\nif TYPE_CHECKING:\n    from .Journal import Journal\n\n\nclass Entry:\n    def __init__(\n        self,\n        journal: \"Journal\",\n        date: datetime.datetime | None = None,\n        text: str = \"\",\n        starred: bool = False,\n    ):\n        self.journal = journal  # Reference to journal mainly to access its config\n        self.date = date or datetime.datetime.now()\n        self.text = text\n        self._title = None\n        self._body = None\n        self._tags = None\n        self.starred = starred\n        self.modified = False\n\n    @property\n    def fulltext(self) -> str:\n        return self.title + \" \" + self.body\n\n    def _parse_text(self):\n        raw_text = self.text\n        lines = raw_text.splitlines()\n        if lines and lines[0].strip().endswith(\"*\"):\n            self.starred = True\n            raw_text = lines[0].strip(\"\\n *\") + \"\\n\" + \"\\n\".join(lines[1:])\n        self._title, self._body = split_title(raw_text)\n        if self._tags is None:\n            self._tags = list(self._parse_tags())\n\n    @property\n    def title(self) -> str:\n        if self._title is None:\n            self._parse_text()\n        return self._title\n\n    @title.setter\n    def title(self, x: str):\n        self._title = x\n\n    @property\n    def body(self) -> str:\n        if self._body is None:\n            self._parse_text()\n        return self._body\n\n    @body.setter\n    def body(self, x: str):\n        self._body = x\n\n    @property\n    def tags(self) -> list[str]:\n        if self._tags is None:\n            self._parse_text()\n        return self._tags\n\n    @tags.setter\n    def tags(self, x: list[str]):\n        self._tags = x\n\n    @staticmethod\n    def tag_regex(tagsymbols: str) -> re.Pattern:\n        pattern = rf\"(?<!\\S)([{tagsymbols}][-+*#/\\w]+)\"\n        return re.compile(pattern)\n\n    def _parse_tags(self) -> set[str]:\n        tagsymbols = self.journal.config[\"tagsymbols\"]\n        return {\n            tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text)\n        }\n\n    def __str__(self):\n        \"\"\"Returns string representation of the entry to be written to journal file.\"\"\"\n        date_str = self.date.strftime(self.journal.config[\"timeformat\"])\n        title = \"[{}] {}\".format(date_str, self.title.rstrip(\"\\n \"))\n        if self.starred:\n            title += \" *\"\n        return \"{title}{sep}{body}\\n\".format(\n            title=title,\n            sep=\"\\n\" if self.body.rstrip(\"\\n \") else \"\",\n            body=self.body.rstrip(\"\\n \"),\n        )\n\n    def pprint(self, short: bool = False) -> str:\n        \"\"\"Returns a pretty-printed version of the entry.\n        If short is true, only print the title.\"\"\"\n        # Handle indentation\n        if self.journal.config[\"indent_character\"]:\n            indent = self.journal.config[\"indent_character\"].rstrip() + \" \"\n        else:\n            indent = \"\"\n\n        date_str = colorize(\n            self.date.strftime(self.journal.config[\"timeformat\"]),\n            self.journal.config[\"colors\"][\"date\"],\n            bold=True,\n        )\n\n        if not short and self.journal.config[\"linewrap\"]:\n            columns = self.journal.config[\"linewrap\"]\n\n            if columns == \"auto\":\n                try:\n                    columns = os.get_terminal_size().columns\n                except OSError:\n                    logging.debug(\n                        \"Can't determine terminal size automatically 'linewrap': '%s'\",\n                        self.journal.config[\"linewrap\"],\n                    )\n                    columns = 79\n\n            # Color date / title and bold title\n            title = wrap_with_ansi_colors(\n                date_str\n                + \" \"\n                + highlight_tags_with_background_color(\n                    self,\n                    self.title,\n                    self.journal.config[\"colors\"][\"title\"],\n                    is_title=True,\n                ),\n                columns,\n            )\n            body = highlight_tags_with_background_color(\n                self, self.body.rstrip(\" \\n\"), self.journal.config[\"colors\"][\"body\"]\n            )\n\n            body = wrap_with_ansi_colors(body, columns - len(indent))\n            if indent:\n                # Without explicitly colorizing the indent character, it will lose its\n                # color after a tag appears.\n                body = \"\\n\".join(\n                    colorize(indent, self.journal.config[\"colors\"][\"body\"]) + line\n                    for line in body.splitlines()\n                )\n\n            body = colorize(body, self.journal.config[\"colors\"][\"body\"])\n        else:\n            title = (\n                date_str\n                + \" \"\n                + highlight_tags_with_background_color(\n                    self,\n                    self.title.rstrip(\"\\n\"),\n                    self.journal.config[\"colors\"][\"title\"],\n                    is_title=True,\n                )\n            )\n            body = highlight_tags_with_background_color(\n                self, self.body.rstrip(\"\\n \"), self.journal.config[\"colors\"][\"body\"]\n            )\n\n        # Suppress bodies that are just blanks and new lines.\n        has_body = len(self.body) > 20 or not all(\n            char in (\" \", \"\\n\") for char in self.body\n        )\n\n        if short:\n            return title\n        else:\n            return \"{title}{sep}{body}\\n\".format(\n                title=title, sep=\"\\n\" if has_body else \"\", body=body if has_body else \"\"\n            )\n\n    def __repr__(self):\n        return \"<Entry '{}' on {}>\".format(\n            self.title.strip(), self.date.strftime(\"%Y-%m-%d %H:%M\")\n        )\n\n    def __hash__(self):\n        return hash(self.__repr__())\n\n    def __eq__(self, other: \"Entry\"):\n        if (\n            not isinstance(other, Entry)\n            or self.title.strip() != other.title.strip()\n            or self.body.rstrip() != other.body.rstrip()\n            or self.date != other.date\n            or self.starred != other.starred\n        ):\n            return False\n        return True\n\n    def __ne__(self, other: \"Entry\"):\n        return not self.__eq__(other)\n\n\n# Based on Segtok by Florian Leitner\n# https://github.com/fnl/segtok\nSENTENCE_SPLITTER = re.compile(\n    r\"\"\"\n    (\n    [.!?\\u2026\\u203C\\u203D\\u2047\\u2048\\u2049\\u22EF\\uFE52\\uFE57] # Sequence starting with a sentence terminal,\n    [\\'\\u2019\\\"\\u201D]? # an optional right quote,\n    [\\]\\)]*             # optional closing bracket\n    \\s+                 # AND a sequence of required spaces.\n    )\n    |[\\uFF01\\uFF0E\\uFF1F\\uFF61\\u3002] # CJK full/half width terminals usually do not have following spaces.\n    \"\"\",  # noqa: E501\n    re.VERBOSE,\n)\n\nSENTENCE_SPLITTER_ONLY_NEWLINE = re.compile(\"\\n\")\n\n\ndef split_title(text: str) -> tuple[str, str]:\n    \"\"\"Splits the first sentence off from a text.\"\"\"\n    sep = SENTENCE_SPLITTER_ONLY_NEWLINE.search(text.lstrip())\n    if not sep:\n        sep = SENTENCE_SPLITTER.search(text)\n        if not sep:\n            return text, \"\"\n    return text[: sep.end()].strip(), text[sep.end() :].strip()\n"
  },
  {
    "path": "jrnl/journals/FolderJournal.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport codecs\nimport os\nimport pathlib\nfrom typing import TYPE_CHECKING\n\nfrom jrnl import time\n\nfrom .Journal import Journal\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n\n# glob search patterns for folder/file structure\nDIGIT_PATTERN = \"[0123456789]\"\nYEAR_PATTERN = DIGIT_PATTERN * 4\nMONTH_PATTERN = \"[01]\" + DIGIT_PATTERN\nDAY_PATTERN = \"[0123]\" + DIGIT_PATTERN + \".txt\"\n\n\nclass Folder(Journal):\n    \"\"\"A Journal handling multiple files in a folder\"\"\"\n\n    def __init__(self, name: str = \"default\", **kwargs):\n        self.entries = []\n        self._diff_entry_dates = []\n        self.can_be_encrypted = False\n        super().__init__(name, **kwargs)\n\n    def open(self) -> \"Folder\":\n        filenames = []\n        self.entries = []\n\n        if os.path.exists(self.config[\"journal\"]):\n            filenames = Folder._get_files(self.config[\"journal\"])\n            for filename in filenames:\n                with codecs.open(filename, \"r\", \"utf-8\") as f:\n                    journal = f.read()\n                    self.entries.extend(self._parse(journal))\n            self.sort()\n\n        return self\n\n    def write(self) -> None:\n        \"\"\"Writes only the entries that have been modified into proper files.\"\"\"\n        # Create a list of dates of modified entries. Start with diff_entry_dates\n        modified_dates = self._diff_entry_dates\n        seen_dates = set(self._diff_entry_dates)\n\n        for e in self.entries:\n            if e.modified:\n                if e.date not in modified_dates:\n                    modified_dates.append(e.date)\n                if e.date not in seen_dates:\n                    seen_dates.add(e.date)\n\n        # For every date that had a modified entry, write to a file\n        for d in modified_dates:\n            write_entries = []\n            filename = os.path.join(\n                self.config[\"journal\"],\n                d.strftime(\"%Y\"),\n                d.strftime(\"%m\"),\n                d.strftime(\"%d\") + \".txt\",\n            )\n            dirname = os.path.dirname(filename)\n            # create directory if it doesn't exist\n            if not os.path.exists(dirname):\n                os.makedirs(dirname)\n            for e in self.entries:\n                if (\n                    e.date.year == d.year\n                    and e.date.month == d.month\n                    and e.date.day == d.day\n                ):\n                    write_entries.append(e)\n            journal = \"\\n\".join([e.__str__() for e in write_entries])\n            with codecs.open(filename, \"w\", \"utf-8\") as journal_file:\n                journal_file.write(journal)\n        # look for and delete empty files\n        filenames = []\n        filenames = Folder._get_files(self.config[\"journal\"])\n        for filename in filenames:\n            if os.stat(filename).st_size <= 0:\n                os.remove(filename)\n\n    def delete_entries(self, entries_to_delete: list[\"Entry\"]) -> None:\n        \"\"\"Deletes specific entries from a journal.\"\"\"\n        for entry in entries_to_delete:\n            self.entries.remove(entry)\n            self._diff_entry_dates.append(entry.date)\n            self.deleted_entry_count += 1\n\n    def change_date_entries(self, date: str, entries_to_change: list[\"Entry\"]) -> None:\n        \"\"\"Changes entry dates to given date.\"\"\"\n\n        date = time.parse(date)\n\n        self._diff_entry_dates.append(date)\n\n        for entry in entries_to_change:\n            self._diff_entry_dates.append(entry.date)\n            entry.date = date\n            entry.modified = True\n\n    def parse_editable_str(self, edited: str) -> None:\n        \"\"\"Parses the output of self.editable_str and updates its entries.\"\"\"\n        mod_entries = self._parse(edited)\n        diff_entries = set(self.entries) - set(mod_entries)\n        for e in diff_entries:\n            self._diff_entry_dates.append(e.date)\n        # Match those entries that can be found in self.entries and set\n        # these to modified, so we can get a count of how many entries got\n        # modified and how many got deleted later.\n        for entry in mod_entries:\n            entry.modified = not any(entry == old_entry for old_entry in self.entries)\n\n        self.increment_change_counts_by_edit(mod_entries)\n        self.entries = mod_entries\n\n    @staticmethod\n    def _get_files(journal_path: str) -> list[str]:\n        \"\"\"Searches through sub directories starting with journal_path and find all text\n        files that look like entries\"\"\"\n        for year_folder in Folder._get_year_folders(pathlib.Path(journal_path)):\n            for month_folder in Folder._get_month_folders(year_folder):\n                yield from Folder._get_day_files(month_folder)\n\n    @staticmethod\n    def _get_year_folders(path: pathlib.Path) -> list[pathlib.Path]:\n        for child in path.glob(YEAR_PATTERN):\n            if child.is_dir():\n                yield child\n        return\n\n    @staticmethod\n    def _get_month_folders(path: pathlib.Path) -> list[pathlib.Path]:\n        for child in path.glob(MONTH_PATTERN):\n            if int(child.name) > 0 and int(child.name) <= 12 and path.is_dir():\n                yield child\n        return\n\n    @staticmethod\n    def _get_day_files(path: pathlib.Path) -> list[str]:\n        for child in path.glob(DAY_PATTERN):\n            if (\n                int(child.stem) > 0\n                and int(child.stem) <= 31\n                and time.is_valid_date(\n                    year=int(path.parent.name),\n                    month=int(path.name),\n                    day=int(child.stem),\n                )\n                and child.is_file()\n            ):\n                yield str(child)\n"
  },
  {
    "path": "jrnl/journals/Journal.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport datetime\nimport logging\nimport os\nimport re\n\nfrom jrnl import time\nfrom jrnl.config import validate_journal_name\nfrom jrnl.encryption import determine_encryption_method\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\nfrom jrnl.path import expand_path\nfrom jrnl.prompt import yesno\n\nfrom .Entry import Entry\n\n\nclass Tag:\n    def __init__(self, name, count=0):\n        self.name = name\n        self.count = count\n\n    def __str__(self):\n        return self.name\n\n    def __repr__(self):\n        return f\"<Tag '{self.name}'>\"\n\n\nclass Journal:\n    def __init__(self, name=\"default\", **kwargs):\n        self.config = {\n            \"journal\": \"journal.txt\",\n            \"encrypt\": False,\n            \"default_hour\": 9,\n            \"default_minute\": 0,\n            \"timeformat\": \"%Y-%m-%d %H:%M\",\n            \"tagsymbols\": \"@\",\n            \"highlight\": True,\n            \"linewrap\": 80,\n            \"indent_character\": \"|\",\n        }\n        self.config.update(kwargs)\n        # Set up date parser\n        self.search_tags = None  # Store tags we're highlighting\n        self.name = name\n        self.entries = []\n        self.encryption_method = None\n\n        # Track changes to journal in session. Modified is tracked in Entry\n        self.added_entry_count = 0\n        self.deleted_entry_count = 0\n\n    def __len__(self):\n        \"\"\"Returns the number of entries\"\"\"\n        return len(self.entries)\n\n    def __iter__(self):\n        \"\"\"Iterates over the journal's entries.\"\"\"\n        return (entry for entry in self.entries)\n\n    @classmethod\n    def from_journal(cls, other: \"Journal\") -> \"Journal\":\n        \"\"\"Creates a new journal by copying configuration and entries from\n        another journal object\"\"\"\n        new_journal = cls(other.name, **other.config)\n        new_journal.entries = other.entries\n        logging.debug(\n            \"Imported %d entries from %s to %s\",\n            len(new_journal),\n            other.__class__.__name__,\n            cls.__name__,\n        )\n        return new_journal\n\n    def import_(self, other_journal_txt: str) -> None:\n        imported_entries = self._parse(other_journal_txt)\n        for entry in imported_entries:\n            entry.modified = True\n\n        self.entries = list(frozenset(self.entries) | frozenset(imported_entries))\n        self.sort()\n\n    def _get_encryption_method(self) -> None:\n        encryption_method = determine_encryption_method(self.config[\"encrypt\"])\n        self.encryption_method = encryption_method(self.name, self.config)\n\n    def _decrypt(self, text: bytes) -> str:\n        if self.encryption_method is None:\n            self._get_encryption_method()\n\n        return self.encryption_method.decrypt(text)\n\n    def _encrypt(self, text: str) -> bytes:\n        if self.encryption_method is None:\n            self._get_encryption_method()\n\n        return self.encryption_method.encrypt(text)\n\n    def open(self, filename: str | None = None) -> \"Journal\":\n        \"\"\"Opens the journal file and parses it into a list of Entries\n        Entries have the form (date, title, body).\"\"\"\n        filename = filename or self.config[\"journal\"]\n        dirname = os.path.dirname(filename)\n        if not os.path.exists(filename):\n            if not os.path.isdir(dirname):\n                os.makedirs(dirname)\n                print_msg(\n                    Message(\n                        MsgText.DirectoryCreated,\n                        MsgStyle.NORMAL,\n                        {\"directory_name\": dirname},\n                    )\n                )\n            self.create_file(filename)\n            print_msg(\n                Message(\n                    MsgText.JournalCreated,\n                    MsgStyle.NORMAL,\n                    {\n                        \"journal_name\": self.name,\n                        \"filename\": filename,\n                    },\n                )\n            )\n            self.write()\n\n        text = self._load(filename)\n        text = self._decrypt(text)\n        self.entries = self._parse(text)\n        self.sort()\n        logging.debug(\"opened %s with %d entries\", self.__class__.__name__, len(self))\n        return self\n\n    def write(self, filename: str | None = None) -> None:\n        \"\"\"Dumps the journal into the config file, overwriting it\"\"\"\n        filename = filename or self.config[\"journal\"]\n        text = self._to_text()\n        text = self._encrypt(text)\n        self._store(filename, text)\n\n    def validate_parsing(self) -> bool:\n        \"\"\"Confirms that the jrnl is still parsed correctly after conversion to text.\"\"\"\n        new_entries = self._parse(self._to_text())\n        return all(entry == new_entries[i] for i, entry in enumerate(self.entries))\n\n    @staticmethod\n    def create_file(filename: str) -> None:\n        with open(filename, \"w\"):\n            pass\n\n    def _to_text(self) -> str:\n        return \"\\n\".join([str(e) for e in self.entries])\n\n    def _load(self, filename: str) -> bytes:\n        with open(filename, \"rb\") as f:\n            return f.read()\n\n    def _store(self, filename: str, text: bytes) -> None:\n        with open(filename, \"wb\") as f:\n            f.write(text)\n\n    def _parse(self, journal_txt: str) -> list[Entry]:\n        \"\"\"Parses a journal that's stored in a string and returns a list of entries\"\"\"\n\n        # Return empty array if the journal is blank\n        if not journal_txt:\n            return []\n\n        # Initialise our current entry\n        entries = []\n\n        date_blob_re = re.compile(\"(?:^|\\n)\\\\[([^\\\\]]+)\\\\] \")\n        last_entry_pos = 0\n        for match in date_blob_re.finditer(journal_txt):\n            date_blob = match.groups()[0]\n            try:\n                new_date = datetime.datetime.strptime(\n                    date_blob, self.config[\"timeformat\"]\n                )\n            except ValueError:\n                # Passing in a date that had brackets around it\n                new_date = time.parse(date_blob, bracketed=True)\n\n            if new_date:\n                if entries:\n                    entries[-1].text = journal_txt[last_entry_pos : match.start()]\n                last_entry_pos = match.end()\n                entries.append(Entry(self, date=new_date))\n\n        # If no entries were found, treat all the existing text as an entry made now\n        if not entries:\n            entries.append(Entry(self, date=time.parse(\"now\")))\n\n        # Fill in the text of the last entry\n        entries[-1].text = journal_txt[last_entry_pos:]\n\n        for entry in entries:\n            entry._parse_text()\n        return entries\n\n    def pprint(self, short: bool = False) -> str:\n        \"\"\"Prettyprints the journal's entries\"\"\"\n        return \"\\n\".join([e.pprint(short=short) for e in self.entries])\n\n    def __str__(self):\n        return self.pprint()\n\n    def __repr__(self):\n        return f\"<Journal with {len(self.entries)} entries>\"\n\n    def sort(self) -> None:\n        \"\"\"Sorts the Journal's entries by date\"\"\"\n        self.entries = sorted(self.entries, key=lambda entry: entry.date)\n\n    def limit(self, n: int | None = None) -> None:\n        \"\"\"Removes all but the last n entries\"\"\"\n        if n:\n            self.entries = self.entries[-n:]\n\n    @property\n    def tags(self) -> list[Tag]:\n        \"\"\"Returns a set of tuples (count, tag) for all tags present in the journal.\"\"\"\n        # Astute reader: should the following line leave you as puzzled as me the first\n        # time I came across this construction, worry not and embrace the ensuing moment\n        # of enlightment.\n        tags = [tag for entry in self.entries for tag in set(entry.tags)]\n        # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag]\n        tag_counts = {(tags.count(tag), tag) for tag in tags}\n        return [Tag(tag, count=count) for count, tag in sorted(tag_counts)]\n\n    def filter(\n        self,\n        tags=[],\n        month=None,\n        day=None,\n        year=None,\n        start_date=None,\n        end_date=None,\n        starred=False,\n        tagged=False,\n        exclude_starred=False,\n        exclude_tagged=False,\n        strict=False,\n        contains=[],\n        exclude=[],\n    ):\n        \"\"\"Removes all entries from the journal that don't match the filter.\n\n        tags is a list of tags, each being a string that starts with one of the\n        tag symbols defined in the config, e.g. [\"@John\", \"#WorldDomination\"].\n\n        start_date and end_date define a timespan by which to filter.\n\n        starred limits journal to starred entries\n\n        If strict is True, all tags must be present in an entry. If false, the\n\n        exclude is a list of the tags which should not appear in the results.\n        entry is kept if any tag is present, unless they appear in exclude.\"\"\"\n        self.search_tags = {tag.lower() for tag in tags}\n        excluded_tags = {tag.lower() for tag in exclude}\n        end_date = time.parse(end_date, inclusive=True)\n        start_date = time.parse(start_date)\n\n        # If strict mode is on, all tags have to be present in entry\n        has_tags = (\n            self.search_tags.issubset if strict else self.search_tags.intersection\n        )\n\n        def excluded(tags):\n            return 0 < len([tag for tag in tags if tag in excluded_tags])\n\n        if contains:\n            contains_lower = [substring.casefold() for substring in contains]\n\n        # Create datetime object for comparison below\n        # this approach allows various formats\n        if month or day or year:\n            compare_d = time.parse(f\"{month or 1}.{day or 1}.{year or 1}\")\n\n        result = [\n            entry\n            for entry in self.entries\n            if (not tags or has_tags(entry.tags))\n            and (not (starred or exclude_starred) or entry.starred == starred)\n            and (not (tagged or exclude_tagged) or bool(entry.tags) == tagged)\n            and (not month or entry.date.month == compare_d.month)\n            and (not day or entry.date.day == compare_d.day)\n            and (not year or entry.date.year == compare_d.year)\n            and (not start_date or entry.date >= start_date)\n            and (not end_date or entry.date <= end_date)\n            and (not exclude or not excluded(entry.tags))\n            and (\n                not contains\n                or (\n                    strict\n                    and all(\n                        substring in entry.title.casefold()\n                        or substring in entry.body.casefold()\n                        for substring in contains_lower\n                    )\n                )\n                or (\n                    not strict\n                    and any(\n                        substring in entry.title.casefold()\n                        or substring in entry.body.casefold()\n                        for substring in contains_lower\n                    )\n                )\n            )\n        ]\n\n        self.entries = result\n\n    def delete_entries(self, entries_to_delete: list[Entry]) -> None:\n        \"\"\"Deletes specific entries from a journal.\"\"\"\n        for entry in entries_to_delete:\n            self.entries.remove(entry)\n            self.deleted_entry_count += 1\n\n    def change_date_entries(\n        self, date: datetime.datetime, entries_to_change: list[Entry]\n    ) -> None:\n        \"\"\"Changes entry dates to given date.\"\"\"\n        date = time.parse(date)\n\n        for entry in entries_to_change:\n            entry.date = date\n            entry.modified = True\n\n    def prompt_action_entries(self, msg: MsgText) -> list[Entry]:\n        \"\"\"Prompts for action for each entry in a journal, using given message.\n        Returns the entries the user wishes to apply the action on.\"\"\"\n        to_act = []\n\n        def ask_action(entry):\n            return yesno(\n                Message(\n                    msg,\n                    params={\"entry_title\": entry.pprint(short=True)},\n                ),\n                default=False,\n            )\n\n        for entry in self.entries:\n            if ask_action(entry):\n                to_act.append(entry)\n\n        return to_act\n\n    def new_entry(self, raw: str, date=None, sort: bool = True) -> Entry:\n        \"\"\"Constructs a new entry from some raw text input.\n        If a date is given, it will parse and use this, otherwise scan for a date in\n        the input first.\n        \"\"\"\n\n        raw = raw.replace(\"\\\\n \", \"\\n\").replace(\"\\\\n\", \"\\n\")\n        # Split raw text into title and body\n        sep = re.search(r\"\\n|[?!.]+ +\\n?\", raw)\n        first_line = raw[: sep.end()].strip() if sep else raw\n        starred = False\n\n        if not date:\n            colon_pos = first_line.find(\": \")\n            if colon_pos > 0:\n                date = time.parse(\n                    raw[:colon_pos],\n                    default_hour=self.config[\"default_hour\"],\n                    default_minute=self.config[\"default_minute\"],\n                )\n                if date:  # Parsed successfully, strip that from the raw text\n                    starred = raw[:colon_pos].strip().endswith(\"*\")\n                    raw = raw[colon_pos + 1 :].strip()\n        starred = (\n            starred\n            or first_line.startswith(\"*\")\n            or first_line.endswith(\"*\")\n            or raw.startswith(\"*\")\n        )\n        if not date:  # Still nothing? Meh, just live in the moment.\n            date = time.parse(\"now\")\n        entry = Entry(self, date, raw, starred=starred)\n        entry.modified = True\n        self.entries.append(entry)\n        if sort:\n            self.sort()\n        return entry\n\n    def editable_str(self) -> str:\n        \"\"\"Turns the journal into a string of entries that can be edited\n        manually and later be parsed with self.parse_editable_str.\"\"\"\n        return \"\\n\".join([str(e) for e in self.entries])\n\n    def parse_editable_str(self, edited: str) -> None:\n        \"\"\"Parses the output of self.editable_str and updates it's entries.\"\"\"\n        mod_entries = self._parse(edited)\n        # Match those entries that can be found in self.entries and set\n        # these to modified, so we can get a count of how many entries got\n        # modified and how many got deleted later.\n        for entry in mod_entries:\n            entry.modified = not any(entry == old_entry for old_entry in self.entries)\n\n        self.increment_change_counts_by_edit(mod_entries)\n\n        self.entries = mod_entries\n\n    def increment_change_counts_by_edit(self, mod_entries: Entry) -> None:\n        if len(mod_entries) > len(self.entries):\n            self.added_entry_count += len(mod_entries) - len(self.entries)\n        else:\n            self.deleted_entry_count += len(self.entries) - len(mod_entries)\n\n    def get_change_counts(self) -> dict:\n        return {\n            \"added\": self.added_entry_count,\n            \"deleted\": self.deleted_entry_count,\n            \"modified\": len([e for e in self.entries if e.modified]),\n        }\n\n\nclass LegacyJournal(Journal):\n    \"\"\"Legacy class to support opening journals formatted with the jrnl 1.x\n    standard. Main difference here is that in 1.x, timestamps were not cuddled\n    by square brackets. You'll not be able to save these journals anymore.\"\"\"\n\n    def _parse(self, journal_txt: str) -> list[Entry]:\n        \"\"\"Parses a journal that's stored in a string and returns a list of entries\"\"\"\n        # Entries start with a line that looks like 'date title' - let's figure out how\n        # long the date will be by constructing one\n        date_length = len(datetime.datetime.today().strftime(self.config[\"timeformat\"]))\n\n        # Initialise our current entry\n        entries = []\n        current_entry = None\n        new_date_format_regex = re.compile(r\"(^\\[[^\\]]+\\].*?$)\")\n        for line in journal_txt.splitlines():\n            line = line.rstrip()\n            try:\n                # try to parse line as date => new entry begins\n                new_date = datetime.datetime.strptime(\n                    line[:date_length], self.config[\"timeformat\"]\n                )\n\n                # parsing successful => save old entry and create new one\n                if new_date and current_entry:\n                    entries.append(current_entry)\n\n                if line.endswith(\"*\"):\n                    starred = True\n                    line = line[:-1]\n                else:\n                    starred = False\n\n                current_entry = Entry(\n                    self, date=new_date, text=line[date_length + 1 :], starred=starred\n                )\n            except ValueError:\n                # Happens when we can't parse the start of the line as an date.\n                # In this case, just append line to our body (after some\n                # escaping for the new format).\n                line = new_date_format_regex.sub(r\" \\1\", line)\n                if current_entry:\n                    current_entry.text += line + \"\\n\"\n\n        # Append last entry\n        if current_entry:\n            entries.append(current_entry)\n        for entry in entries:\n            entry._parse_text()\n        return entries\n\n\ndef open_journal(journal_name: str, config: dict, legacy: bool = False) -> Journal:\n    \"\"\"\n    Creates a normal, encrypted or DayOne journal based on the passed config.\n    If legacy is True, it will open Journals with legacy classes build for\n    backwards compatibility with jrnl 1.x\n    \"\"\"\n    logging.debug(f\"open_journal '{journal_name}'\")\n    validate_journal_name(journal_name, config)\n    config = config.copy()\n    config[\"journal\"] = expand_path(config[\"journal\"])\n\n    if os.path.isdir(config[\"journal\"]):\n        if config[\"encrypt\"]:\n            print_msg(\n                Message(\n                    MsgText.ConfigEncryptedForUnencryptableJournalType,\n                    MsgStyle.WARNING,\n                    {\n                        \"journal_name\": journal_name,\n                    },\n                )\n            )\n\n        if config[\"journal\"].strip(\"/\").endswith(\".dayone\") or \"entries\" in os.listdir(\n            config[\"journal\"]\n        ):\n            from jrnl.journals import DayOne\n\n            return DayOne(**config).open()\n        else:\n            from jrnl.journals import Folder\n\n            return Folder(journal_name, **config).open()\n\n    if not config[\"encrypt\"]:\n        if legacy:\n            return LegacyJournal(journal_name, **config).open()\n        if config[\"journal\"].endswith(os.sep):\n            from jrnl.journals import Folder\n\n            return Folder(journal_name, **config).open()\n        return Journal(journal_name, **config).open()\n\n    if legacy:\n        config[\"encrypt\"] = \"jrnlv1\"\n        return LegacyJournal(journal_name, **config).open()\n    return Journal(journal_name, **config).open()\n"
  },
  {
    "path": "jrnl/journals/__init__.py",
    "content": "from .DayOneJournal import DayOne\nfrom .Entry import Entry\nfrom .FolderJournal import Folder\nfrom .Journal import Journal\nfrom .Journal import open_journal\n"
  },
  {
    "path": "jrnl/keyring.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport keyring\n\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\n\n\ndef get_keyring_password(journal_name: str = \"default\") -> str | None:\n    try:\n        return keyring.get_password(\"jrnl\", journal_name)\n    except keyring.errors.KeyringError as e:\n        if not isinstance(e, keyring.errors.NoKeyringError):\n            print_msg(Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR))\n        return None\n\n\ndef set_keyring_password(password: str, journal_name: str = \"default\") -> None:\n    try:\n        return keyring.set_password(\"jrnl\", journal_name, password)\n    except keyring.errors.KeyringError as e:\n        if isinstance(e, keyring.errors.NoKeyringError):\n            msg = Message(MsgText.KeyringBackendNotFound, MsgStyle.WARNING)\n        else:\n            msg = Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR)\n        print_msg(msg)\n"
  },
  {
    "path": "jrnl/main.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\nimport sys\nimport traceback\n\nfrom rich.logging import RichHandler\n\nfrom jrnl import controller\nfrom jrnl.args import parse_args\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\n\n\ndef configure_logger(debug: bool = False) -> None:\n    if not debug:\n        logging.disable()\n        return\n\n    logging.basicConfig(\n        level=logging.DEBUG,\n        datefmt=\"[%X]\",\n        format=\"%(message)s\",\n        handlers=[RichHandler()],\n    )\n    logging.getLogger(\"parsedatetime\").setLevel(logging.INFO)\n    logging.getLogger(\"keyring.backend\").setLevel(logging.ERROR)\n    logging.debug(\"Logging start\")\n\n\ndef run(manual_args: list[str] | None = None) -> int:\n    try:\n        if manual_args is None:\n            manual_args = sys.argv[1:]\n\n        args = parse_args(manual_args)\n        configure_logger(args.debug)\n        logging.debug(\"Parsed args:\\n%s\", args)\n\n        status_code = controller.run(args)\n\n    except JrnlException as e:\n        status_code = 1\n        e.print()\n\n    except KeyboardInterrupt:\n        status_code = 1\n\n        print_msg(\n            Message(\n                MsgText.KeyboardInterruptMsg,\n                MsgStyle.ERROR_ON_NEW_LINE,\n            )\n        )\n\n    except Exception as e:\n        # uncaught exception\n        status_code = 1\n        debug = False\n        try:\n            if args.debug:  # type: ignore\n                debug = True\n        except NameError:\n            # This should only happen when the exception\n            # happened before the args were parsed\n            if \"--debug\" in sys.argv:\n                debug = True\n\n        if debug:\n            from rich.console import Console\n\n            traceback.print_tb(sys.exc_info()[2])\n            Console(stderr=True).print_exception(extra_lines=1)\n\n        print_msg(\n            Message(\n                MsgText.UncaughtException,\n                MsgStyle.ERROR,\n                {\"name\": type(e).__name__, \"exception\": e},\n            )\n        )\n\n    # This should be the only exit point\n    return status_code\n"
  },
  {
    "path": "jrnl/messages/Message.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom typing import TYPE_CHECKING\nfrom typing import Mapping\nfrom typing import NamedTuple\n\nfrom jrnl.messages.MsgStyle import MsgStyle\n\nif TYPE_CHECKING:\n    from jrnl.messages.MsgText import MsgText\n\n\nclass Message(NamedTuple):\n    text: \"MsgText\"\n    style: MsgStyle = MsgStyle.NORMAL\n    params: Mapping = {}\n"
  },
  {
    "path": "jrnl/messages/MsgStyle.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom enum import Enum\nfrom typing import Callable\nfrom typing import NamedTuple\n\nfrom rich import box\nfrom rich.panel import Panel\n\nfrom jrnl.messages.MsgText import MsgText\n\n\nclass MsgStyle(Enum):\n    class _Color(NamedTuple):\n        \"\"\"\n        String representing a standard color to display\n        see: https://rich.readthedocs.io/en/stable/appendix/colors.html\n        \"\"\"\n\n        color: str\n\n    class _Decoration(Enum):\n        NONE = {\n            \"callback\": lambda x, **_: x,\n            \"args\": {},\n        }\n        BOX = {\n            \"callback\": Panel,\n            \"args\": {\n                \"expand\": False,\n                \"padding\": (0, 2),\n                \"title_align\": \"left\",\n                \"box\": box.HEAVY,\n            },\n        }\n\n        @property\n        def callback(self) -> Callable:\n            return self.value[\"callback\"]\n\n        @property\n        def args(self) -> dict:\n            return self.value[\"args\"]\n\n    PROMPT = {\n        \"decoration\": _Decoration.NONE,\n        \"color\": _Color(\"white\"),\n        \"append_space\": True,\n    }\n    TITLE = {\n        \"decoration\": _Decoration.BOX,\n        \"color\": _Color(\"cyan\"),\n    }\n    NORMAL = {\n        \"decoration\": _Decoration.BOX,\n        \"color\": _Color(\"white\"),\n    }\n    WARNING = {\n        \"decoration\": _Decoration.BOX,\n        \"color\": _Color(\"yellow\"),\n    }\n    ERROR = {\n        \"decoration\": _Decoration.BOX,\n        \"color\": _Color(\"red\"),\n        \"box_title\": str(MsgText.Error),\n    }\n    ERROR_ON_NEW_LINE = {\n        \"decoration\": _Decoration.BOX,\n        \"color\": _Color(\"red\"),\n        \"prepend_newline\": True,\n        \"box_title\": str(MsgText.Error),\n    }\n\n    @property\n    def decoration(self) -> _Decoration:\n        return self.value[\"decoration\"]\n\n    @property\n    def color(self) -> _Color:\n        return self.value[\"color\"].color\n\n    @property\n    def prepend_newline(self) -> bool:\n        return self.value.get(\"prepend_newline\", False)\n\n    @property\n    def append_space(self) -> bool:\n        return self.value.get(\"append_space\", False)\n\n    @property\n    def box_title(self) -> MsgText:\n        return self.value.get(\"box_title\")\n"
  },
  {
    "path": "jrnl/messages/MsgText.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom enum import Enum\n\n\nclass MsgText(Enum):\n    def __str__(self) -> str:\n        return self.value\n\n    # -- Welcome --- #\n    WelcomeToJrnl = \"\"\"\n        Welcome to jrnl {version}!\n\n        It looks like you've been using an older version of jrnl until now. That's\n        okay - jrnl will now upgrade your configuration and journal files. Afterwards\n        you can enjoy all of the great new features that come with jrnl 2:\n\n        - Support for storing your journal in multiple files\n        - Faster reading and writing for large journals\n        - New encryption back-end that makes installing jrnl much easier\n        - Tons of bug fixes\n\n        Please note that jrnl 1.x is NOT forward compatible with this version of jrnl.\n        If you choose to proceed, you will not be able to use your journals with\n        older versions of jrnl anymore.\n        \"\"\"\n\n    AllDoneUpgrade = \"We're all done here and you can start enjoying jrnl 2\"\n\n    InstallComplete = \"\"\"\n        jrnl configuration created at {config_path}\n        For advanced features, read the docs at https://jrnl.sh\n    \"\"\"\n\n    # --- Prompts --- #\n    InstallJournalPathQuestion = \"\"\"\n        Path to your journal file (leave blank for {default_journal_path}):\n        \"\"\"\n    DeleteEntryQuestion = \"Delete entry '{entry_title}'?\"\n    ChangeTimeEntryQuestion = \"Change time for '{entry_title}'?\"\n    EncryptJournalQuestion = \"\"\"\n        Do you want to encrypt your journal? (You can always change this later)\n        \"\"\"\n    UseColorsQuestion = \"\"\"\n        Do you want jrnl to use colors to display entries? (You can always change this later)\n        \"\"\"  # noqa: E501 - the line is still under 88 when dedented\n    YesOrNoPromptDefaultYes = \"[Y/n]\"\n    YesOrNoPromptDefaultNo = \"[y/N]\"\n    ContinueUpgrade = \"Continue upgrading jrnl?\"\n\n    # these should be lowercase, if possible in language\n    # \"lowercase\" means whatever `.lower()` returns\n    OneCharacterYes = \"y\"\n    OneCharacterNo = \"n\"\n\n    # --- Exceptions ---#\n    Error = \"Error\"\n    UncaughtException = \"\"\"\n        {name}\n        {exception}\n\n        This is probably a bug. Please file an issue at:\n        https://github.com/jrnl-org/jrnl/issues/new/choose\n        \"\"\"\n\n    ConfigDirectoryIsFile = \"\"\"\n        Problem with config file!\n        The path to your jrnl configuration directory is a file, not a directory:\n\n        {config_directory_path}\n\n        Removing this file will allow jrnl to save its configuration.\n        \"\"\"\n\n    CantParseConfigFile = \"\"\"\n        Unable to parse config file at:\n        {config_path}\n        \"\"\"\n\n    LineWrapTooSmallForDateFormat = \"\"\"\n        The provided linewrap value of {config_linewrap} is too small by\n        {columns} columns to display the timestamps in the configured time\n        format for journal {journal}.\n\n        You can avoid this error by specifying a linewrap value that is larger\n        by at least {columns} in the configuration file or by using\n        --config-override at the command line\n        \"\"\"\n\n    CannotEncryptJournalType = \"\"\"\n        The journal {journal_name} can't be encrypted because it is a\n        {journal_type} journal.\n\n        To encrypt it, create a new journal referencing a file, export\n        this journal to the new journal, then encrypt the new journal.\n        \"\"\"\n\n    ConfigEncryptedForUnencryptableJournalType = \"\"\"\n        The config for journal \"{journal_name}\" has 'encrypt' set to true, but this type\n        of journal can't be encrypted. Please fix your config file.\n        \"\"\"\n\n    DecryptionFailedGeneric = \"The decryption of journal data failed.\"\n\n    KeyboardInterruptMsg = \"Aborted by user\"\n\n    CantReadTemplate = \"\"\"\n        Unable to find a template file {template_path}.\n\n        The following paths were checked:\n         * {jrnl_template_dir}{template_path}\n         * {actual_template_path}\n        \"\"\"\n\n    NoNamedJournal = \"No '{journal_name}' journal configured\\n{journals}\"\n\n    DoesNotExist = \"{name} does not exist\"\n\n    # --- Journal status ---#\n    JournalNotSaved = \"Entry NOT saved to journal\"\n    JournalEntryAdded = \"Entry added to {journal_name} journal\"\n\n    JournalCountAddedSingular = \"{num} entry added\"\n    JournalCountModifiedSingular = \"{num} entry modified\"\n    JournalCountDeletedSingular = \"{num} entry deleted\"\n\n    JournalCountAddedPlural = \"{num} entries added\"\n    JournalCountModifiedPlural = \"{num} entries modified\"\n    JournalCountDeletedPlural = \"{num} entries deleted\"\n\n    JournalCreated = \"Journal '{journal_name}' created at {filename}\"\n    DirectoryCreated = \"Directory {directory_name} created\"\n    JournalEncrypted = \"Journal will be encrypted\"\n    JournalEncryptedTo = \"Journal encrypted to {path}\"\n    JournalDecryptedTo = \"Journal decrypted to {path}\"\n    BackupCreated = \"Created a backup at {filename}\"\n\n    # --- Editor ---#\n    WritingEntryStart = \"\"\"\n        Writing Entry\n        To finish writing, press {how_to_quit} on a blank line.\n        \"\"\"\n    HowToQuitWindows = \"Ctrl+z and then Enter\"\n    HowToQuitLinux = \"Ctrl+d\"\n\n    EditorMisconfigured = \"\"\"\n        No such file or directory: '{editor_key}'\n\n        Please check the 'editor' key in your config file for errors:\n            editor: '{editor_key}'\n        \"\"\"\n\n    EditorNotConfigured = \"\"\"\n        There is no editor configured\n\n        To use the --edit option, please specify an editor your config file:\n            {config_file}\n\n        For examples of how to configure an external editor, see:\n            https://jrnl.sh/en/stable/external-editors/\n        \"\"\"\n\n    NoEditsReceivedJournalNotDeleted = \"\"\"\n        No text received from editor. Were you trying to delete all the entries?\n\n        This seems a bit drastic, so the operation was cancelled.\n\n        To delete all entries, use the --delete option.\n        \"\"\"\n\n    NoEditsReceived = \"No edits to save, because nothing was changed\"\n\n    NoTextReceived = \"\"\"\n        No entry to save, because no text was received\n        \"\"\"\n    NoChangesToTemplate = \"\"\"\n        No entry to save, because the template was not changed\n    \"\"\"\n    # --- Upgrade --- #\n    JournalFailedUpgrade = \"\"\"\n        The following journal{s} failed to upgrade:\n        {failed_journals}\n\n        Please tell us about this problem at the following URL:\n        https://github.com/jrnl-org/jrnl/issues/new?title=JournalFailedUpgrade\n        \"\"\"\n\n    UpgradeAborted = \"jrnl was NOT upgraded\"\n\n    AbortingUpgrade = \"Aborting upgrade...\"\n\n    ImportAborted = \"Entries were NOT imported\"\n\n    JournalsToUpgrade = \"\"\"\n        The following journals will be upgraded to jrnl {version}:\n\n        \"\"\"\n\n    JournalsToIgnore = \"\"\"\n        The following journals will not be touched:\n\n        \"\"\"\n\n    UpgradingJournal = \"\"\"\n        Upgrading '{journal_name}' journal stored in {path}...\n        \"\"\"\n\n    UpgradingConfig = \"Upgrading config...\"\n\n    PaddedJournalName = \"{journal_name:{pad}} -> {path}\"\n\n    # -- Config --- #\n    AltConfigNotFound = \"\"\"\n        Alternate configuration file not found at the given path:\n            {config_file}\n        \"\"\"\n\n    ConfigUpdated = \"\"\"\n        Configuration updated to newest version at {config_path}\n        \"\"\"\n\n    ConfigDoubleKeys = \"\"\"\n        There is at least one duplicate key in your configuration file.\n\n        Details:\n        {error_message}\n        \"\"\"\n\n    # --- Password --- #\n    Password = \"Password:\"\n    PasswordFirstEntry = \"Enter password for journal '{journal_name}': \"\n    PasswordConfirmEntry = \"Enter password again: \"\n    PasswordMaxTriesExceeded = \"Too many attempts with wrong password\"\n    PasswordCanNotBeEmpty = \"Password can't be empty!\"\n    PasswordDidNotMatch = \"Passwords did not match, please try again\"\n    WrongPasswordTryAgain = \"Wrong password, try again\"\n    PasswordStoreInKeychain = \"Do you want to store the password in your keychain?\"\n\n    # --- Search --- #\n    NothingToDelete = \"\"\"\n        No entries to delete, because the search returned no results\n        \"\"\"\n\n    NothingToModify = \"\"\"\n        No entries to modify, because the search returned no results\n        \"\"\"\n\n    NoEntriesFound = \"no entries found\"\n    EntryFoundCountSingular = \"{num} entry found\"\n    EntryFoundCountPlural = \"{num} entries found\"\n\n    # --- Formats --- #\n    HeadingsPastH6 = \"\"\"\n        Headings increased past H6 on export - {date} {title}\n        \"\"\"\n\n    YamlMustBeDirectory = \"\"\"\n        YAML export must be to a directory, not a single file\n        \"\"\"\n\n    JournalExportedTo = \"Journal exported to {path}\"\n\n    # --- Import --- #\n    ImportSummary = \"\"\"\n        {count} imported to {journal_name} journal\n        \"\"\"\n\n    ImporterNotFound = \"\"\"\n        No importer found for file type '{format}'.\n        '{format}' is likely to be an export-only format.\n        \"\"\"\n\n    # --- Color --- #\n    InvalidColor = \"{key} set to invalid color: {color}\"\n\n    # --- Keyring --- #\n    KeyringBackendNotFound = \"\"\"\n        Keyring backend not found.\n\n        Please install one of the supported backends by visiting:\n          https://pypi.org/project/keyring/\n        \"\"\"\n\n    KeyringRetrievalFailure = \"Failed to retrieve keyring\"\n\n    # --- Deprecation --- #\n    DeprecatedCommand = \"\"\"\n        The command {old_cmd} is deprecated and will be removed from jrnl soon.\n        Please use {new_cmd} instead.\n        \"\"\"\n"
  },
  {
    "path": "jrnl/messages/__init__.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\n\nMessage = Message.Message\nMsgStyle = MsgStyle.MsgStyle\nMsgText = MsgText.MsgText\n"
  },
  {
    "path": "jrnl/os_compat.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport shlex\nfrom sys import platform\n\n\ndef on_windows() -> bool:\n    return \"win32\" in platform\n\n\ndef on_posix() -> bool:\n    return not on_windows()\n\n\ndef split_args(args: str) -> list[str]:\n    \"\"\"Split arguments and add escape characters as appropriate for the OS\"\"\"\n    return shlex.split(args, posix=on_posix())\n"
  },
  {
    "path": "jrnl/output.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport textwrap\nfrom typing import Callable\n\nfrom rich.console import Console\nfrom rich.text import Text\n\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\n\n\ndef deprecated_cmd(\n    old_cmd: str, new_cmd: str, callback: Callable | None = None, **kwargs\n) -> None:\n    print_msg(\n        Message(\n            MsgText.DeprecatedCommand,\n            MsgStyle.WARNING,\n            {\"old_cmd\": old_cmd, \"new_cmd\": new_cmd},\n        )\n    )\n\n    if callback is not None:\n        callback(**kwargs)\n\n\ndef journal_list_to_json(journal_list: dict) -> str:\n    import json\n\n    return json.dumps(journal_list)\n\n\ndef journal_list_to_yaml(journal_list: dict) -> str:\n    from io import StringIO\n\n    from ruamel.yaml import YAML\n\n    output = StringIO()\n    dumper = YAML()\n    dumper.width = 1000\n    dumper.dump(journal_list, output)\n\n    return output.getvalue()\n\n\ndef journal_list_to_stdout(journal_list: dict) -> str:\n    result = f\"Journals defined in config ({journal_list['config_path']})\\n\"\n    ml = min(max(len(k) for k in journal_list[\"journals\"]), 20)\n    for journal, cfg in journal_list[\"journals\"].items():\n        result += \" * {:{}} -> {}\\n\".format(\n            journal, ml, cfg[\"journal\"] if isinstance(cfg, dict) else cfg\n        )\n    return result\n\n\ndef list_journals(configuration: dict, format: str | None = None) -> str:\n    from jrnl import config\n\n    \"\"\"List the journals specified in the configuration file\"\"\"\n\n    journal_list = {\n        \"config_path\": config.get_config_path(),\n        \"journals\": configuration[\"journals\"],\n    }\n\n    if format == \"json\":\n        return journal_list_to_json(journal_list)\n    elif format == \"yaml\":\n        return journal_list_to_yaml(journal_list)\n    else:\n        return journal_list_to_stdout(journal_list)\n\n\ndef print_msg(msg: Message, **kwargs) -> str | None:\n    \"\"\"Helper function to print a single message\"\"\"\n    kwargs[\"style\"] = msg.style\n    return print_msgs([msg], **kwargs)\n\n\ndef print_msgs(\n    msgs: list[Message],\n    delimiter: str = \"\\n\",\n    style: MsgStyle = MsgStyle.NORMAL,\n    get_input: bool = False,\n    hide_input: bool = False,\n) -> str | None:\n    # Same as print_msg, but for a list\n    text = Text(\"\", end=\"\")\n    kwargs = style.decoration.args\n\n    for i, msg in enumerate(msgs):\n        kwargs = _add_extra_style_args_if_needed(kwargs, msg=msg)\n\n        m = format_msg_text(msg)\n\n        if i != len(msgs) - 1:\n            m.append(delimiter)\n\n        text.append(m)\n\n    if style.append_space:\n        text.append(\" \")\n\n    decorated_text = style.decoration.callback(text, **kwargs)\n\n    # Always print messages to stderr\n    console = _get_console(stderr=True)\n\n    if get_input:\n        return str(console.input(prompt=decorated_text, password=hide_input))\n    console.print(decorated_text, new_line_start=style.prepend_newline)\n\n\ndef _get_console(stderr: bool = True) -> Console:\n    return Console(stderr=stderr)\n\n\ndef _add_extra_style_args_if_needed(args: dict, msg: Message):\n    args[\"border_style\"] = msg.style.color\n    args[\"title\"] = msg.style.box_title\n    return args\n\n\ndef format_msg_text(msg: Message) -> Text:\n    text = textwrap.dedent(msg.text.value)\n    text = text.format(**msg.params)\n    # dedent again in case inserted text needs it\n    text = textwrap.dedent(text)\n    text = text.strip()\n    return Text(text)\n\n\ndef wrap_with_ansi_colors(text: str, width: int) -> str:\n    richtext = Text.from_ansi(text, no_wrap=False, tab_size=None)\n\n    console = Console(width=width, force_terminal=True)\n    with console.capture() as capture:\n        console.print(richtext, sep=\"\", end=\"\")\n    return capture.get()\n"
  },
  {
    "path": "jrnl/override.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.config import make_yaml_valid_dict\nfrom jrnl.config import update_config\n\nif TYPE_CHECKING:\n    from argparse import Namespace\n\n\n# import logging\ndef apply_overrides(args: \"Namespace\", base_config: dict) -> dict:\n    \"\"\"Unpack CLI provided overrides into the configuration tree.\n\n    :param overrides: List of configuration key-value pairs collected from the CLI\n    :type overrides: list\n    :param base_config: Configuration Loaded from the saved YAML\n    :type base_config: dict\n    :return: Configuration to be used during runtime with the overrides applied\n    :rtype: dict\n    \"\"\"\n    overrides = vars(args).get(\"config_override\")\n    if not overrides:\n        return base_config\n\n    cfg_with_overrides = base_config.copy()\n    for pairs in overrides:\n        pairs = make_yaml_valid_dict(pairs)\n        key_as_dots, override_value = _get_key_and_value_from_pair(pairs)\n        keys = _convert_dots_to_list(key_as_dots)\n        cfg_with_overrides = _recursively_apply(\n            cfg_with_overrides, keys, override_value\n        )\n\n    update_config(base_config, cfg_with_overrides, None)\n    return base_config\n\n\ndef _get_key_and_value_from_pair(pairs: dict) -> tuple:\n    key_as_dots, override_value = list(pairs.items())[0]\n    return key_as_dots, override_value\n\n\ndef _convert_dots_to_list(key_as_dots: str) -> list[str]:\n    keys = key_as_dots.split(\".\")\n    keys = [k for k in keys if k != \"\"]  # remove empty elements\n    return keys\n\n\ndef _recursively_apply(tree: dict, nodes: list, override_value) -> dict:\n    \"\"\"Recurse through configuration and apply overrides at the leaf of the config tree\n\n    Credit to iJames on SO: https://stackoverflow.com/a/47276490 for algorithm\n\n    Args:\n        config (dict): Configuration to modify\n        nodes (list): Vector of override keys; the length of the vector indicates tree\n            depth\n        override_value (str): Runtime override passed from the command-line\n    \"\"\"\n    key = nodes[0]\n    if len(nodes) == 1:\n        tree[key] = override_value\n    else:\n        next_key = nodes[1:]\n        next_node = _get_config_node(tree, key)\n        _recursively_apply(next_node, next_key, override_value)\n\n    return tree\n\n\ndef _get_config_node(config: dict, key: str):\n    if key in config:\n        pass\n    else:\n        config[key] = None\n    return config[key]\n"
  },
  {
    "path": "jrnl/path.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport os.path\nfrom pathlib import Path\n\nimport xdg.BaseDirectory\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\n\n# Constants\nXDG_RESOURCE = \"jrnl\"\nDEFAULT_CONFIG_NAME = \"jrnl.yaml\"\nDEFAULT_JOURNAL_NAME = \"journal.txt\"\n\n\ndef home_dir() -> str:\n    return os.path.expanduser(\"~\")\n\n\ndef expand_path(path: str) -> str:\n    return os.path.expanduser(os.path.expandvars(path))\n\n\ndef absolute_path(path: str) -> str:\n    return os.path.abspath(expand_path(path))\n\n\ndef get_default_journal_path() -> str:\n    journal_data_path = xdg.BaseDirectory.save_data_path(XDG_RESOURCE) or home_dir()\n    return os.path.join(journal_data_path, DEFAULT_JOURNAL_NAME)\n\n\ndef get_templates_path() -> str:\n    \"\"\"\n    Get the path to the XDG templates directory. Creates the directory if it\n    doesn't exist.\n    \"\"\"\n    # jrnl_xdg_resource_path is created by save_data_path if it does not exist\n    jrnl_xdg_resource_path = Path(xdg.BaseDirectory.save_data_path(XDG_RESOURCE))\n    jrnl_templates_path = jrnl_xdg_resource_path / \"templates\"\n    # Create the directory if needed.\n    jrnl_templates_path.mkdir(exist_ok=True)\n    return str(jrnl_templates_path)\n\n\ndef get_config_directory() -> str:\n    try:\n        return xdg.BaseDirectory.save_config_path(XDG_RESOURCE)\n    except FileExistsError:\n        raise JrnlException(\n            Message(\n                MsgText.ConfigDirectoryIsFile,\n                MsgStyle.ERROR,\n                {\n                    \"config_directory_path\": os.path.join(\n                        xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE\n                    )\n                },\n            ),\n        )\n\n\ndef get_config_path() -> str:\n    try:\n        config_directory_path = get_config_directory()\n    except JrnlException:\n        return os.path.join(home_dir(), DEFAULT_CONFIG_NAME)\n    return os.path.join(config_directory_path, DEFAULT_CONFIG_NAME)\n"
  },
  {
    "path": "jrnl/plugins/__init__.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom typing import Type\n\nfrom jrnl.plugins.calendar_heatmap_exporter import CalendarHeatmapExporter\nfrom jrnl.plugins.dates_exporter import DatesExporter\nfrom jrnl.plugins.fancy_exporter import FancyExporter\nfrom jrnl.plugins.jrnl_importer import JRNLImporter\nfrom jrnl.plugins.json_exporter import JSONExporter\nfrom jrnl.plugins.markdown_exporter import MarkdownExporter\nfrom jrnl.plugins.tag_exporter import TagExporter\nfrom jrnl.plugins.text_exporter import TextExporter\nfrom jrnl.plugins.xml_exporter import XMLExporter\nfrom jrnl.plugins.yaml_exporter import YAMLExporter\n\n__exporters = [\n    CalendarHeatmapExporter,\n    DatesExporter,\n    FancyExporter,\n    JSONExporter,\n    MarkdownExporter,\n    TagExporter,\n    TextExporter,\n    XMLExporter,\n    YAMLExporter,\n]\n__importers = [JRNLImporter]\n\n__exporter_types = {name: plugin for plugin in __exporters for name in plugin.names}\n__exporter_types[\"pretty\"] = None\n__exporter_types[\"short\"] = None\n__importer_types = {name: plugin for plugin in __importers for name in plugin.names}\n\nEXPORT_FORMATS = sorted(__exporter_types.keys())\nIMPORT_FORMATS = sorted(__importer_types.keys())\n\n\ndef get_exporter(format: str) -> Type[TextExporter] | None:\n    for exporter in __exporters:\n        if hasattr(exporter, \"names\") and format in exporter.names:\n            return exporter\n    return None\n\n\ndef get_importer(format: str) -> Type[JRNLImporter] | None:\n    for importer in __importers:\n        if hasattr(importer, \"names\") and format in importer.names:\n            return importer\n    return None\n"
  },
  {
    "path": "jrnl/plugins/calendar_heatmap_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport calendar\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING\n\nfrom rich import box\nfrom rich.align import Align\nfrom rich.columns import Columns\nfrom rich.console import Console\nfrom rich.table import Table\nfrom rich.text import Text\n\nfrom jrnl.plugins.text_exporter import TextExporter\nfrom jrnl.plugins.util import get_journal_frequency_nested\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n    from jrnl.plugins.util import NestedDict\n\n\nclass CalendarHeatmapExporter(TextExporter):\n    \"\"\"This Exporter displays a calendar heatmap of the journaling frequency.\"\"\"\n\n    names = [\"calendar\", \"heatmap\"]\n    extension = \"cal\"\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\"):\n        raise NotImplementedError\n\n    @classmethod\n    def print_calendar_heatmap(cls, journal_frequency: \"NestedDict\") -> str:\n        \"\"\"Returns a string representation of the calendar heatmap.\"\"\"\n        console = Console()\n        cal = calendar.Calendar()\n        curr_year = datetime.now().year\n        curr_month = datetime.now().month\n        curr_day = datetime.now().day\n        hit_first_entry = False\n        with console.capture() as capture:\n            for year, month_journaling_freq in journal_frequency.items():\n                year_calendar = []\n                for month in range(1, 13):\n                    if month > curr_month and year == curr_year:\n                        break\n\n                    entries_this_month = sum(month_journaling_freq[month].values())\n                    if not hit_first_entry and entries_this_month > 0:\n                        hit_first_entry = True\n\n                    if entries_this_month == 0 and not hit_first_entry:\n                        continue\n                    elif entries_this_month == 0:\n                        entry_msg = \"No entries\"\n                    elif entries_this_month == 1:\n                        entry_msg = \"1 entry\"\n                    else:\n                        entry_msg = f\"{entries_this_month} entries\"\n                    table = Table(\n                        title=f\"{calendar.month_name[month]} {year} ({entry_msg})\",\n                        title_style=\"bold green\",\n                        box=box.SIMPLE_HEAVY,\n                        padding=0,\n                    )\n\n                    for week_day in cal.iterweekdays():\n                        table.add_column(\n                            \"{:.3}\".format(calendar.day_name[week_day]), justify=\"right\"\n                        )\n\n                    month_days = cal.monthdayscalendar(year, month)\n                    for weekdays in month_days:\n                        days = []\n                        for _, day in enumerate(weekdays):\n                            if day == 0:  # Not a part of this month, just filler.\n                                day_label = Text(str(day or \"\"), style=\"white\")\n                            elif (\n                                day > curr_day\n                                and month == curr_month\n                                and year == curr_year\n                            ):\n                                break\n                            else:\n                                journal_frequency_for_day = (\n                                    month_journaling_freq[month][day] or 0\n                                )\n                                day = str(day)\n                                # TODO: Make colors configurable?\n                                if journal_frequency_for_day == 0:\n                                    day_label = Text(day, style=\"red on black\")\n                                elif journal_frequency_for_day == 1:\n                                    day_label = Text(day, style=\"black on yellow\")\n                                elif journal_frequency_for_day == 2:\n                                    day_label = Text(day, style=\"black on green\")\n                                else:\n                                    day_label = Text(day, style=\"black on white\")\n\n                            days.append(day_label)\n                        table.add_row(*days)\n\n                    year_calendar.append(Align.center(table))\n\n                # Print year header line\n                console.rule(str(year))\n                console.print()\n                # Print calendar\n                console.print(Columns(year_calendar, padding=1, expand=True))\n        return capture.get()\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\"):\n        \"\"\"Returns dates and their frequencies for an entire journal.\"\"\"\n        journal_entry_date_frequency = get_journal_frequency_nested(journal)\n        return cls.print_calendar_heatmap(journal_entry_date_frequency)\n"
  },
  {
    "path": "jrnl/plugins/dates_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.plugins.text_exporter import TextExporter\nfrom jrnl.plugins.util import get_journal_frequency_one_level\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass DatesExporter(TextExporter):\n    \"\"\"This Exporter lists dates and their respective counts, for heatingmapping etc.\"\"\"\n\n    names = [\"dates\"]\n    extension = \"dates\"\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\"):\n        raise NotImplementedError\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\") -> str:\n        \"\"\"Returns dates and their frequencies for an entire journal.\"\"\"\n        date_counts = get_journal_frequency_one_level(journal)\n        result = \"\\n\".join(f\"{date}, {count}\" for date, count in date_counts.items())\n        return result\n"
  },
  {
    "path": "jrnl/plugins/fancy_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\nimport os\nfrom textwrap import TextWrapper\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.plugins.text_exporter import TextExporter\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass FancyExporter(TextExporter):\n    \"\"\"This Exporter converts entries and journals into text with unicode boxes.\"\"\"\n\n    names = [\"fancy\", \"boxed\"]\n    extension = \"txt\"\n\n    # Top border of the card\n    border_a = \"┎\"\n    border_b = \"─\"\n    border_c = \"╮\"\n    border_d = \"╘\"\n    border_e = \"═\"\n    border_f = \"╕\"\n\n    border_g = \"┃\"\n    border_h = \"│\"\n    border_i = \"┠\"\n    border_j = \"╌\"\n    border_k = \"┤\"\n    border_l = \"┖\"\n    border_m = \"┘\"\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\") -> str:\n        \"\"\"Returns a fancy unicode representation of a single entry.\"\"\"\n        date_str = entry.date.strftime(entry.journal.config[\"timeformat\"])\n\n        if entry.journal.config[\"linewrap\"]:\n            linewrap = entry.journal.config[\"linewrap\"]\n\n            if linewrap == \"auto\":\n                try:\n                    linewrap = os.get_terminal_size().columns\n                except OSError:\n                    logging.debug(\n                        \"Can't determine terminal size automatically 'linewrap': '%s'\",\n                        entry.journal.config[\"linewrap\"],\n                    )\n                    linewrap = 79\n        else:\n            linewrap = 79\n\n        initial_linewrap = max((1, linewrap - len(date_str) - 2))\n        body_linewrap = linewrap - 2\n        card = [\n            cls.border_a + cls.border_b * (initial_linewrap) + cls.border_c + date_str\n        ]\n        check_provided_linewrap_viability(linewrap, card, entry.journal.name)\n\n        w = TextWrapper(\n            width=initial_linewrap,\n            initial_indent=cls.border_g + \" \",\n            subsequent_indent=cls.border_g + \" \",\n        )\n\n        title_lines = w.wrap(entry.title) or [\"\"]\n        card.append(\n            title_lines[0].ljust(initial_linewrap + 1)\n            + cls.border_d\n            + cls.border_e * (len(date_str) - 1)\n            + cls.border_f\n        )\n        w.width = body_linewrap\n        if len(title_lines) > 1:\n            for line in w.wrap(\n                \" \".join(\n                    [\n                        title_line[len(w.subsequent_indent) :]\n                        for title_line in title_lines[1:]\n                    ]\n                )\n            ):\n                card.append(line.ljust(body_linewrap + 1) + cls.border_h)\n        if entry.body:\n            card.append(cls.border_i + cls.border_j * body_linewrap + cls.border_k)\n            for line in entry.body.splitlines():\n                body_lines = w.wrap(line) or [cls.border_g]\n                for body_line in body_lines:\n                    card.append(body_line.ljust(body_linewrap + 1) + cls.border_h)\n        card.append(cls.border_l + cls.border_b * body_linewrap + cls.border_m)\n        return \"\\n\".join(card)\n\n    @classmethod\n    def export_journal(cls, journal) -> str:\n        \"\"\"Returns a unicode representation of an entire journal.\"\"\"\n        return \"\\n\".join(cls.export_entry(entry) for entry in journal)\n\n\ndef check_provided_linewrap_viability(\n    linewrap: int, card: list[str], journal: \"Journal\"\n):\n    if len(card[0]) > linewrap:\n        width_violation = len(card[0]) - linewrap\n        raise JrnlException(\n            Message(\n                MsgText.LineWrapTooSmallForDateFormat,\n                MsgStyle.NORMAL,\n                {\n                    \"config_linewrap\": linewrap,\n                    \"columns\": width_violation,\n                    \"journal\": journal,\n                },\n            )\n        )\n"
  },
  {
    "path": "jrnl/plugins/jrnl_importer.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport sys\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\n\nif TYPE_CHECKING:\n    from jrnl.journals import Journal\n\n\nclass JRNLImporter:\n    \"\"\"This plugin imports entries from other jrnl files.\"\"\"\n\n    names = [\"jrnl\"]\n\n    @staticmethod\n    def import_(journal: \"Journal\", input: str | None = None) -> None:\n        \"\"\"Imports from an existing file if input is specified, and\n        standard input otherwise.\"\"\"\n        old_cnt = len(journal.entries)\n        if input:\n            with open(input, \"r\", encoding=\"utf-8\") as f:\n                other_journal_txt = f.read()\n        else:\n            try:\n                other_journal_txt = sys.stdin.read()\n            except KeyboardInterrupt:\n                raise JrnlException(\n                    Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR_ON_NEW_LINE),\n                    Message(MsgText.ImportAborted, MsgStyle.WARNING),\n                )\n\n        journal.import_(other_journal_txt)\n        new_cnt = len(journal.entries)\n        journal.write()\n        print_msg(\n            Message(\n                MsgText.ImportSummary,\n                MsgStyle.NORMAL,\n                {\n                    \"count\": new_cnt - old_cnt,\n                    \"journal_name\": journal.name,\n                },\n            )\n        )\n"
  },
  {
    "path": "jrnl/plugins/json_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport json\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.plugins.text_exporter import TextExporter\nfrom jrnl.plugins.util import get_tags_count\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass JSONExporter(TextExporter):\n    \"\"\"This Exporter can convert entries and journals into json.\"\"\"\n\n    names = [\"json\"]\n    extension = \"json\"\n\n    @classmethod\n    def entry_to_dict(cls, entry: \"Entry\") -> dict:\n        entry_dict = {\n            \"title\": entry.title,\n            \"body\": entry.body,\n            \"date\": entry.date.strftime(\"%Y-%m-%d\"),\n            \"time\": entry.date.strftime(\"%H:%M\"),\n            \"tags\": entry.tags,\n            \"starred\": entry.starred,\n        }\n        if hasattr(entry, \"uuid\"):\n            entry_dict[\"uuid\"] = entry.uuid\n        if (\n            hasattr(entry, \"creator_device_agent\")\n            or hasattr(entry, \"creator_generation_date\")\n            or hasattr(entry, \"creator_host_name\")\n            or hasattr(entry, \"creator_os_agent\")\n            or hasattr(entry, \"creator_software_agent\")\n        ):\n            entry_dict[\"creator\"] = {}\n            if hasattr(entry, \"creator_device_agent\"):\n                entry_dict[\"creator\"][\"device_agent\"] = entry.creator_device_agent\n            if hasattr(entry, \"creator_generation_date\"):\n                entry_dict[\"creator\"][\"generation_date\"] = str(\n                    entry.creator_generation_date\n                )\n            if hasattr(entry, \"creator_host_name\"):\n                entry_dict[\"creator\"][\"host_name\"] = entry.creator_host_name\n            if hasattr(entry, \"creator_os_agent\"):\n                entry_dict[\"creator\"][\"os_agent\"] = entry.creator_os_agent\n            if hasattr(entry, \"creator_software_agent\"):\n                entry_dict[\"creator\"][\"software_agent\"] = entry.creator_software_agent\n\n        return entry_dict\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\") -> str:\n        \"\"\"Returns a json representation of a single entry.\"\"\"\n        return json.dumps(cls.entry_to_dict(entry), indent=2) + \"\\n\"\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\") -> str:\n        \"\"\"Returns a json representation of an entire journal.\"\"\"\n        tags = get_tags_count(journal)\n        result = {\n            \"tags\": {tag: count for count, tag in tags},\n            \"entries\": [cls.entry_to_dict(e) for e in journal.entries],\n        }\n        return json.dumps(result, indent=2)\n"
  },
  {
    "path": "jrnl/plugins/markdown_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport os\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\nfrom jrnl.plugins.text_exporter import TextExporter\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass MarkdownExporter(TextExporter):\n    \"\"\"This Exporter can convert entries and journals into Markdown.\"\"\"\n\n    names = [\"md\", \"markdown\"]\n    extension = \"md\"\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\", to_multifile: bool = True) -> str:\n        \"\"\"Returns a markdown representation of a single entry.\"\"\"\n        date_str = entry.date.strftime(entry.journal.config[\"timeformat\"])\n        body_wrapper = \"\\n\" if entry.body else \"\"\n        body = body_wrapper + entry.body\n\n        if to_multifile is True:\n            heading = \"#\"\n        else:\n            heading = \"###\"\n\n        \"\"\"Increase heading levels in body text\"\"\"\n        newbody = \"\"\n        previous_line = \"\"\n        warn_on_heading_level = False\n        for line in body.splitlines(True):\n            if re.match(r\"^#+ \", line):\n                \"\"\"ATX style headings\"\"\"\n                newbody = newbody + previous_line + heading + line\n                if re.match(r\"^#######+ \", heading + line):\n                    warn_on_heading_level = True\n                line = \"\"\n            elif re.match(r\"^=+$\", line.rstrip()) and not re.match(\n                r\"^$\", previous_line.strip()\n            ):\n                \"\"\"Setext style H1\"\"\"\n                newbody = newbody + heading + \"# \" + previous_line\n                line = \"\"\n            elif re.match(r\"^-+$\", line.rstrip()) and not re.match(\n                r\"^$\", previous_line.strip()\n            ):\n                \"\"\"Setext style H2\"\"\"\n                newbody = newbody + heading + \"## \" + previous_line\n                line = \"\"\n            else:\n                newbody = newbody + previous_line\n            previous_line = line\n        newbody = newbody + previous_line  # add very last line\n\n        # make sure the export ends with a blank line\n        if previous_line not in [\"\\r\", \"\\n\", \"\\r\\n\", \"\\n\\r\"]:\n            newbody = newbody + os.linesep\n\n        if warn_on_heading_level is True:\n            print_msg(\n                Message(\n                    MsgText.HeadingsPastH6,\n                    MsgStyle.WARNING,\n                    {\"date\": date_str, \"title\": entry.title},\n                )\n            )\n\n        return f\"{heading} {date_str} {entry.title}\\n{newbody} \"\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\") -> str:\n        \"\"\"Returns a Markdown representation of an entire journal.\"\"\"\n        out = []\n        year, month = -1, -1\n        for e in journal.entries:\n            if e.date.year != year:\n                year = e.date.year\n                out.append(\"# \" + str(year))\n                out.append(\"\")\n            if e.date.month != month:\n                month = e.date.month\n                out.append(\"## \" + e.date.strftime(\"%B\"))\n                out.append(\"\")\n            out.append(cls.export_entry(e, False))\n        result = \"\\n\".join(out)\n        return result\n"
  },
  {
    "path": "jrnl/plugins/tag_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.plugins.text_exporter import TextExporter\nfrom jrnl.plugins.util import get_tags_count\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass TagExporter(TextExporter):\n    \"\"\"This Exporter lists the tags for entries and journals.\"\"\"\n\n    names = [\"tags\"]\n    extension = \"tags\"\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\") -> str:\n        \"\"\"Returns a list of tags for a single entry.\"\"\"\n        return \", \".join(entry.tags)\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\") -> str:\n        \"\"\"Returns a list of tags and their frequency for an entire journal.\"\"\"\n        tag_counts = get_tags_count(journal)\n        result = \"\"\n        if not tag_counts:\n            return \"[No tags found in journal.]\"\n        elif min(tag_counts)[0] == 0:\n            tag_counts = filter(lambda x: x[0] > 1, tag_counts)\n            result += \"[Removed tags that appear only once.]\\n\"\n        result += \"\\n\".join(\n            \"{:20} : {}\".format(tag, n) for n, tag in sorted(tag_counts, reverse=True)\n        )\n        return result\n"
  },
  {
    "path": "jrnl/plugins/text_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport errno\nimport os\nimport re\nimport unicodedata\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass TextExporter:\n    \"\"\"This Exporter can convert entries and journals into text files.\"\"\"\n\n    names = [\"text\", \"txt\"]\n    extension = \"txt\"\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\") -> str:\n        \"\"\"Returns a string representation of a single entry.\"\"\"\n        return str(entry)\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\") -> str:\n        \"\"\"Returns a string representation of an entire journal.\"\"\"\n        return \"\\n\".join(cls.export_entry(entry) for entry in journal)\n\n    @classmethod\n    def write_file(cls, journal: \"Journal\", path: str) -> str:\n        \"\"\"Exports a journal into a single file.\"\"\"\n        export_str = cls.export_journal(journal)\n        with open(path, \"w\", encoding=\"utf-8\") as f:\n            f.write(export_str)\n        print_msg(\n            Message(\n                MsgText.JournalExportedTo,\n                MsgStyle.NORMAL,\n                {\n                    \"path\": path,\n                },\n            )\n        )\n        return \"\"\n\n    @classmethod\n    def make_filename(cls, entry: \"Entry\") -> str:\n        return entry.date.strftime(\"%Y-%m-%d\") + \"_{}.{}\".format(\n            cls._slugify(str(entry.title)), cls.extension\n        )\n\n    @classmethod\n    def write_files(cls, journal: \"Journal\", path: str) -> str:\n        \"\"\"Exports a journal into individual files for each entry.\"\"\"\n        for entry in journal.entries:\n            entry_is_written = False\n            while not entry_is_written:\n                full_path = os.path.join(path, cls.make_filename(entry))\n                try:\n                    with open(full_path, \"w\", encoding=\"utf-8\") as f:\n                        f.write(cls.export_entry(entry))\n                        entry_is_written = True\n                except OSError as oserr:\n                    title_length = len(str(entry.title))\n                    if (\n                        oserr.errno == errno.ENAMETOOLONG\n                        or oserr.errno == errno.ENOENT\n                        or oserr.errno == errno.EINVAL\n                    ) and title_length > 1:\n                        shorter_file_length = title_length // 2\n                        entry.title = str(entry.title)[:shorter_file_length]\n                    else:\n                        raise\n        print_msg(\n            Message(\n                MsgText.JournalExportedTo,\n                MsgStyle.NORMAL,\n                {\"path\": path},\n            )\n        )\n        return \"\"\n\n    def _slugify(string: str) -> str:\n        \"\"\"Slugifies a string.\n        Based on public domain code from https://github.com/zacharyvoase/slugify\n        \"\"\"\n        normalized_string = str(unicodedata.normalize(\"NFKD\", string))\n        no_punctuation = re.sub(r\"[^\\w\\s-]\", \"\", normalized_string).strip().lower()\n        slug = re.sub(r\"[-\\s]+\", \"-\", no_punctuation)\n        return slug\n\n    @classmethod\n    def export(cls, journal: \"Journal\", output: str | None = None) -> str:\n        \"\"\"Exports to individual files if output is an existing path, or into\n        a single file if output is a file name, or returns the exporter's\n        representation as string if output is None.\"\"\"\n        if output and os.path.isdir(output):  # multiple files\n            return cls.write_files(journal, output)\n        elif output:  # single file\n            return cls.write_file(journal, output)\n        else:\n            return cls.export_journal(journal)\n"
  },
  {
    "path": "jrnl/plugins/util.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom collections import Counter\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from jrnl.journals import Journal\n\n\nclass NestedDict(dict):\n    \"\"\"https://stackoverflow.com/a/74873621/8740440\"\"\"\n\n    def __missing__(self, x):\n        self[x] = NestedDict()\n        return self[x]\n\n\ndef get_tags_count(journal: \"Journal\") -> set[tuple[int, str]]:\n    \"\"\"Returns a set of tuples (count, tag) for all tags present in the journal.\"\"\"\n    # Astute reader: should the following line leave you as puzzled as me the first time\n    # I came across this construction, worry not and embrace the ensuing moment of\n    # enlightment.\n    tags = [tag for entry in journal.entries for tag in set(entry.tags)]\n    # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag]\n    tag_counts = {(tags.count(tag), tag) for tag in tags}\n    return tag_counts\n\n\ndef oxford_list(lst: list) -> str:\n    \"\"\"Return Human-readable list of things obeying the object comma)\"\"\"\n    lst = sorted(lst)\n    if not lst:\n        return \"(nothing)\"\n    elif len(lst) == 1:\n        return lst[0]\n    elif len(lst) == 2:\n        return lst[0] + \" or \" + lst[1]\n    else:\n        return \", \".join(lst[:-1]) + \", or \" + lst[-1]\n\n\ndef get_journal_frequency_nested(journal: \"Journal\") -> NestedDict:\n    \"\"\"Returns a NestedDict of the form {year: {month: {day: count}}}\"\"\"\n    journal_frequency = NestedDict()\n    for entry in journal.entries:\n        date = entry.date.date()\n        if date.day in journal_frequency[date.year][date.month]:\n            journal_frequency[date.year][date.month][date.day] += 1\n        else:\n            journal_frequency[date.year][date.month][date.day] = 1\n\n    return journal_frequency\n\n\ndef get_journal_frequency_one_level(journal: \"Journal\") -> Counter:\n    \"\"\"Returns a Counter of the form {date (YYYY-MM-DD): count}\"\"\"\n    date_counts = Counter()\n    for entry in journal.entries:\n        # entry.date.date() gets date without time\n        date = str(entry.date.date())\n        date_counts[date] += 1\n    return date_counts\n"
  },
  {
    "path": "jrnl/plugins/xml_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom typing import TYPE_CHECKING\nfrom xml.dom import minidom\n\nfrom jrnl.plugins.json_exporter import JSONExporter\nfrom jrnl.plugins.util import get_tags_count\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass XMLExporter(JSONExporter):\n    \"\"\"This Exporter can convert entries and journals into XML.\"\"\"\n\n    names = [\"xml\"]\n    extension = \"xml\"\n\n    @classmethod\n    def export_entry(\n        cls, entry: \"Entry\", doc: minidom.Document | None = None\n    ) -> minidom.Element | str:\n        \"\"\"Returns an XML representation of a single entry.\"\"\"\n        doc_el = doc or minidom.Document()\n        entry_el = doc_el.createElement(\"entry\")\n        for key, value in cls.entry_to_dict(entry).items():\n            elem = doc_el.createElement(key)\n            elem.appendChild(doc_el.createTextNode(value))\n            entry_el.appendChild(elem)\n        if not doc:\n            doc_el.appendChild(entry_el)\n            return doc_el.toprettyxml()\n        else:\n            return entry_el\n\n    @classmethod\n    def entry_to_xml(cls, entry: \"Entry\", doc: minidom.Document) -> minidom.Element:\n        entry_el = doc.createElement(\"entry\")\n        entry_el.setAttribute(\"date\", entry.date.isoformat())\n        if hasattr(entry, \"uuid\"):\n            entry_el.setAttribute(\"uuid\", entry.uuid)\n        entry_el.setAttribute(\"starred\", entry.starred)\n        tags = entry.tags\n        for tag in tags:\n            tag_el = doc.createElement(\"tag\")\n            tag_el.setAttribute(\"name\", tag)\n            entry_el.appendChild(tag_el)\n        entry_el.appendChild(doc.createTextNode(entry.fulltext))\n        return entry_el\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\") -> str:\n        \"\"\"Returns an XML representation of an entire journal.\"\"\"\n        tags = get_tags_count(journal)\n        doc = minidom.Document()\n        xml = doc.createElement(\"journal\")\n        tags_el = doc.createElement(\"tags\")\n        entries_el = doc.createElement(\"entries\")\n        for count, tag in tags:\n            tag_el = doc.createElement(\"tag\")\n            tag_el.setAttribute(\"name\", tag)\n            count_node = doc.createTextNode(str(count))\n            tag_el.appendChild(count_node)\n            tags_el.appendChild(tag_el)\n        for entry in journal.entries:\n            entries_el.appendChild(cls.entry_to_xml(entry, doc))\n        xml.appendChild(entries_el)\n        xml.appendChild(tags_el)\n        doc.appendChild(xml)\n        return doc.toprettyxml()\n"
  },
  {
    "path": "jrnl/plugins/yaml_exporter.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport os\nimport re\nfrom typing import TYPE_CHECKING\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\nfrom jrnl.plugins.text_exporter import TextExporter\n\nif TYPE_CHECKING:\n    from jrnl.journals import Entry\n    from jrnl.journals import Journal\n\n\nclass YAMLExporter(TextExporter):\n    \"\"\"This Exporter converts entries and journals into Markdown formatted text with\n    YAML front matter.\"\"\"\n\n    names = [\"yaml\"]\n    extension = \"md\"\n\n    @classmethod\n    def export_entry(cls, entry: \"Entry\", to_multifile: bool = True) -> str:\n        \"\"\"Returns a markdown representation of an entry, with YAML front matter.\"\"\"\n        if to_multifile is False:\n            raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR))\n\n        date_str = entry.date.strftime(entry.journal.config[\"timeformat\"])\n        body_wrapper = \"\\n\" if entry.body else \"\"\n        body = body_wrapper + entry.body\n\n        tagsymbols = entry.journal.config[\"tagsymbols\"]\n        # see also Entry.rag_regex\n        multi_tag_regex = re.compile(rf\"(?u)^\\s*([{tagsymbols}][-+*#/\\w]+\\s*)+$\")\n\n        \"\"\"Increase heading levels in body text\"\"\"\n        newbody = \"\"\n        heading = \"#\"\n        previous_line = \"\"\n        warn_on_heading_level = False\n        for line in body.splitlines(True):\n            if re.match(r\"^#+ \", line):\n                \"\"\"ATX style headings\"\"\"\n                newbody = newbody + previous_line + heading + line\n                if re.match(r\"^#######+ \", heading + line):\n                    warn_on_heading_level = True\n                line = \"\"\n            elif re.match(r\"^=+$\", line.rstrip()) and not re.match(\n                r\"^$\", previous_line.strip()\n            ):\n                \"\"\"Setext style H1\"\"\"\n                newbody = newbody + heading + \"# \" + previous_line\n                line = \"\"\n            elif re.match(r\"^-+$\", line.rstrip()) and not re.match(\n                r\"^$\", previous_line.strip()\n            ):\n                \"\"\"Setext style H2\"\"\"\n                newbody = newbody + heading + \"## \" + previous_line\n                line = \"\"\n            elif multi_tag_regex.match(line):\n                \"\"\"Tag only lines\"\"\"\n                line = \"\"\n            else:\n                newbody = newbody + previous_line\n            previous_line = line\n        newbody = newbody + previous_line  # add very last line\n\n        # make sure the export ends with a blank line\n        if previous_line not in [\"\\r\", \"\\n\", \"\\r\\n\", \"\\n\\r\"]:\n            newbody = newbody + os.linesep\n\n        # set indentation for YAML body block\n        spacebody = \"\\t\"\n        for line in newbody.splitlines(True):\n            spacebody = spacebody + \"\\t\" + line\n\n        if warn_on_heading_level is True:\n            print_msg(\n                Message(\n                    MsgText.HeadingsPastH6,\n                    MsgStyle.WARNING,\n                    {\"date\": date_str, \"title\": entry.title},\n                )\n            )\n\n        dayone_attributes = \"\"\n        if hasattr(entry, \"uuid\"):\n            dayone_attributes += \"uuid: \" + entry.uuid + \"\\n\"\n        if (\n            hasattr(entry, \"creator_device_agent\")\n            or hasattr(entry, \"creator_generation_date\")\n            or hasattr(entry, \"creator_host_name\")\n            or hasattr(entry, \"creator_os_agent\")\n            or hasattr(entry, \"creator_software_agent\")\n        ):\n            dayone_attributes += \"creator:\\n\"\n            if hasattr(entry, \"creator_device_agent\"):\n                dayone_attributes += f\"    device agent: {entry.creator_device_agent}\\n\"\n            if hasattr(entry, \"creator_generation_date\"):\n                dayone_attributes += \"    generation date: {}\\n\".format(\n                    str(entry.creator_generation_date)\n                )\n            if hasattr(entry, \"creator_host_name\"):\n                dayone_attributes += f\"    host name: {entry.creator_host_name}\\n\"\n            if hasattr(entry, \"creator_os_agent\"):\n                dayone_attributes += f\"    os agent: {entry.creator_os_agent}\\n\"\n            if hasattr(entry, \"creator_software_agent\"):\n                dayone_attributes += (\n                    f\"    software agent: {entry.creator_software_agent}\\n\"\n                )\n\n        # TODO: copy over pictures, if present\n        # source directory is  entry.journal.config['journal']\n        # output directory is...?\n\n        return (\n            \"{start}\\n\"\n            \"title: {title}\\n\"\n            \"date: {date}\\n\"\n            \"starred: {starred}\\n\"\n            \"tags: {tags}\\n\"\n            \"{dayone}body: |{body}{end}\"\n        ).format(\n            start=\"---\",\n            date=date_str,\n            title=entry.title,\n            starred=entry.starred,\n            tags=\", \".join([tag[1:] for tag in entry.tags]),\n            dayone=dayone_attributes,\n            body=spacebody,\n            end=\"...\",\n        )\n\n    @classmethod\n    def export_journal(cls, journal: \"Journal\"):\n        \"\"\"Returns an error, as YAML export requires a directory as a target.\"\"\"\n        raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR))\n"
  },
  {
    "path": "jrnl/prompt.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\nfrom jrnl.output import print_msgs\n\n\ndef create_password(journal_name: str) -> str:\n    kwargs = {\n        \"get_input\": True,\n        \"hide_input\": True,\n    }\n    while True:\n        pw = print_msg(\n            Message(\n                MsgText.PasswordFirstEntry,\n                MsgStyle.PROMPT,\n                params={\"journal_name\": journal_name},\n            ),\n            **kwargs,\n        )\n\n        if not pw:\n            print_msg(Message(MsgText.PasswordCanNotBeEmpty, MsgStyle.WARNING))\n            continue\n\n        elif pw == print_msg(\n            Message(MsgText.PasswordConfirmEntry, MsgStyle.PROMPT), **kwargs\n        ):\n            break\n\n        print_msg(Message(MsgText.PasswordDidNotMatch, MsgStyle.ERROR))\n\n    if yesno(Message(MsgText.PasswordStoreInKeychain), default=True):\n        from jrnl.keyring import set_keyring_password\n\n        set_keyring_password(pw, journal_name)\n\n    return pw\n\n\ndef prompt_password(first_try: bool = True) -> str:\n    if not first_try:\n        print_msg(Message(MsgText.WrongPasswordTryAgain, MsgStyle.WARNING))\n\n    return (\n        print_msg(\n            Message(MsgText.Password, MsgStyle.PROMPT),\n            get_input=True,\n            hide_input=True,\n        )\n        or \"\"\n    )\n\n\ndef yesno(prompt: Message | str, default: bool = True) -> bool:\n    response = print_msgs(\n        [\n            prompt,\n            Message(\n                MsgText.YesOrNoPromptDefaultYes\n                if default\n                else MsgText.YesOrNoPromptDefaultNo\n            ),\n        ],\n        style=MsgStyle.PROMPT,\n        delimiter=\" \",\n        get_input=True,\n    )\n\n    answers = {\n        str(MsgText.OneCharacterYes): True,\n        str(MsgText.OneCharacterNo): False,\n    }\n\n    # Does using `lower()` work in all languages?\n    return answers.get(str(response).lower().strip(), default)\n"
  },
  {
    "path": "jrnl/templates/sample.template",
    "content": "---\nextension: txt\n---\n\n{% block journal %}\n{% for entry in entries %}\n{% include entry %}\n{% endfor %}\n\n{% endblock %}\n\n{% block entry %}\n{{ entry.title }}\n{{ \"-\" * len(entry.title) }}\n\n{{ entry.body }}\n\n{% endblock %}\n"
  },
  {
    "path": "jrnl/time.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport datetime\n\nFAKE_YEAR = 9999\nDEFAULT_FUTURE = datetime.datetime(FAKE_YEAR, 12, 31, 23, 59, 59)\nDEFAULT_PAST = datetime.datetime(FAKE_YEAR, 1, 1, 0, 0)\n\n\ndef __get_pdt_calendar():\n    import parsedatetime as pdt\n\n    consts = pdt.Constants(usePyICU=False)\n    consts.DOWParseStyle = -1  # \"Monday\" will be either today or the last Monday\n    calendar = pdt.Calendar(consts, version=pdt.VERSION_CONTEXT_STYLE)\n\n    return calendar\n\n\ndef parse(\n    date_str: str | datetime.datetime,\n    inclusive: bool = False,\n    default_hour: int | None = None,\n    default_minute: int | None = None,\n    bracketed: bool = False,\n) -> datetime.datetime | None:\n    \"\"\"Parses a string containing a fuzzy date and returns a datetime.datetime object\"\"\"\n    if not date_str:\n        return None\n    elif isinstance(date_str, datetime.datetime):\n        return date_str\n\n    # Don't try to parse anything with 6 or fewer characters and was parsed from the\n    # existing journal. It's probably a markdown footnote\n    if len(date_str) <= 6 and bracketed:\n        return None\n\n    default_date = DEFAULT_FUTURE if inclusive else DEFAULT_PAST\n    date = None\n    year_present = False\n\n    hasTime = False\n    hasDate = False\n\n    while not date:\n        try:\n            from dateutil.parser import parse as dateparse\n\n            date = dateparse(date_str, default=default_date)\n            if date.year == FAKE_YEAR:\n                date = datetime.datetime(\n                    datetime.datetime.now().year, date.timetuple()[1:6]\n                )\n            else:\n                year_present = True\n            hasTime = not (date.hour == date.minute == 0)\n            hasDate = True\n            date = date.timetuple()\n        except Exception as e:\n            if e.args[0] == \"day is out of range for month\":\n                y, m, d, H, M, S = default_date.timetuple()[:6]\n                default_date = datetime.datetime(y, m, d - 1, H, M, S)\n            else:\n                calendar = __get_pdt_calendar()\n                date, parse_context = calendar.parse(date_str)\n                hasTime = parse_context.hasTime\n                hasDate = parse_context.hasDate\n\n    if not hasDate and not hasTime:\n        try:  # Try and parse this as a single year\n            year = int(date_str)\n            return datetime.datetime(year, 1, 1)\n        except ValueError:\n            return None\n        except TypeError:\n            return None\n\n    if hasDate and not hasTime:\n        date = datetime.datetime(  # Use the default time\n            *date[:3],\n            hour=23 if inclusive else default_hour or 0,\n            minute=59 if inclusive else default_minute or 0,\n            second=59 if inclusive else 0,\n        )\n    else:\n        date = datetime.datetime(*date[:6])\n\n    # Ugly heuristic: if the date is more than 4 weeks in the future, we got the year\n    # wrong. Rather than this, we would like to see parsedatetime patched so we can\n    # tell it to prefer past dates\n    dt = datetime.datetime.now() - date\n    if dt.days < -28 and not year_present:\n        date = date.replace(date.year - 1)\n    return date\n\n\ndef is_valid_date(year: int, month: int, day: int) -> bool:\n    try:\n        datetime.datetime(year, month, day)\n        return True\n    except ValueError:\n        return False\n"
  },
  {
    "path": "jrnl/upgrade.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport logging\nimport os\n\nfrom jrnl import __version__\nfrom jrnl.config import is_config_json\nfrom jrnl.config import load_config\nfrom jrnl.config import scope_config\nfrom jrnl.exception import JrnlException\nfrom jrnl.journals import Journal\nfrom jrnl.journals import open_journal\nfrom jrnl.messages import Message\nfrom jrnl.messages import MsgStyle\nfrom jrnl.messages import MsgText\nfrom jrnl.output import print_msg\nfrom jrnl.output import print_msgs\nfrom jrnl.path import expand_path\nfrom jrnl.prompt import yesno\n\n\ndef backup(filename: str, binary: bool = False):\n    filename = expand_path(filename)\n\n    try:\n        with open(filename, \"rb\" if binary else \"r\") as original:\n            contents = original.read()\n\n        with open(filename + \".backup\", \"wb\" if binary else \"w\") as backup:\n            backup.write(contents)\n\n        print_msg(\n            Message(\n                MsgText.BackupCreated, MsgStyle.NORMAL, {\"filename\": \"filename.backup\"}\n            )\n        )\n\n    except FileNotFoundError:\n        print_msg(Message(MsgText.DoesNotExist, MsgStyle.WARNING, {\"name\": filename}))\n        cont = yesno(f\"\\nCreate {filename}?\", default=False)\n        if not cont:\n            raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING))\n\n\ndef check_exists(path: str) -> bool:\n    \"\"\"\n    Checks if a given path exists.\n    \"\"\"\n    return os.path.exists(path)\n\n\ndef upgrade_jrnl(config_path: str) -> None:\n    config = load_config(config_path)\n\n    print_msg(Message(MsgText.WelcomeToJrnl, MsgStyle.NORMAL, {\"version\": __version__}))\n\n    encrypted_journals = {}\n    plain_journals = {}\n    other_journals = {}\n    all_journals = []\n\n    for journal_name, journal_conf in config[\"journals\"].items():\n        if isinstance(journal_conf, dict):\n            path = expand_path(journal_conf.get(\"journal\"))\n            encrypt = journal_conf.get(\"encrypt\")\n        else:\n            encrypt = config.get(\"encrypt\")\n            path = expand_path(journal_conf)\n\n        if os.path.exists(path):\n            path = os.path.expanduser(path)\n        else:\n            print_msg(Message(MsgText.DoesNotExist, MsgStyle.ERROR, {\"name\": path}))\n            continue\n\n        if encrypt:\n            encrypted_journals[journal_name] = path\n        elif os.path.isdir(path):\n            other_journals[journal_name] = path\n        else:\n            plain_journals[journal_name] = path\n\n    kwargs = {\n        # longest journal name\n        \"pad\": max([len(journal) for journal in config[\"journals\"]]),\n    }\n\n    _print_journal_summary(\n        journals=encrypted_journals,\n        header=Message(\n            MsgText.JournalsToUpgrade,\n            params={\n                \"version\": __version__,\n            },\n        ),\n        **kwargs,\n    )\n\n    _print_journal_summary(\n        journals=plain_journals,\n        header=Message(\n            MsgText.JournalsToUpgrade,\n            params={\n                \"version\": __version__,\n            },\n        ),\n        **kwargs,\n    )\n\n    _print_journal_summary(\n        journals=other_journals,\n        header=Message(MsgText.JournalsToIgnore),\n        **kwargs,\n    )\n\n    cont = yesno(Message(MsgText.ContinueUpgrade), default=False)\n    if not cont:\n        raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING))\n\n    for journal_name, path in encrypted_journals.items():\n        print_msg(\n            Message(\n                MsgText.UpgradingJournal,\n                params={\n                    \"journal_name\": journal_name,\n                    \"path\": path,\n                },\n            )\n        )\n\n        backup(path, binary=True)\n        old_journal = open_journal(\n            journal_name, scope_config(config, journal_name), legacy=True\n        )\n\n        logging.debug(f\"Clearing encryption method for '{journal_name}' journal\")\n\n        # Update the encryption method\n        new_journal = Journal.from_journal(old_journal)\n        new_journal.config[\"encrypt\"] = \"jrnlv2\"\n        new_journal._get_encryption_method()\n        # Copy over password (jrnlv1 only supported password-based encryption)\n        new_journal.encryption_method.password = old_journal.encryption_method.password\n\n        all_journals.append(new_journal)\n\n    for journal_name, path in plain_journals.items():\n        print_msg(\n            Message(\n                MsgText.UpgradingJournal,\n                params={\n                    \"journal_name\": journal_name,\n                    \"path\": path,\n                },\n            )\n        )\n\n        backup(path)\n        old_journal = open_journal(\n            journal_name, scope_config(config, journal_name), legacy=True\n        )\n        all_journals.append(Journal.from_journal(old_journal))\n\n    # loop through lists to validate\n    failed_journals = [j for j in all_journals if not j.validate_parsing()]\n\n    if len(failed_journals) > 0:\n        raise JrnlException(\n            Message(MsgText.AbortingUpgrade, MsgStyle.WARNING),\n            Message(\n                MsgText.JournalFailedUpgrade,\n                MsgStyle.ERROR,\n                {\n                    \"s\": \"s\" if len(failed_journals) > 1 else \"\",\n                    \"failed_journals\": \"\\n\".join(j.name for j in failed_journals),\n                },\n            ),\n        )\n\n    # write all journals - or - don't\n    for j in all_journals:\n        j.write()\n\n    print_msg(Message(MsgText.UpgradingConfig, MsgStyle.NORMAL))\n\n    backup(config_path)\n\n    print_msg(Message(MsgText.AllDoneUpgrade, MsgStyle.NORMAL))\n\n\ndef is_old_version(config_path: str) -> bool:\n    return is_config_json(config_path)\n\n\ndef _print_journal_summary(journals: dict, header: Message, pad: int) -> None:\n    if not journals:\n        return\n\n    msgs = [header]\n    for journal, path in journals.items():\n        msgs.append(\n            Message(\n                MsgText.PaddedJournalName,\n                params={\n                    \"journal_name\": journal,\n                    \"path\": path,\n                    \"pad\": pad,\n                },\n            )\n        )\n    print_msgs(msgs)\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: jrnl\nsite_url: https://jrnl.sh\ntheme:\n  name: readthedocs\n  custom_dir: docs_theme\n  static_templates:\n      - index.html\nwatch:\n  - docs\n  - docs_theme\nextra_css:\n    - https://fonts.googleapis.com/css?family=Open+Sans:300,600\n    - assets/colors.css\n    - assets/theme.css\n    - assets/highlight.css\nmarkdown_extensions:\n  - admonition\nrepo_url: https://github.com/jrnl-org/jrnl/\nedit_uri: edit/develop/docs/\nsite_author: jrnl contributors\nsite_description: Collect your thoughts and notes without leaving the command line.\nnav:\n  - Overview: overview.md\n  - 'User Guide':\n    - Quickstart: installation.md\n    - Basic Usage: usage.md\n    - Encryption: encryption.md\n    - Journal Types: journal-types.md\n    - Privacy and Security: privacy-and-security.md\n    - Formats: formats.md\n    - Advanced Usage: advanced.md\n    - 'External Editors': external-editors.md\n    - 'Tips and Tricks': tips-and-tricks.md\n  - 'Reference':\n    - Command Line: reference-command-line.md\n    - Configuration File: reference-config-file.md\n  - 'Contributing':\n    - Contributing to jrnl: contributing.md\n\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"devDependencies\": {\n    \"pa11y-ci\": \"4.0.0\"\n  }\n}\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"jrnl\"\ndescription = \"Collect your thoughts and notes without leaving the command line.\"\nlicense = \"GPL-3.0-only\"\ndynamic = [\"version\", \"classifiers\"]\nreadme = \"README.md\"\nauthors = [\n  {name = \"Micah Ellison\", email = \"micahellison@gmail.com\"},\n  {name = \"Jonathan Wren\", email = \"jonathan@nowandwren.com\"},\n  {name = \"Manuel Ebert\", email = \"manuel@1450.me\"},\n  {name = \"jrnl contributors\", email = \"maintainers@jrnl.sh\"}\n]\nmaintainers = [{name = \"Jonathan Wren and Micah Ellison\", email = \"maintainers@jrnl.sh\"}]\nrequires-python = '>=3.10.0,<3.15'\ndependencies = [\n  'colorama (>=0.4)', # https://github.com/tartley/colorama/blob/master/CHANGELOG.rst\n  'cryptography (>=3.0)', # https://cryptography.io/en/latest/api-stability.html\n  'keyring (>=21.0)', # https://github.com/jaraco/keyring#integration\n  'parsedatetime (>=2.6)',\n  'python-dateutil (>=2.8,<3.0)', # https://github.com/dateutil/dateutil/blob/master/RELEASING\n  'pyxdg (>=0.27.0)',\n  'ruamel.yaml (>=0.17.22)',\n  'ruamel.yaml.clib (>=0.2.12)', # https://sourceforge.net/p/ruamel-yaml-clib/tickets/36/\n  'rich (>=14.3.2,<14.4.0)',\n  'tzlocal (>=4.0)' # https://github.com/regebro/tzlocal/blob/master/CHANGES.txt\n]\n\n[project.urls]\nhomepage = \"https://jrnl.sh\"\nrepository = \"https://github.com/jrnl-org/jrnl\"\nDocumentation = \"https://jrnl.sh\"\n\"Issue Tracker\" = \"https://github.com/jrnl-org/jrnl/issues\"\nFunding = \"https://opencollective.com/jrnl\"\n\n[project.scripts]\njrnl = 'jrnl.main:run'\n\n[tool.poetry]\nversion = \"4.3\"\nclassifiers = [\n  \"Topic :: Office/Business :: News/Diary\",\n  \"Environment :: Console\",\n  \"Operating System :: OS Independent\"\n]\n\n[tool.poetry.group.dev.dependencies]\nblack = { version = \">=21.5b2\", allow-prereleases = true }\nipdb = \"*\"\nmkdocs = \">=1.4\"\nparse-type = \">=0.6.0\"\npoethepoet = \"*\"\npytest = \">=8.1\"\npytest-bdd = \">=8.0\"\npytest-clarity = \"*\"\npytest-xdist = \">=2.5.0\"\nrequests = \"*\"\nruff = \">=0.0.276\"\ntoml = { version = \">=0.10\", markers = \"python_version < '3.11'\" }\ntox = \"*\"\nxmltodict = \"*\"\n\n[tool.poe.tasks]\ndocs-check.default_item_type = \"script\"\ndocs-check.sequence = [\n  \"tasks:delete_files(['sitemap.xml', 'config.json'])\",\n  \"tasks:generate_sitemap\",\n  \"tasks:output_file('sitemap.xml')\",\n  \"tasks:generate_pa11y_config_from_sitemap\",\n  \"tasks:output_file('config.json')\",\n  \"tasks:run_shell('npx pa11y-ci -c config.json')\",\n  \"tasks:delete_files(['sitemap.xml', 'config.json'])\",\n]\ndocs-run = [\n  {cmd = \"mkdocs serve\"},\n]\n\ntest-run = [\n  {cmd = \"tox -q -e py --\"},\n]\n\n# Groups of tasks\nformat.default_item_type = \"cmd\"\nformat.sequence = [\n  \"ruff check . --select I --fix\", # equivalent to \"isort .\"\n  \"black .\",\n]\n\nlint.default_item_type = \"cmd\"\nlint.sequence = [\n  \"poetry --version\",\n  \"poetry check\",\n  \"ruff --version\",\n  \"ruff check .\",\n  \"black --version\",\n  \"black --check .\"\n]\n\ntest = [\n  \"lint\",\n  \"test-run\",\n]\n\n[tool.pytest.ini_options]\nminversion = \"6.0\"\nrequired_plugins = [\n  \"pytest-bdd\"\n]\nmarkers = [\n  \"todo\",\n  \"skip_win\",\n  \"skip_posix\",\n  \"on_win\",\n  \"on_posix\",\n]\naddopts = [\n  \"--pdbcls=IPython.terminal.debugger:Pdb\",\n  \"--tb=native\",\n  \"-n=auto\",\n]\nfilterwarnings = [\n  \"ignore::DeprecationWarning\",\n  \"ignore:[WinError 32].*\",\n  \"ignore:[WinError 5].*\"\n]\n\n[tool.ruff]\nline-length = 88\ntarget-version = \"py310\"\n\n# https://beta.ruff.rs/docs/rules/\nlint.select = [ \n  'F',     # Pyflakes\n  'E',     # pycodestyle errors\n  'W',     # pycodestyle warnings\n  'I',     # isort\n  'ASYNC', # flake8-async\n  'S110',  # try-except-pass\n  'S112',  # try-except-continue\n  'EM',    # flake8-errmsg\n  'ISC',   # flake8-implicit-str-concat\n  'Q',     # flake8-quotes\n  'RSE',   # flake8-raise\n  'TID',   # flake8-tidy-imports\n  'TCH',   # flake8-type-checking\n  'T100',  # debugger, don't allow break points\n  'ICN'    # flake8-import-conventions\n]\nexclude = [\".git\", \".tox\", \".venv\", \"node_modules\"]\n\n[tool.ruff.lint.isort]\nforce-single-line = true\nknown-first-party = [\"jrnl\", \"tests\"]\n\n[tool.ruff.lint.per-file-ignores]\n\"__init__.py\" = [\"F401\"] # unused imports\n\n[build-system]\nrequires = [\"poetry-core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.tox]\n# see: https://tox.wiki/en/latest/example/basic.html\nlegacy_tox_ini = \"\"\"\n[tox]\nenvlist = py\nisolated_build = True\n\n[testenv]\ndeps =\n  pytest >=8.1\n  pytest-bdd >=8.0\n  pytest-xdist >=2.5.0\n  parse-type >=0.6.0\n  toml >=0.10; python_version < '3.11'\n\ncommands = pytest {posargs}\npassenv = HOME\n\"\"\"\n"
  },
  {
    "path": "tasks.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport json\nimport os\nimport pathlib\nimport subprocess\n\nimport requests\nimport xmltodict\n\nDOCS_URL = \"http://127.0.0.1:8000\"\nSITEMAP_FILENAME = \"sitemap.xml\"\nCONFIG_FILENAME = \"config.json\"\n\n\ndef delete_files(files):\n    for file in files:\n        pathlib.Path(file).unlink(missing_ok=True)\n\n\ndef run_shell(command):\n    # Required to run NPM commands in Windows and *nix\n    subprocess.run(command, check=True, shell=True)\n\n\ndef generate_sitemap():\n    sitemap = requests.get(f\"{DOCS_URL}/{SITEMAP_FILENAME}\")\n    with open(SITEMAP_FILENAME, \"wb\") as f:\n        f.write(sitemap.content)\n\n\ndef generate_pa11y_config_from_sitemap():\n    with open(SITEMAP_FILENAME) as f:\n        xml_sitemap = xmltodict.parse(f.read())\n\n    urls = [\n        f\"{DOCS_URL}/\",\n        f\"{DOCS_URL}/search.html?q=jrnl\",\n    ]\n    urls += [url[\"loc\"] for url in xml_sitemap[\"urlset\"][\"url\"]]\n\n    with open(CONFIG_FILENAME, \"w\") as f:\n        f.write(\n            json.dumps(\n                {\n                    \"defaults\": {\"chromeLaunchConfig\": {\"args\": [\"--no-sandbox\"]}},\n                    \"urls\": urls,\n                }\n            )\n        )\n\n\ndef output_file(file):\n    if not os.getenv(\"CI\", False):\n        return\n\n    print(f\"::group::{file}\")\n\n    with open(file) as f:\n        print(f.read())\n\n    print(\"::endgroup::\")\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/bdd/features/actions.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Test combinations of edit, change-time, and delete\n\n    Scenario Outline: --change-time with --edit modifies selected entries\n        Given we use the config \"<config_file>\"\n        And we write nothing to the editor if opened\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30' --edit\" and enter\n            \"\"\"\n            Y\n            N\n            Y\n            \"\"\"\n        Then the error output should contain \"No text received from editor. Were you trying to delete all the entries?\"\n        And the editor should have been called\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2022-04-23 10:30 Entry the first.\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_folder.yaml    |\n        | basic_encrypted.yaml |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario Outline: --delete with --edit deletes selected entries\n        Given we use the config \"<config_file>\"\n        And we append to the editor if opened\n          \"\"\"\n          [2023-02-21 10:32] Here is a new entry\n          \"\"\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --delete --edit\" and enter\n            \"\"\"\n            Y\n            N\n            Y\n            \"\"\"\n        Then the editor should have been called\n        And the error output should contain \"3 entries found\"\n        And the error output should contain \"2 entries deleted\"\n        And the error output should contain \"1 entry added\"\n        When we run \"jrnl -99 --short\"\n        Then the error output should contain \"2 entries found\"\n        And the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2023-02-21 10:32 Here is a new entry\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_folder.yaml    |\n        | basic_encrypted.yaml |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario Outline: --change-time with --delete affects appropriate entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        # --change-time is asked first, then --delete\n        When we run \"jrnl --change-time '2022-04-23 10:30' --delete\" and enter\n            \"\"\"\n            N\n            N\n            Y\n            Y\n            N\n            N\n            \"\"\"\n        Then the error output should contain \"3 entries found\"\n        And the error output should contain \"1 entry deleted\"\n        And the error output should contain \"1 entry modified\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_folder.yaml    |\n        | basic_encrypted.yaml |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario Outline: Combining --change-time and --delete and --edit affects appropriate entries\n        Given we use the config \"<config_file>\"\n        And we append to the editor if opened\n            \"\"\"\n            [2023-02-21 10:32] Here is a new entry\n            \"\"\"\n        And we use the password \"test\" if prompted\n        # --change-time is asked first, then --delete, then --edit\n        When we run \"jrnl --change-time '2022-04-23 10:30' --delete --edit\" and enter\n            \"\"\"\n            N\n            Y\n            Y\n            Y\n            Y\n            N\n            \"\"\"\n        Then the error output should contain \"3 entries found\"\n        And the error output should contain \"2 entries deleted\"\n        And the error output should contain \"1 entry modified\"\n        And the error output should contain \"1 entry added\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            2023-02-21 10:32 Here is a new entry\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_folder.yaml    |\n        | basic_encrypted.yaml |\n        # | basic_dayone.yaml    | @todo\n"
  },
  {
    "path": "tests/bdd/features/build.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Build process\n\n    Scenario: Version numbers should stay in sync\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl --version\"\n        Then we should get no error\n        And the output should contain pyproject.toml version\n"
  },
  {
    "path": "tests/bdd/features/change_time.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Change entry times in journal\n    Scenario Outline: Change time flag changes single entry timestamp\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -1\"\n        Then the output should contain \"2020-09-24 09:14 The third entry finally\"\n        When we run \"jrnl -1 --change-time '2022-04-23 10:30'\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry modified\"\n        And the error output should not contain \"deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario Outline: Change flag changes prompted entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n        When we run \"jrnl --change-time '2022-04-23 10:30'\" and enter\n            \"\"\"\n            Y\n            N\n            Y\n            \"\"\"\n        Then the error output should contain \"3 entries found\"\n        And the error output should contain \"2 entries modified\"\n        When we run \"jrnl --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2022-04-23 10:30 Entry the first.\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Answering \"N\" to change-time prompt deletes no entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -1\"\n        Then the output should contain \"2020-09-24 09:14 The third entry finally\"\n        When we run \"jrnl -1 --change-time '2023-02-21 10:30'\" and enter\n            \"\"\"\n            N\n            \"\"\"\n        Then the error output should not contain \"modified\"\n        And the error output should not contain \"deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml  | @todo\n\n    Scenario Outline: Change time flag with nonsense input changes nothing\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time now asdfasdf\"\n        Then the output should contain \"No entries to modify\"\n        And the error output should not contain \"entries modified\"\n        And the error output should not contain \"entries deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        | basic_dayone.yaml     |\n\n\n    Scenario Outline: Change time flag with tag only changes tagged entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30' @ipsum\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry found\"\n        And the error output should contain \"1 entry modified\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            2022-04-23 10:30 Entry the first.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with multiple tags changes all entries matching any of the tags\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30'  @ipsum @tagthree\" and enter\n            \"\"\"\n            Y\n            Y\n            \"\"\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2022-04-23 10:30 Entry the first.\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with -and changes boolean AND of tagged entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30' -and @tagone @tagtwo\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            2022-04-23 10:30 Entry the first.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with -not does not change entries from given tag\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30' @tagone -not @ipsum\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with -from search operator only changes entries since that date\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30' -from 2020-09-01\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2022-04-23 10:30 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with -to only changes entries up to specified date\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30' -to 2020-08-31\" and enter\n            \"\"\"\n            Y\n            Y\n            \"\"\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            2022-04-23 10:30 Entry the first.\n            2022-04-23 10:30 A second entry in what I hope to be a long series.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with -starred only changes starred entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30' -starred\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            2022-04-23 10:30 A second entry in what I hope to be a long series.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with -contains only changes entries containing expression\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time '2022-04-23 10:30'  -contains dignissim\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry modified\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            2022-04-23 10:30 Entry the first.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Change time flag with no entries specified changes nothing\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --change-time\" and enter\n            \"\"\"\n            N\n            N\n            N\n            \"\"\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file           |\n        | basic_onefile.yaml    |\n        | basic_folder.yaml     |\n        | basic_encrypted.yaml  |\n        | basic_dayone.yaml     |\n"
  },
  {
    "path": "tests/bdd/features/config_file.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Multiple journals\n\n    Scenario: Read a journal from an alternate config\n        Given the config \"basic_onefile.yaml\" exists\n        And we use the config \"multiple.yaml\"\n        When we run \"jrnl --cf basic_onefile.yaml -999\"\n        Then the output should not contain \"My first entry\"\n        And the output should contain \"Lorem ipsum\"\n\n    Scenario: Write to default journal by default using an alternate config\n        Given the config \"multiple.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl --cf multiple.yaml this goes to default\"\n        And we run \"jrnl -1\"\n        Then the output should not contain \"this goes to default\"\n        When we run \"jrnl --cf multiple.yaml -1\"\n        Then the output should contain \"this goes to default\"\n\n    Scenario: Write to specified journal using an alternate config\n        Given the config \"multiple.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl work --cf multiple.yaml a long day in the office\"\n        And we run \"jrnl default --cf multiple.yaml -1\"\n        Then the output should contain \"But I'm better\"\n        When we run \"jrnl work --cf multiple.yaml -1\"\n        Then the output should contain \"a long day in the office\"\n\n    Scenario: Tell user which journal was used while using an alternate config\n        Given the config \"multiple.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl --cf multiple.yaml work a long day in the office\"\n        Then the output should contain \"Entry added to work journal\"\n\n    Scenario: Write to specified journal with a timestamp using an alternate config\n        Given the config \"multiple.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl work --cf multiple.yaml 23 july 2012: a long day in the office\"\n        And we run \"jrnl --cf multiple.yaml -1\"\n        Then the output should contain \"But I'm better\"\n        When we run \"jrnl --cf multiple.yaml work -1\"\n        Then the output should contain \"a long day in the office\"\n        And the output should contain \"2012-07-23\"\n\n    Scenario: Write to specified journal without a timestamp but with colon using an alternate config\n        Given the config \"multiple.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl work --cf multiple.yaml : a long day in the office\"\n        And we run \"jrnl --cf multiple.yaml -1\"\n        Then the output should contain \"But I'm better\"\n        When we run \"jrnl --cf multiple.yaml work -1\"\n        Then the output should contain \"a long day in the office\"\n\n    Scenario: Create new journals as required using an alternate config\n        Given the config \"multiple.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl ideas -1\"\n        Then the output should be empty\n        When we run \"jrnl ideas --cf multiple.yaml 23 july 2012: sell my junk on ebay and make lots of money\"\n        Then the output should contain \"Journal 'ideas' created\"\n        When we run \"jrnl ideas --cf multiple.yaml -1\"\n        Then the output should contain \"sell my junk on ebay and make lots of money\"\n\n    Scenario: Don't crash if no default journal is specified using an alternate config\n        Given the config \"no_default_journal.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl --cf no_default_journal.yaml a long day in the office\"\n        Then the output should contain \"No 'default' journal configured\"\n\n    Scenario: Don't crash if no file exists for a configured encrypted journal using an alternate config\n        Given the config \"multiple.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl new_encrypted --cf multiple.yaml Adding first entry\" and enter\n            \"\"\"\n            these three eyes\n            these three eyes\n            n\n            \"\"\"\n        Then the output should contain \"Journal 'new_encrypted' created at \"\n\n    Scenario: Don't overwrite main config when encrypting a journal in an alternate config\n        Given the config \"basic_onefile.yaml\" exists\n        And we use the config \"multiple.yaml\"\n        When we run \"jrnl --cf basic_onefile.yaml --encrypt\" and enter\n            \"\"\"\n            these three eyes\n            these three eyes\n            n\n            \"\"\"\n        Then the output should contain \"Journal encrypted to features/journals/basic_onefile.journal\"\n        And the config should contain \"encrypt: false\"\n\n    Scenario: Don't overwrite main config when decrypting a journal in an alternate config\n        Given the config \"editor_encrypted.yaml\" exists\n        And we use the password \"bad doggie no biscuit\" if prompted\n        And we use the config \"basic_encrypted.yaml\"\n        When we run \"jrnl --cf editor_encrypted.yaml --decrypt\"\n        Then the config should contain \"encrypt: true\"\n        And the output should not contain \"Wrong password\"\n\n    Scenario: Show an error message when the config file is empty\n        Given we use the config \"empty_file.yaml\"\n        When we run \"jrnl -1\"\n        Then the error output should contain \"Unable to parse config file\"\n\n    Scenario: Show an error message when using --config-file with empty file\n        Given the config \"empty_file.yaml\" exists\n        And we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl --cf empty_file.yaml\"\n        Then the error output should contain \"Unable to parse config file\"\n\n    Scenario: Use a config file with linewrap set to 'auto'\n        Given we use the config \"linewrap_auto.yaml\"\n        When we run \"jrnl -1\"\n        Then the output should contain \"Life is good.\"\n\n    Scenario: Use a config file with linewrap set to 'auto' and use format 'fancy'\n        Given we use the config \"linewrap_auto.yaml\"\n        When we run \"jrnl -1 --format fancy\"\n        Then the output should contain \"Life is good.\"\n\n    Scenario: Show a warning message when the config file contains duplicate keys at the same level\n        Given the config \"duplicate_keys.yaml\" exists\n        And we use the config \"duplicate_keys.yaml\"\n        When we run \"jrnl -1\"\n        Then the output should contain \"There is at least one duplicate key in your configuration file\"\n\n    Scenario: Show a warning message when using --config-file with duplicate keys\n        Given the config \"duplicate_keys.yaml\" exists\n        And we use the config \"multiple.yaml\"\n        When we run \"jrnl --cf duplicate_keys.yaml -1\"\n        Then the output should contain \"There is at least one duplicate key in your configuration file\"\n\n    Scenario: Don't show a duplicate keys warning message when using --config-override on an existing value\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl --config-override highlight false\"\n        Then the output should not contain \"There is at least one duplicate key in your configuration file\"\n\n    Scenario: Update version number in config file when running newer version\n        Given we use the config \"format_md.yaml\"\n        When we run \"jrnl -1\"\n        Then the output should contain \"Configuration updated to newest version at\"\n        And the version in the config file should be up-to-date\n"
  },
  {
    "path": "tests/bdd/features/core.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Functionality of jrnl outside of actually handling journals\n\n    Scenario: Displaying the version number\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl --version\"\n        Then we should get no error\n        Then the output should match \"^jrnl v\\d+\\.\\d+(\\.\\d+)?(-(alpha|beta)\\d*)?\"\n\n    Scenario: Running the diagnostic command\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl --diagnostic\"\n        Then the output should contain \"jrnl\"\n        And the output should contain \"Python\"\n        And the output should contain \"OS\"\n\n    @todo\n    Scenario: Listing available journals\n\n"
  },
  {
    "path": "tests/bdd/features/datetime.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Reading and writing to journal with custom date formats\n\n    Scenario: Dates can include a time\n        # https://github.com/jrnl-org/jrnl/issues/117\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl 2013-11-30 15:42: Project Started.\"\n        Then we should get no error\n        When we run \"jrnl -999\"\n        Then the output should contain \"2013-11-30 15:42 Project Started.\"\n\n\n    Scenario: Dates can be in the future\n        # https://github.com/jrnl-org/jrnl/issues/185\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl 26/06/2099: Planet? Earth. Year? 2099.\"\n        Then we should get no error\n        When we run \"jrnl -999\"\n        Then the output should contain \"2099-06-26 09:00 Planet?\"\n\n\n    Scenario: Loading a sample journal with custom date\n        Given we use the config \"little_endian_dates.yaml\"\n        When we run \"jrnl -n 2\"\n        Then we should get no error\n        When we run \"jrnl -n 999\"\n        Then the output should be\n            \"\"\"\n            09.06.2013 15:39 My first entry.\n            | Everything is alright\n\n            10.07.2013 15:40 Life is good.\n            | But I'm better.\n            \"\"\"\n\n\n    Scenario Outline: Writing an entry from command line with custom date\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl <command>\"\n        Then we should get no error\n        When we run \"jrnl -n 1\"\n        Then the output should contain \"<expected_output>\"\n\n        Examples: Day-first Dates\n        | config_file              | command                      | expected_output                   |\n        | little_endian_dates.yaml | 2020-09-19: My first entry.  | 19.09.2020 09:00 My first entry.  |\n        | little_endian_dates.yaml | 2020-08-09: My second entry. | 09.08.2020 09:00 My second entry. |\n        | little_endian_dates.yaml | 2020-02-29: Test.            | 29.02.2020 09:00 Test.            |\n        | little_endian_dates.yaml | 2019-02-29: Test.            | 2019-02-29: Test.                 |\n        | little_endian_dates.yaml | 2020-08-32: Test.            | 2020-08-32: Test.                 |\n        | little_endian_dates.yaml | 2032-02-01: Test.            | 01.02.2032 09:00 Test.            |\n        | little_endian_dates.yaml | 2020-01-01: Test.            | 01.01.2020 09:00 Test.            |\n        | little_endian_dates.yaml | 2020-12-31: Test.            | 31.12.2020 09:00 Test.            |\n\n\n    Scenario Outline: Searching for dates with custom date\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl <command>\"\n        Then the output should be \"<expected_output>\"\n\n        Examples: Day-first Dates\n        | config_file              | command                    | expected_output                  |\n        | little_endian_dates.yaml | -on '2013-07-10' --short   | 10.07.2013 15:40 Life is good.   |\n        | little_endian_dates.yaml | -on 'june 9 2013' --short  | 09.06.2013 15:39 My first entry. |\n        | little_endian_dates.yaml | -on 'july 10 2013' --short | 10.07.2013 15:40 Life is good.   |\n        | little_endian_dates.yaml | -on 'june 2013' --short    | 09.06.2013 15:39 My first entry. |\n        | little_endian_dates.yaml | -on 'july 2013' --short    | 10.07.2013 15:40 Life is good.   |\n        # @todo month alone with no year should work\n        # | little_endian_dates.yaml | -on 'june' --short         | 09.06.2013 15:39 My first entry. |\n        # | little_endian_dates.yaml | -on 'july' --short         | 10.07.2013 15:40 Life is good.   |\n\n\n    Scenario: Writing an entry at the prompt with custom date\n        Given we use the config \"little_endian_dates.yaml\"\n        When we run \"jrnl\" and type \"2013-05-10: I saw Elvis. He's alive.\"\n        Then we should get no error\n        When we run \"jrnl -999\"\n        Then the output should contain \"10.05.2013 09:00 I saw Elvis.\"\n        And the output should contain \"He's alive.\"\n\n\n    Scenario: Viewing today's entries does not print the entire journal\n        # see: https://github.com/jrnl-org/jrnl/issues/741\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl -on today\"\n        Then the output should not contain \"Life is good\"\n        And the output should not contain \"But I'm better.\"\n\n\n    Scenario Outline: Create entry using day of the week as entry date one.\n        Given we use the config \"simple.yaml\"\n        And now is \"2019-03-12 01:30:32 PM\"\n        When we run \"jrnl <command>\"\n        Then we should get no error\n        When we run \"jrnl -1\"\n        Then the output should contain \"<expected_output>\"\n        Then the output should contain the date \"<date>\"\n\n        Examples: Days of the week\n        | command                         | expected_output      | date             |\n        | Monday: entry on a monday       | entry on a monday    | 2019-03-11 09:00 |\n        | Tuesday: entry on a tuesday     | entry on a tuesday   | 2019-03-05 09:00 |\n        | Wednesday: entry on a wednesday | entry on a wednesday | 2019-03-06 09:00 |\n        | Thursday: entry on a thursday   | entry on a thursday  | 2019-03-07 09:00 |\n        | Friday: entry on a friday       | entry on a friday    | 2019-03-08 09:00 |\n        | Saturday: entry on a saturday   | entry on a saturday  | 2019-03-09 09:00 |\n        | Sunday: entry on a sunday       | entry on a sunday    | 2019-03-10 09:00 |\n        | sunday: entry on a sunday       | entry on a sunday    | 2019-03-10 09:00 |\n        | sUndAy: entry on a sunday       | entry on a sunday    | 2019-03-10 09:00 |\n\n\n    Scenario Outline: Create entry using day of the week as entry date two.\n        Given we use the config \"simple.yaml\"\n        And now is \"2019-03-12 01:30:32 PM\"\n        When we run \"jrnl <command>\"\n        Then we should get no error\n        When we run \"jrnl -1\"\n        Then the output should contain \"<expected_output>\"\n        Then the output should contain the date \"<date>\"\n\n        Examples: Days of the week\n        | command                   | expected_output      | date             |\n        | Mon: entry on a monday    | entry on a monday    | 2019-03-11 09:00 |\n        | Tue: entry on a tuesday   | entry on a tuesday   | 2019-03-05 09:00 |\n        | Wed: entry on a wednesday | entry on a wednesday | 2019-03-06 09:00 |\n        | Thu: entry on a thursday  | entry on a thursday  | 2019-03-07 09:00 |\n        | Fri: entry on a friday    | entry on a friday    | 2019-03-08 09:00 |\n        | Sat: entry on a saturday  | entry on a saturday  | 2019-03-09 09:00 |\n        | Sun: entry on a sunday    | entry on a sunday    | 2019-03-10 09:00 |\n        | sun: entry on a sunday    | entry on a sunday    | 2019-03-10 09:00 |\n        | sUn: entry on a sunday    | entry on a sunday    | 2019-03-10 09:00 |\n\n\n    Scenario: Journals with unreadable dates should still be loaded\n        Given we use the config \"unreadabledates.yaml\"\n        When we run \"jrnl -2\"\n        Then the output should contain \"I've lost track of time.\"\n        And the output should contain \"Time has no meaning.\"\n\n\n    Scenario: Journals with readable dates AND unreadable dates should still contain all data.\n        Given we use the config \"mostlyreadabledates.yaml\"\n        When we run \"jrnl --short\"\n        Then the output should be\n            \"\"\"\n            2019-07-01 14:23 The third entry\n            2019-07-18 14:23 The first entry\n            2019-07-19 14:23 The second entry\n            \"\"\"\n\n\n    Scenario: Update near-valid dates after journal is edited\n        Given we use the config \"mostlyreadabledates.yaml\"\n        When we run \"jrnl 2222-08-19: I have made it exactly one month into the future.\"\n        When we run \"jrnl -2\"\n        Then the output should contain \"2019-07-19 14:23 The second entry\"\n\n\n    Scenario: Integers in square brackets should not be read as dates\n        Given we use the config \"brackets.yaml\"\n        When we run \"jrnl -1\"\n        Then the output should contain \"[1] line starting with 1\"\n\n\n    # broken still\n    @skip\n    Scenario: Dayone entries without timezone information are interpreted in current timezone\n        Given we use the config \"dayone.yaml\"\n        When we run \"jrnl -until 'feb 2013'\"\n        Then we should get no error\n        And the output should contain \"2013-01-17T18:37Z\" in the local time\n\n\n    Scenario: Loading entry with ambiguous time stamp in timezone-aware journal (like Dayone)\n        #https://github.com/jrnl-org/jrnl/issues/153\n        Given we use the config \"bug153.yaml\"\n        When we run \"jrnl -1\"\n        Then we should get no error\n        And the output should be\n            \"\"\"\n            2013-10-27 04:27 Some text.\n            \"\"\"\n\n\n    @skip #1422\n    Scenario Outline: Using \"tomorrow\" near daylight savings works in Dayone journals\n        Given we use the config \"dayone.yaml\"\n        And now is \"<date>\"\n        When we run \"jrnl yesterday: This thing happened yesterday\"\n        Then we should get no error\n        When we run \"jrnl today at 11:59pm: Adding an entry right now.\"\n        Then we should get no error\n        When we run \"jrnl tomorrow: A future entry.\"\n        Then we should get no error\n        When we run \"jrnl -from yesterday -to today\"\n        Then the output should contain \"This thing happened yesterday\"\n        And the output should contain \"Adding an entry right now.\"\n        And the output should not contain \"A future entry.\"\n\n        Examples: Dates\n        | date                   |\n        | 2022-02-10 01:00:00 PM |\n        | 2021-03-13 01:00:00 PM |\n        | 2021-11-06 01:00:00 PM |\n        | 2022-03-12 01:00:00 PM |\n        | 2022-11-05 01:00:00 PM |\n\n"
  },
  {
    "path": "tests/bdd/features/delete.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Delete entries from journal\n    Scenario Outline: Delete flag allows deletion of single entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -1\"\n        Then the output should contain \"2020-09-24 09:14 The third entry finally\"\n        When we run \"jrnl --delete\" and enter\n            \"\"\"\n            N\n            N\n            Y\n            \"\"\"\n        Then the error output should contain \"3 entries found\"\n        And the error output should contain \"1 entry deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            \"\"\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Backing out of interactive delete does not change journal\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete -n 1\" and enter\n            \"\"\"\n            N\n            \"\"\"\n        Then the error output should not contain \"deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n\n    Scenario Outline: Delete flag with nonsense input deletes nothing (issue #932)\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete asdfasdf\"\n        Then the error output should contain \"No entries to delete\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n\n    Scenario Outline: Delete flag with tag only deletes tagged entries\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete @ipsum\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry found\"\n        Then the error output should contain \"1 entry deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Delete flag with multiple tags deletes all entries matching any of the tags\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete @ipsum @tagthree\" and enter\n            \"\"\"\n            Y\n            Y\n            \"\"\"\n        Then the error output should contain \"2 entries found\"\n        And the error output should contain \"2 entries deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Delete flag with -and deletes boolean AND of tagged entries\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete -and @tagone @tagtwo\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry found\"\n        And the error output should contain \"1 entry deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Delete flag with -not does not delete entries from given tag\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete @tagone -not @ipsum\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry found\"\n        And the error output should contain \"1 entry deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Delete flag with -from search operator only deletes entries since that date\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete -from 2020-09-01\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry found\"\n        And the error output should contain \"1 entry deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Delete flag with -to only deletes entries up to specified date\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete -to 2020-08-31\" and enter\n            \"\"\"\n            Y\n            Y\n            \"\"\"\n        Then the error output should contain \"2 entries found\"\n        And the error output should contain \"2 entries deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Delete flag with -starred only deletes starred entries\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete -starred\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n\n\n    Scenario Outline: Delete flag with -contains only entries containing expression\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --delete -contains dignissim\" and enter\n            \"\"\"\n            Y\n            \"\"\"\n        Then the error output should contain \"1 entry found\"\n        And the error output should contain \"1 entry deleted\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: Configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        # | basic_dayone.yaml  | @todo\n"
  },
  {
    "path": "tests/bdd/features/encrypt.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Encrypting and decrypting journals\n\n    Scenario: Decrypting a journal\n        Given we use the config \"encrypted.yaml\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl --decrypt\"\n        Then the output should contain \"Journal decrypted\"\n        And the config for journal \"default\" should contain \"encrypt: false\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n\n\n    @todo\n    Scenario: Trying to decrypt an already unencrypted journal\n        # This should warn the user that the journal is already encrypted\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl --decrypt\"\n        Then the config for journal \"default\" should contain \"encrypt: false\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n\n\n    Scenario: Trying to encrypt an already encrypted journal\n        Given we use the config \"encrypted.yaml\"\n        When we run \"jrnl --encrypt\" and enter \"bad doggie no biscuit\"\n        Then the output should contain \"already encrypted. Create a new password.\"\n        Then we should be prompted for a password\n\n    Scenario Outline: Encrypting a journal\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            swordfish\n            swordfish\n            n\n            \"\"\"\n        Then we should get no error\n        And the output should contain \"Journal encrypted\"\n        And the config for journal \"default\" should contain \"encrypt: true\"\n        When we run \"jrnl -n 1\" and enter \"swordfish\"\n        Then we should be prompted for a password\n        And the output should contain \"2013-06-10 15:40 Life is good\"\n\n    Scenario: Encrypt journal twice and get prompted each time\n        Given we use the config \"simple.yaml\"\n        And we don't have a keyring\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            swordfish\n            swordfish\n            y\n            \"\"\"\n        Then we should get no error\n        And the output should contain \"Journal encrypted\"\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            swordfish\n            tuna\n            tuna\n            y\n            \"\"\"\n        Then we should get no error\n        And the output should contain \"Journal default is already encrypted. Create a new password.\"\n        And we should be prompted for a password\n        And the config for journal \"default\" should contain \"encrypt: true\"\n\n    Scenario: Encrypt journal twice and get prompted each time with keyring\n        Given we use the config \"simple.yaml\"\n        And we have a keyring\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            swordfish\n            swordfish\n            y\n            \"\"\"\n        Then we should get no error\n        And the output should contain \"Journal encrypted\"\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            tuna\n            tuna\n            y\n            \"\"\"\n        Then we should get no error\n        And the output should contain \"Journal default is already encrypted. Create a new password.\"\n        And we should be prompted for a password\n        And the config for journal \"default\" should contain \"encrypt: true\"\n\n    Scenario Outline: Running jrnl with encrypt: true on unencryptable journals\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --config-override encrypt true here is a new entry\"\n        Then the error output should contain \"journal can't be encrypted\"\n\n        Examples: configs\n        | config_file       |\n        | basic_folder.yaml |\n        | basic_dayone.yaml |\n\n\n    Scenario Outline: Attempt to encrypt a folder or DayOne journal should result in an error\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --encrypt\"\n        Then the error output should contain \"can't be encrypted\"\n\n        Examples: configs\n        | config_file       |\n        | basic_folder.yaml |\n        | basic_dayone.yaml |\n"
  },
  {
    "path": "tests/bdd/features/file_storage.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Journals iteracting with the file system in a way that users can see\n\n    Scenario: Adding entries to a Folder journal should generate date files\n        Given we use the config \"empty_folder.yaml\"\n        When we run \"jrnl 23 July 2013: Testing folder journal.\"\n        Then we should get no error\n        And the journal directory should contain\n            \"\"\"\n            2013/07/23.txt\n            \"\"\"\n\n    Scenario: Adding multiple entries to a Folder journal should generate multiple date files\n        Given we use the config \"empty_folder.yaml\"\n        When we run \"jrnl 23 July 2013: Testing folder journal.\"\n        And we run \"jrnl 3/7/2014: Second entry of journal.\"\n        Then we should get no error\n        And the journal directory should contain\n            \"\"\"\n            2013/07/23.txt\n            \"\"\"\n\n    Scenario: If the journal and its parent directory don't exist, they should be created\n        Given we use the config \"missing_directory.yaml\"\n        Then the journal should not exist\n        When we run \"jrnl This is a new entry in my journal\"\n        Then the journal should exist\n        When we run \"jrnl -99 --short\"\n        Then the output should contain \"This is a new entry in my journal\"\n\n    Scenario: If the journal file doesn't exist, then it should be created\n        Given we use the config \"missing_journal.yaml\"\n        Then the journal should not exist\n        When we run \"jrnl This is a new entry in my journal\"\n        Then the journal should exist\n        When we run \"jrnl -99 --short\"\n        Then the output should contain \"This is a new entry in my journal\"\n\n    @on_posix\n    Scenario: If the directory for a Folder journal ending in a slash ('/') doesn't exist, then it should be created\n        Given we use the config \"missing_directory.yaml\"\n        Then the journal \"endslash\" directory should not exist\n        When we run \"jrnl endslash This is a new entry in my journal\"\n        Then the journal \"endslash\" directory should exist\n        When we run \"jrnl endslash -1\"\n        Then the output should contain \"This is a new entry in my journal\"\n\n    @on_win\n    Scenario: If the directory for a Folder journal ending in a backslash ('\\') doesn't exist, then it should be created\n        Given we use the config \"missing_directory.yaml\"\n        Then the journal \"endbackslash\" directory should not exist\n        When we run \"jrnl endbackslash This is a new entry in my journal\"\n        Then the journal \"endbackslash\" directory should exist\n        When we run \"jrnl endbackslash -1\"\n        Then the output should contain \"This is a new entry in my journal\"\n\n    Scenario: Creating journal with relative path should update to absolute path\n        Given we use no config\n        When we run \"jrnl hello world\" and enter\n            \"\"\"\n            test.txt\n            n\n            \\n\n            \"\"\"\n        Then the output should contain \"Journal 'default' created\"\n        When we change directory to \"subfolder\"\n        And we run \"jrnl -n 1\"\n        Then the output should contain \"hello world\"\n\n    Scenario: the temporary filename suffix should default to \".jrnl\"\n        Given we use the config \"editor.yaml\"\n        When we run \"jrnl --edit\"\n        Then the editor should have been called\n        Then the editor filename should end with \".jrnl\"\n\n    Scenario: the temporary filename suffix should be \"-{template_filename}\"\n        Given we use the config \"editor_markdown_extension.yaml\"\n        When we run \"jrnl --edit\"\n        Then the editor filename should end with \"-extension.md\"\n"
  },
  {
    "path": "tests/bdd/features/format.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Custom formats\n\n    Scenario Outline: Short printing via --format flag\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --format short -3\"\n        Then we should get no error\n\n        Examples: configs\n        |   config_file          |\n        |   basic_onefile.yaml   |\n        |   basic_encrypted.yaml |\n        |   basic_folder.yaml    |\n        |   basic_dayone.yaml    |\n\n\n    Scenario Outline: Pretty Printing aka the Default\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --format pretty -3\"\n        Then we should get no error\n\n        Examples: configs\n        |   config_file          |\n        |   basic_onefile.yaml   |\n        |   basic_encrypted.yaml |\n        |   basic_folder.yaml    |\n        |   basic_dayone.yaml    |\n\n\n    Scenario Outline: JSON format\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --format json\"\n        Then we should get no error\n        And the output should be valid JSON\n        Given we parse the output as JSON\n        Then \"entries\" in the parsed output should have 3 elements\n        And \"tags\" in the parsed output should be\n            \"\"\"\n            @ipsum\n            @tagone\n            @tagtwo\n            @tagthree\n            \"\"\"\n        And \"entries.0.tags\" in the parsed output should have 3 elements\n        And \"entries.1.tags\" in the parsed output should have 1 elements\n        And \"entries.2.tags\" in the parsed output should have 2 elements\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario: Exporting dayone to json should include UUID\n        Given we use the config \"dayone.yaml\"\n        When we run \"jrnl --export json\"\n        Then we should get no error\n        And the output should be valid JSON\n        Given we parse the output as JSON\n        Then \"entries.0.uuid\" in the parsed output should be\n            \"\"\"\n            4BB1F46946AD439996C9B59DE7C4DDC1\n            \"\"\"\n\n    Scenario Outline: Printing a journal that has multiline entries with tags\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -n 1 @ipsum\"\n        Then we should get no error\n        And the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            | Lorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada\n            | quis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus \n            | pellentesque\n            | augue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu\n            | consequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In\n            | commodo nulla nec tellus placerat, sed ultricies metus bibendum. Duis eget\n            | venenatis erat. In at dolor dui. @tagone and maybe also @tagtwo.\n            | \n            | Curabitur accumsan nunc ac neque tristique, eleifend faucibus justo\n            | ullamcorper. Suspendisse at mattis nunc. Nullam eget lacinia urna. Suspendisse\n            | potenti. Ut urna est, venenatis sed ante in, ultrices congue mi. Maecenas eget\n            | molestie metus. Mauris porttitor dui ornare gravida porta. Quisque sed lectus\n            | hendrerit, lacinia ante eget, vulputate ante. Aliquam vitae erat non felis\n            | feugiat sagittis. Phasellus quis arcu fringilla, mattis ligula id, vestibulum\n            | urna. Vivamus facilisis leo a mi tincidunt condimentum. Donec eu euismod enim.\n            | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu ligula eget\n            | velit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac\n            | porta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per\n            | conubia nostra, per inceptos himenaeos.\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Exporting using filters should only export parts of the journal\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -until 'August 2020' --format json\"\n        Then the output should be valid JSON\n        Then we should get no error\n        And the output should be valid JSON\n        Given we parse the output as JSON\n        Then \"entries\" in the parsed output should have 2 elements\n        And \"tags\" in the parsed output should be\n            \"\"\"\n            @ipsum\n            @tagone\n            @tagtwo\n            \"\"\"\n        And \"entries.0.tags\" in the parsed output should have 3 elements\n        And \"entries.1.tags\" in the parsed output should have 1 elements\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Increasing Headings on Markdown export\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        Given we append to the editor if opened\n            \"\"\"\n            [2021-10-14 13:23] Heading Test\n\n            H1-1\n            =\n\n            H1-2\n            ===\n\n            H1-3\n            ============================\n\n            H2-1\n            -\n\n            H2-2\n            ---\n\n            H2-3\n            ----------------------------------\n\n            Horizontal Rules (ignore)\n\n            ---\n\n            ===\n\n            # ATX H1\n\n            ## ATX H2\n\n            ### ATX H3\n\n            #### ATX H4\n\n            ##### ATX H5\n\n            ###### ATX H6\n\n            Stuff\n\n            More stuff\n            more stuff again\n            \"\"\"\n        When we run \"jrnl --edit -1\"\n        Then the editor should have been called\n        When we run \"jrnl -1 --export markdown\"\n        Then the output should be\n            \"\"\"\n            # 2021\n\n            ## October\n\n            ### 2021-10-14 13:23 Heading Test\n\n            #### H1-1\n\n            #### H1-2\n\n            #### H1-3\n\n            ##### H2-1\n\n            ##### H2-2\n\n            ##### H2-3\n\n            Horizontal Rules (ignore)\n\n            ---\n\n            ===\n\n            #### ATX H1\n\n            ##### ATX H2\n\n            ###### ATX H3\n\n            ####### ATX H4\n\n            ######## ATX H5\n\n            ######### ATX H6\n\n            Stuff\n\n            More stuff\n            more stuff again\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n    # | basic_dayone.yaml    | @todo\n\n    @skip\n    Scenario Outline: Exporting to XML\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --export xml\"\n        Then the output should be a valid XML string\n        And \"entries\" in the xml output should have 3 elements\n        And \"tags\" in the xml output should contain\n            \"\"\"\n            @ipsum\n            @tagone\n            @tagtwo\n            @tagthree\n            \"\"\"\n        And there should be 10 \"tag\" elements\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario: Exporting to XML single\n        Given we use the config \"tags.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --export xml\"\n        Then the output should be valid XML\n        Given we parse the output as XML\n        Then \"entries\" in the parsed output should have 2 elements\n        And \"tags\" in the parsed output should be\n            \"\"\"\n            @idea\n            @journal\n            @dan\n            \"\"\"\n        And there should be 7 \"tag\" elements\n\n    Scenario Outline: Exporting tags\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --export tags\"\n        Then the output should be\n            \"\"\"\n            @tagtwo              : 2\n            @tagone              : 2\n            @tagthree            : 1\n            @ipsum               : 1\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n\n    Scenario Outline: Export fancy with small linewrap\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --config-override linewrap 35 --format fancy -3\"\n        Then we should get no error\n        And the output should be 35 columns wide\n\n        Examples: configs\n        |   config_file          |\n        |   basic_onefile.yaml   |\n        |   basic_encrypted.yaml |\n        |   basic_folder.yaml    |\n        |   basic_dayone.yaml    |\n\n\n    @todo\n    Scenario Outline: Exporting fancy\n        # Needs better emoji support\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --export fancy\"\n        Then the output should be\n            \"\"\"\n            ┎──────────────────────────────────────────────────────────────╮2020-08-29 11:11\n            ┃ Entry the first.                                             ╘═══════════════╕\n            ┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n            ┃ Lorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada │\n            ┃ quis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus           │\n            ┃ pellentesque                                                                 │\n            ┃ augue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu │\n            ┃ consequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In   │\n            ┃ commodo nulla nec tellus placerat, sed ultricies metus bibendum. Duis eget   │\n            ┃ venenatis erat. In at dolor dui. @tagone and maybe also @tagtwo.             │\n            ┃                                                                              │\n            ┃ Curabitur accumsan nunc ac neque tristique, eleifend faucibus justo          │\n            ┃ ullamcorper. Suspendisse at mattis nunc. Nullam eget lacinia urna.           │\n            ┃ Suspendisse                                                                  │\n            ┃ potenti. Ut urna est, venenatis sed ante in, ultrices congue mi. Maecenas    │\n            ┃ eget                                                                         │\n            ┃ molestie metus. Mauris porttitor dui ornare gravida porta. Quisque sed       │\n            ┃ lectus                                                                       │\n            ┃ hendrerit, lacinia ante eget, vulputate ante. Aliquam vitae erat non felis   │\n            ┃ feugiat sagittis. Phasellus quis arcu fringilla, mattis ligula id,           │\n            ┃ vestibulum                                                                   │\n            ┃ urna. Vivamus facilisis leo a mi tincidunt condimentum. Donec eu euismod     │\n            ┃ enim.                                                                        │\n            ┃ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu ligula eget  │\n            ┃ velit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac │\n            ┃ porta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per   │\n            ┃ conubia nostra, per inceptos himenaeos.                                      │\n            ┖──────────────────────────────────────────────────────────────────────────────┘\n            ┎──────────────────────────────────────────────────────────────╮2020-08-31 14:32\n            ┃ A second entry in what I hope to be a long series.           ╘═══════════════╕\n            ┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n            ┃ Sed sit amet metus et sapien feugiat elementum. Aliquam bibendum lobortis    │\n            ┃ leo                                                                          │\n            ┃ vitae tempus. Donec eleifend nec mi non volutpat. Lorem ipsum dolor sit      │\n            ┃ amet,                                                                        │\n            ┃ consectetur adipiscing elit. Praesent ut sodales libero. Maecenas nisl       │\n            ┃ lorem,                                                                       │\n            ┃ vestibulum in tempus sit amet, fermentum ut arcu. Donec vel vestibulum       │\n            ┃ lectus,                                                                      │\n            ┃ eget pretium enim. Maecenas diam nunc, imperdiet vitae pharetra sed, pretium │\n            ┃ id                                                                           │\n            ┃ lectus. Donec eu metus et turpis tempor tristique ac non ex. In tellus arcu, │\n            ┃ egestas at efficitur et, ultrices vel est. Sed commodo et nibh non           │\n            ┃ elementum.                                                                   │\n            ┃ Mauris tempus vitae neque vel viverra. @tagtwo all by its lonesome.          │\n            ┃                                                                              │\n            ┃ Nulla mattis elementum magna, viverra pretium dui fermentum et. Cras vel     │\n            ┃ vestibulum odio. Quisque sit amet turpis et urna finibus maximus. Interdum   │\n            ┃ et                                                                           │\n            ┃ malesuada fames ac ante ipsum primis in faucibus. Fusce porttitor iaculis    │\n            ┃ sem,                                                                         │\n            ┃ non dictum ipsum varius nec. Nulla eu erat at risus gravida blandit non vel  │\n            ┃ ante. Nam egestas ipsum leo, eu ultricies ipsum tincidunt vel. Morbi a       │\n            ┃ commodo                                                                      │\n            ┃ eros.                                                                        │\n            ┃                                                                              │\n            ┃ Nullam dictum, nisl ac varius tempus, ex tortor fermentum nisl, non          │\n            ┃ tempus dolor neque a lorem. Suspendisse a faucibus ex, vel ornare tortor.    │\n            ┃ Maecenas tincidunt id felis quis semper. Pellentesque enim libero, fermentum │\n            ┃ quis metus id, rhoncus euismod magna. Nulla finibus velit eu purus bibendum  │\n            ┃ interdum. Integer id justo dui. Integer eu tellus in turpis bibendum         │\n            ┃ blandit.                                                                     │\n            ┃ Quisque auctor lacinia consectetur.                                          │\n            ┖──────────────────────────────────────────────────────────────────────────────┘\n            ┎──────────────────────────────────────────────────────────────╮2020-09-24 09:14\n            ┃ The third entry finally after weeks without writing.         ╘═══════════════╕\n            ┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n            ┃ I'm so excited about emojis. 💯 🎶 💩                                           │\n            ┃                                                                              │\n            ┃ Donec semper pellentesque iaculis. Nullam cursus et justo sit amet           │\n            ┃ venenatis.                                                                   │\n            ┃ Vivamus tempus ex dictum metus vehicula gravida. Aliquam sed sem dolor.      │\n            ┃ Nulla                                                                        │\n            ┃ eget ultrices purus. Quisque at nunc at quam pharetra consectetur vitae quis │\n            ┃ dolor. Fusce ultricies purus eu est feugiat, quis scelerisque nibh           │\n            ┃ malesuada.                                                                   │\n            ┃ Quisque egestas semper nibh in hendrerit. Nam finibus ex in mi mattis        │\n            ┃ vulputate. Sed mauris urna, consectetur in justo eu, volutpat accumsan       │\n            ┃ justo.                                                                       │\n            ┃ Phasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at  │\n            ┃ ante eget fringilla. @tagthree and also @tagone                              │\n            ┖──────────────────────────────────────────────────────────────────────────────┘\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    @skip_win\n    Scenario Outline: Export to yaml\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we create a cache directory\n        When we run \"jrnl --format yaml --file {cache_dir}\"\n        Then the cache directory should contain the files\n            \"\"\"\n            2020-08-29_entry-the-first.md\n            2020-08-31_a-second-entry-in-what-i-hope-to-be-a-long-series.md\n            2020-09-24_the-third-entry-finally-after-weeks-without-writing.md\n            \"\"\"\n\n        And the content of file \"2020-08-29_entry-the-first.md\" in the cache should be\n            \"\"\"\n            ---\n            title: Entry the first.\n            date: 2020-08-29 11:11\n            starred: False\n            tags: tagone, ipsum, tagtwo\n            body: |\n                Lorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada\n                quis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque\n                augue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu\n                consequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In\n                commodo nulla nec tellus placerat, sed ultricies metus bibendum. Duis eget\n                venenatis erat. In at dolor dui. @tagone and maybe also @tagtwo.\n\n                Curabitur accumsan nunc ac neque tristique, eleifend faucibus justo\n                ullamcorper. Suspendisse at mattis nunc. Nullam eget lacinia urna. Suspendisse\n                potenti. Ut urna est, venenatis sed ante in, ultrices congue mi. Maecenas eget\n                molestie metus. Mauris porttitor dui ornare gravida porta. Quisque sed lectus\n                hendrerit, lacinia ante eget, vulputate ante. Aliquam vitae erat non felis\n                feugiat sagittis. Phasellus quis arcu fringilla, mattis ligula id, vestibulum\n                urna. Vivamus facilisis leo a mi tincidunt condimentum. Donec eu euismod enim.\n                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu ligula eget\n                velit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac\n                porta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per\n                conubia nostra, per inceptos himenaeos.        \n            ...\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml    |\n\n    Scenario Outline: Exporting YAML to nonexistent directory leads to user-friendly error with no traceback\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --export yaml --file nonexistent_dir\"\n        Then the output should contain \"YAML export must be to a directory\"\n        And the output should not contain \"Traceback\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    @skip_win # @todo YAML exporter does not correctly export emoji on Windows\n    Scenario Outline: Add a blank line to YAML export if there isn't one already\n        # https://github.com/jrnl-org/jrnl/issues/768\n        # https://github.com/jrnl-org/jrnl/issues/881\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we create a cache directory\n        When we run \"jrnl --export yaml -o {cache_dir}\"\n        Then the cache should contain the files\n            \"\"\"\n            2020-08-29_entry-the-first.md\n            2020-08-31_a-second-entry-in-what-i-hope-to-be-a-long-series.md\n            2020-09-24_the-third-entry-finally-after-weeks-without-writing.md\n            \"\"\"\n        And the content of file \"2020-09-24_the-third-entry-finally-after-weeks-without-writing.md\" in the cache should be\n            \"\"\"\n            ---\n            title: The third entry finally after weeks without writing.\n            date: 2020-09-24 09:14\n            starred: False\n            tags: tagone, tagthree\n            body: |\n                I'm so excited about emojis. 💯 🎶 💩\n\n                Donec semper pellentesque iaculis. Nullam cursus et justo sit amet venenatis.\n                Vivamus tempus ex dictum metus vehicula gravida. Aliquam sed sem dolor. Nulla\n                eget ultrices purus. Quisque at nunc at quam pharetra consectetur vitae quis\n                dolor. Fusce ultricies purus eu est feugiat, quis scelerisque nibh malesuada.\n                Quisque egestas semper nibh in hendrerit. Nam finibus ex in mi mattis\n                vulputate. Sed mauris urna, consectetur in justo eu, volutpat accumsan justo.\n                Phasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at\n                ante eget fringilla. @tagthree and also @tagone        \n            ...\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario: Empty DayOne entry bodies should not error\n        # https://github.com/jrnl-org/jrnl/issues/780\n        Given we use the config \"bug780.yaml\"\n        When we run \"jrnl --short\"\n        Then we should get no error\n\n    Scenario Outline: --short displays the short version of entries (only the title)\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -on 2020-08-31 --short\"\n        Then the output should be \"2020-08-31 14:32 A second entry in what I hope to be a long series.\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: -s displays the short version of entries (only the title)\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -on 2020-08-31 -s\"\n        Then the output should be \"2020-08-31 14:32 A second entry in what I hope to be a long series.\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario: Markdown Support from config file\n        Given we use the config \"format_md.yaml\"\n        When we run \"jrnl -n 1\"\n        Then the output should be\n            \"\"\"\n            # 2013\n\n            ## June\n\n            ### 2013-06-10 15:40 Life is good.\n\n            But I'm better.\n            \"\"\"\n\n    Scenario: Text Formatter from config file\n        Given we use the config \"format_text.yaml\"\n        When we run \"jrnl -n 1\"\n        Then the output should be\n            \"\"\"\n            [2013-06-10 15:40] Life is good.\n            But I'm better.\n            \"\"\"\n\n    Scenario Outline: Exporting entries with Cyrillic characters to directory should not fail\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we create a cache directory\n        When we run \"jrnl 2020-11-21: Первая\"\n        When we run \"jrnl --format md --file {cache_dir} -on 2020-11-21\"\n        Then the cache should contain the files\n            \"\"\"\n            2020-11-21_первая.md\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Export date counts\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl 2020-08-31 01:01: Hi.\"\n        And we run \"jrnl --format dates\"\n        Then the output should be\n            \"\"\"\n            2020-08-29, 1\n            2020-08-31, 2\n            2020-09-24, 1\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n\n    Scenario Outline: display_format short and pretty do not crash if specified as config values\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --config-override display_format short -1\"\n        Then we should get no error\n        When we run \"jrnl --config-override display_format pretty -1\"\n        Then we should get no error\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario: Export entries in markdown format with a title longer than max file name length.\n        Given we use the config \"basic_onefile.yaml\"\n        And we create a cache directory\n        When we run \"jrnl 2022-07-31 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua Laoreet id donec ultrices tincidunt arcu Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin Ut pharetra sit amet aliquam id diam maecenas Habitasse platea dictumst quisque sagittis Aliquam purus sit amet luctus venenatis lectus magna Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat Diam vel quam elementum pulvinar etiam non Odio ut enim blandit volutpat maecenas volutpat Lacus vestibulum sed arcu non odio euismod lacinia at quis. Pretium nibh ipsum consequat nisl.\"\n        When we run \"jrnl 2022-07-31 Magna fermentum iaculis eu non diam phasellus Non pulvinar neque laoreet suspendisse interdum consectetur libero id Scelerisque felis imperdiet proin fermentum leo Eu ultrices vitae auctor eu augue ut lectus Bibendum arcu vitae elementum curabitur vitae nunc sed Tincidunt tortor aliquam nulla facilisi cras fermentum Malesuada nunc vel risus commodo viverra maecenas accumsan lacus vel Non sodales neque sodales ut Enim nulla aliquet porttitor lacus luctus accumsan Volutpat blandit aliquam etiam erat velit scelerisque in dictum non Egestas fringilla phasellus faucibus scelerisque At risus viverra adipiscing at in tellus integer feugiat scelerisque Eget velit aliquet sagittis id consectetur purus ut Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis Lacus vestibulum sed arcu non odio euismod lacinia at Elit pellentesque habitant morbi tristique Vestibulum lorem sed risus ultricies Integer eget aliquet nibh praesent tristique magna sit amet purus Quisque id diam vel quam elementum pulvinar etiam non quam Nisi scelerisque eu ultrices vitae auctor eu augue. Malesuada fames ac turpis egestas integer eget aliquet.\"\n        When we run \"jrnl --format markdown --file {cache_dir}\"\n        Then the cache directory should contain 5 files\n        And we should get no error\n\n    Scenario: Export entries in text format with a title longer than max file name length.\n        Given we use the config \"basic_onefile.yaml\"\n        And we create a cache directory\n        When we run \"jrnl 2022-07-31 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua Laoreet id donec ultrices tincidunt arcu Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin Ut pharetra sit amet aliquam id diam maecenas Habitasse platea dictumst quisque sagittis Aliquam purus sit amet luctus venenatis lectus magna Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat Diam vel quam elementum pulvinar etiam non Odio ut enim blandit volutpat maecenas volutpat Lacus vestibulum sed arcu non odio euismod lacinia at quis. Pretium nibh ipsum consequat nisl.\"\n        When we run \"jrnl 2022-07-31 Magna fermentum iaculis eu non diam phasellus Non pulvinar neque laoreet suspendisse interdum consectetur libero id Scelerisque felis imperdiet proin fermentum leo Eu ultrices vitae auctor eu augue ut lectus Bibendum arcu vitae elementum curabitur vitae nunc sed Tincidunt tortor aliquam nulla facilisi cras fermentum Malesuada nunc vel risus commodo viverra maecenas accumsan lacus vel Non sodales neque sodales ut Enim nulla aliquet porttitor lacus luctus accumsan Volutpat blandit aliquam etiam erat velit scelerisque in dictum non Egestas fringilla phasellus faucibus scelerisque At risus viverra adipiscing at in tellus integer feugiat scelerisque Eget velit aliquet sagittis id consectetur purus ut Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis Lacus vestibulum sed arcu non odio euismod lacinia at Elit pellentesque habitant morbi tristique Vestibulum lorem sed risus ultricies Integer eget aliquet nibh praesent tristique magna sit amet purus Quisque id diam vel quam elementum pulvinar etiam non quam Nisi scelerisque eu ultrices vitae auctor eu augue. Malesuada fames ac turpis egestas integer eget aliquet.\"\n        When we run \"jrnl --format text --file {cache_dir}\"\n        Then the cache directory should contain 5 files\n        And we should get no error\n\n    Scenario: Export journal list to multiple formats.\n        Given we use the config \"basic_onefile.yaml\"\n        When we run \"jrnl --list\"\n        Then the output should match\n            \"\"\"\n            Journals defined in config \\(.+basic_onefile\\.yaml\\)\n             \\* default -> features/journals/basic_onefile\\.journal\n            \"\"\"\n        When we run \"jrnl --list --format json\"\n        Then the output should match\n            \"\"\"\n            {\"config_path\": \".+basic_onefile\\.yaml\", \"journals\": {\"default\": \"features/journals/basic_onefile\\.journal\"}}\n            \"\"\"\n        When we run \"jrnl --list --format yaml\"\n        Then the output should match\n            \"\"\"\n            config_path: .+basic_onefile\\.yaml\n            journals:\n              default: features/journals/basic_onefile\\.journal\n            \"\"\"\n\n    Scenario: Export journal list to formats with no default journal\n        Given we use the config \"no_default_journal.yaml\"\n        When we run \"jrnl --list\"\n        Then the output should match\n            \"\"\"\n            Journals defined in config \\(.+no_default_journal\\.yaml\\)\n             \\* simple -> features/journals/simple\\.journal\n             \\* work   -> features/journals/work\\.journal\n            \"\"\"\n        When we run \"jrnl --list --format json\"\n        Then the output should match\n            \"\"\"\n            {\"config_path\": \".+no_default_journal\\.yaml\", \"journals\": {\"simple\": \"features/journals/simple\\.journal\", \"work\": \"features/journals/work\\.journal\"}}\n            \"\"\"\n        When we run \"jrnl --list --format yaml\"\n        Then the output should match\n            \"\"\"\n            config_path: .+no_default_journal\\.yaml\n            journals:\n              simple: features/journals/simple\\.journal\n              work: features/journals/work\\.journal\n            \"\"\"\n"
  },
  {
    "path": "tests/bdd/features/import.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Importing data\n\n    Scenario Outline: --import allows new entry from stdin\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --import\" and pipe \"[2020-07-05 15:00] Observe and import.\"\n        When we run \"jrnl -9 --short\"\n        Then the output should contain \"Observe and import\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario Outline: --import allows new large entry from stdin\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --import\" and pipe\n            \"\"\"\n            [2020-07-05 15:00] Observe and import.\n            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada quis\n            est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque augue\n            et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu consequat.\n            Aenean ante ex, elementum ut interdum et, mattis eget lacus. In commodo nulla nec\n            tellus placerat, sed ultricies metus bibendum. Duis eget venenatis erat. In at\n            dolor dui end of entry.\n            \"\"\"\n        When we run \"jrnl -on 2020-07-05\"\n        Then the output should contain \"2020-07-05 15:00 Observe and import.\"\n        And the output should contain \"Lorem ipsum\"\n        And the output should contain \"end of entry.\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario Outline: --import allows multiple new entries from stdin\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --import\" and pipe\n            \"\"\"\n            [2020-07-05 15:00] Observe and import.\n            Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\n            [2020-07-05 15:01] Twice as nice.\n            Sed dignissim sed nisl eu consequat.\n            \"\"\"\n        When we run \"jrnl -on 2020-07-05\"\n        Then the output should contain \"2020-07-05 15:00 Observe and import.\"\n        And the output should contain \"Lorem ipsum\"\n        And the output should contain \"2020-07-05 15:01 Twice as nice.\"\n        And the output should contain \"Sed dignissim\"\n\n        Examples: Configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        # | basic_dayone.yaml    | @todo\n\n    Scenario: --import allows import new entries from file\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl -99\"\n        Then the output should contain \"My first entry.\"\n        And the output should contain \"Life is good.\"\n        But the output should not contain \"I have an @idea\"\n        And the output should not contain \"I met with\"\n        When we run \"jrnl --import --file features/journals/tags.journal\"\n        And we run \"jrnl -99\"\n        Then the output should contain \"My first entry.\"\n        And the output should contain \"Life is good.\"\n        And the output should contain \"PROFIT!\"\n\n    Scenario: --import prioritizes --file over pipe data if both are given\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl -99\"\n        Then the output should contain \"My first entry.\"\n        And the output should contain \"Life is good.\"\n        But the output should not contain \"I have an @idea\"\n        And the output should not contain \"I met with\"\n        When we run \"jrnl --import --file features/journals/tags.journal\" and pipe\n            \"\"\"\n            [2020-07-05 15:00] I should not exist!\n            \"\"\"\n        And we run \"jrnl -99\"\n        Then the output should contain \"My first entry.\"\n        And the output should contain \"PROFIT!\"\n        But the output should not contain \"I should not exist!\"\n\n"
  },
  {
    "path": "tests/bdd/features/install.feature",
    "content": "Feature: Installing jrnl\n\n    Scenario: Install jrnl with default options\n        Given we use no config\n        When we run \"jrnl hello world\" and enter\n            \"\"\"\t\n            \\n\n            \\n\n            \\n\n            \"\"\"\n        Then the output should contain \"jrnl configuration created at\"\n        And the output should contain \"For advanced features, read the docs at https://jrnl.sh\"\n        And the output should contain \"Journal 'default' created\"\n        And the default journal \"journal.txt\" should be in the \".\" directory\n        And the config should contain \"encrypt: false\"\n        And the version in the config file should be up-to-date\n\n    Scenario: Install jrnl with custom relative default journal path\n        Given we use no config\n        When we run \"jrnl hello world\" and enter\n            \"\"\"\n            default/custom.txt\n            n\n            \\n\n            \"\"\"\n        Then the output should contain \"Journal 'default' created\"\n        And the default journal \"custom.txt\" should be in the \"default\" directory\n        And the config should contain \"encrypt: false\"\n        And the version in the config file should be up-to-date\n\n    Scenario: Install jrnl with custom expanded default journal path\n        Given we use no config\n        And the home directory is called \"home\"\n        When we run \"jrnl hello world\" and enter\n            \"\"\"\n            ~/custom.txt\n            n\n            \\n\n            \"\"\"\n        Then the output should contain \"Journal 'default' created\"\n        And the default journal \"custom.txt\" should be in the \"home\" directory\n        And the config should contain \"encrypt: false\"\n        And the version in the config file should be up-to-date\n\n    Scenario: Install jrnl with encrypted default journal\n        Given we use no config\n        When we run \"jrnl hello world\" and enter\n            \"\"\"\n            encrypted.txt\n            y\n            \\n\n            \"\"\"\n        Then the output should contain \"Journal will be encrypted\"\n        And the default journal \"encrypted.txt\" should be in the \".\" directory\n        And the config should contain \"encrypt: true\"\n        And the version in the config file should be up-to-date\n        When we run \"jrnl\"\n        Then we should be prompted for a password\n\n   Scenario: Install jrnl with colors by default\n        Given we use no config\n        When we run \"jrnl hello world\" and enter\n            \"\"\"\n            \\n\n            \\n\n            \\n\n            \"\"\"\n        Then the output should contain \"Journal 'default' created\"\n        And the config should contain\n            \"\"\"\n            colors:\n                body: none\n                date: black\n                tags: yellow\n                title: cyan\n            \"\"\"\n\n   Scenario: Install jrnl without colors\n        Given we use no config\n        When we run \"jrnl hello world\" and enter\n            \"\"\"\n            \\n\n            \\n\n            N\n            \"\"\"\n        Then the output should contain \"Journal 'default' created\"\n        And the config should contain\n            \"\"\"\n            colors:\n                body: none\n                date: none\n                tags: none\n                title: none\n            \"\"\"\n\n    Scenario: Install jrnl with encrypted default journal with no entries\n        Given we use no config\n        When we run \"jrnl -1\" and enter\n            \"\"\"\n            encrypted.txt\n            y\n            n\n            test\n            test\n            n\n            \"\"\"\n        Then the error output should contain \"Journal will be encrypted\"\n        And the default journal \"encrypted.txt\" should be in the \".\" directory\n        And the config should contain \"encrypt: true\"\n        And the version in the config file should be up-to-date\n        When we run \"jrnl -1\" and enter\n            \"\"\"\n            test\n            \"\"\"\n        Then we should be prompted for a password\n        And the error output should contain \"no entries found\"\n        And the error output should not contain \"Wrong password, try again\"\n"
  },
  {
    "path": "tests/bdd/features/multiple_journals.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Multiple journals\n\n    Scenario: Loading a config with two journals\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n        When we run \"jrnl work -99 --short\"\n        Then the output should be empty\n\n    Scenario: Write to default config by default\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl this goes to default\"\n        When we run \"jrnl -99 --short\"\n        Then the output should contain\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n        Then the output should contain\n            \"\"\"\n            this goes to default\n            \"\"\"\n        When we run \"jrnl work -99 --short\"\n        Then the output should be empty\n\n    Scenario: Write to specified journal\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl work a long day in the office\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n        When we run \"jrnl work -99 --short\"\n        Then the output should contain \"a long day in the office\"\n\n    Scenario: Tell user which journal was used\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl work a long day in the office\"\n        Then the output should contain \"Entry added to work journal\"\n\n    Scenario: Write to specified journal with a timestamp\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl work 23 july 2012: a long day in the office\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n        When we run \"jrnl work -99 --short\"\n        Then the output should be\n            \"\"\"\n            2012-07-23 09:00 a long day in the office\n            \"\"\"\n\n    Scenario: Write to specified journal without a timestamp but with colon\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl work : a long day in the office\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n        When we run \"jrnl work -99 --short\"\n        Then the output should be contain\n            \"\"\"\n            a long day in the office\n            \"\"\"\n\n    Scenario: Write to specified journal without a timestamp but with colon\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl work: a long day in the office\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n        When we run \"jrnl work -99 --short\"\n        Then the output should contain\n            \"\"\"\n            a long day in the office\n            \"\"\"\n\n   Scenario: Create new journals as required\n        Given we use the config \"multiple.yaml\"\n        Then journal \"ideas\" should not exist\n        When we run \"jrnl ideas 23 july 2012: sell my junk on ebay and make lots of money\"\n        When we run \"jrnl ideas -99 --short\"\n        Then the output should be\n            \"\"\"\n            2012-07-23 09:00 sell my junk on ebay and make lots of money\n            \"\"\"\n\n   Scenario: Don't crash if no default journal is specified\n        Given we use the config \"no_default_journal.yaml\"\n        When we run \"jrnl a long day in the office\"\n        Then the output should contain \"No 'default' journal configured\"\n\n   Scenario: Don't crash if no file exists for a configured encrypted journal\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl new_encrypted Adding first entry\" and enter\n            \"\"\"\n            these three eyes\n            these three eyes\n            n\n            \"\"\"\n        Then the output should contain \"Journal 'new_encrypted' created at\"\n\n   Scenario: Read and write to journal with emoji name\n        Given we use the config \"multiple.yaml\"\n        When we run \"jrnl ✨ Adding entry to sparkly journal\"\n        When we run \"jrnl ✨ -1\"\n        Then the output should contain \"Adding entry to sparkly journal\"\n"
  },
  {
    "path": "tests/bdd/features/override.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Implementing Runtime Overrides for Select Configuration Keys\n\n        Scenario: Override configured editor with built-in input === editor:''\n        Given we use the config \"basic_encrypted.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --config-override editor ''\" and type\n            \"\"\"\n            This is a journal entry\n            \"\"\"\n        Then the stdin prompt should have been called\n        And the editor should not have been called\n        When we run \"jrnl -1\"\n        Then the output should contain \"This is a journal entry\"\n\n\n        Scenario: Postconfig commands with overrides\n        Given we use the config \"basic_encrypted.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --decrypt --config-override highlight false --config-override editor nano\"\n        Then the config in memory should contain \"highlight: false\"\n        Then the editor should not have been called\n\n\n        Scenario: Override configured linewrap with a value of 23\n        Given we use the config \"simple.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl  -2 --config-override linewrap 23 --format fancy\"\n        Then the standard output should contain\n            \"\"\"\n            ┎─────╮2013-06-09 15:39\n            ┃ My  ╘═══════════════╕\n            \"\"\"\n        And the standard output should contain\n            \"\"\"\n            ┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n            ┃ Everything is       │\n            ┃ alright             │\n            ┖─────────────────────┘\n            ┎─────╮2013-06-10 15:40\n            ┃ Lif ╘═══════════════╕\n            \"\"\"\n        And the standard output should contain\n            \"\"\"\n            ┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤\n            ┃ But I'm better.     │\n            ┖─────────────────────┘\n            \"\"\"\n\n\n        Scenario: Override color selections with runtime overrides\n        Given we use the config \"basic_encrypted.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -1 --config-override colors.body blue\"\n        Then the config in memory should contain \"colors.body: blue\"\n\n        Scenario: Override color selections with --co alias\n        Given we use the config \"basic_encrypted.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -1 --co colors.body blue\"\n        Then the config in memory should contain \"colors.body: blue\"\n\n        Scenario: Apply multiple config overrides\n        Given we use the config \"basic_encrypted.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -1 --config-override colors.body green --config-override editor 'nano'\"\n        Then the config in memory should contain\n            \"\"\"\n            editor: nano\n            colors:\n                title: none\n                body: green\n                tags: none\n                date: none\n            \"\"\"\n\n\n        Scenario: Override default journal\n        Given we use the config \"basic_dayone.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --config-override journals.default features/journals/simple.journal 20 Mar 2000: The rain in Spain comes from clouds\"\n        Then we should get no error\n        When we run \"jrnl -3 --config-override journals.default features/journals/simple.journal\"\n        Then the output should be\n            \"\"\"\n            2000-03-20 09:00 The rain in Spain comes from clouds\n\n            2013-06-09 15:39 My first entry.\n            | Everything is alright\n\n            2013-06-10 15:40 Life is good.\n            | But I'm better.\n            \"\"\"\n\n\n        Scenario: Make an entry into an overridden journal\n        Given we use the config \"basic_dayone.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --config-override journals.temp features/journals/simple.journal temp Sep 06 1969: @say Ni\"\n        Then we should get no error\n        And the output should contain \"Entry added\"\n        When we run \"jrnl --config-override journals.temp features/journals/simple.journal temp -3\"\n        Then the output should be\n            \"\"\"\n            1969-09-06 09:00 @say Ni\n\n            2013-06-09 15:39 My first entry.\n            | Everything is alright\n\n            2013-06-10 15:40 Life is good.\n            | But I'm better.\n            \"\"\"\n"
  },
  {
    "path": "tests/bdd/features/password.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Using the installed keyring\n\n    Scenario: Storing a password in keyring\n        Given we use the config \"multiple.yaml\"\n        And we have a keyring\n        When we run \"jrnl simple --encrypt\" and enter\n            \"\"\"\n            sabertooth\n            sabertooth\n            Y\n            \"\"\"\n        Then the config for journal \"simple\" should contain \"encrypt: true\"\n        When we run \"jrnl simple -n 1\"\n        Then the output should contain \"2013-06-10 15:40 Life is good\"\n\n\n    Scenario: Encrypt journal with no keyring backend and do not store in keyring\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl test entry\"\n        And we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            password\n            password\n            n\n            \"\"\"\n        Then we should get no error\n        And the output should not contain \"Failed to retrieve keyring\"\n\n\n    Scenario: Encrypt journal with no keyring backend and do store in keyring\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl test entry\"\n        And we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            password\n            password\n            y\n            \"\"\"\n        Then we should get no error\n        And the output should not contain \"Failed to retrieve keyring\"\n        # @todo add step to check contents of keyring\n\n\n    @todo\n    Scenario: Open an encrypted journal with wrong password in keyring\n    # This should ask the user for the password after the keyring fails\n\n\n    @todo\n    Scenario: Decrypt journal with password in keyring\n\n\n    @todo\n    Scenario: Decrypt journal without a keyring\n\n\n    Scenario: Encrypt journal when keyring exists but fails\n        Given we use the config \"simple.yaml\"\n        And we have a failed keyring\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            this password will not be saved in keyring\n            this password will not be saved in keyring\n            y\n            \"\"\"\n        Then the output should contain \"Failed to retrieve keyring\"\n        And we should get no error\n        And we should be prompted for a password\n        And the config for journal \"default\" should contain \"encrypt: true\"\n\n\n    Scenario: Decrypt journal when keyring exists but fails\n        Given we use the config \"encrypted.yaml\"\n        And we have a failed keyring\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl --decrypt\"\n        Then the error output should contain \"Failed to retrieve keyring\"\n        And we should get no error\n        And we should be prompted for a password\n        And the output should contain \"Journal decrypted\"\n        And the config for journal \"default\" should contain \"encrypt: false\"\n        When we run \"jrnl --short\"\n        Then we should not be prompted for a password\n        And the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n\n\n    Scenario: Open encrypted journal when keyring exists but fails\n    # This should ask the user for the password after the keyring fails\n        Given we use the config \"encrypted.yaml\"\n        And we have a failed keyring\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl -n 1\"\n        Then we should get no error\n        And we should be prompted for a password\n        And the output should contain \"Failed to retrieve keyring\"\n        And the output should contain \"2013-06-10 15:40 Life is good\"\n\n\n    Scenario: Mistyping your password\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            swordfish\n            sordfish\n            \"\"\"\n        Then we should be prompted for a password\n        And the output should contain \"Passwords did not match\"\n        And the config for journal \"default\" should not contain \"encrypt: true\"\n        When we run \"jrnl --short\"\n        Then the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            2013-06-10 15:40 Life is good.\n            \"\"\"\n\n\n    Scenario: Mistyping your password, then getting it right\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl --encrypt\" and enter\n            \"\"\"\n            swordfish\n            sordfish\n            swordfish\n            swordfish\n            n\n            \"\"\"\n        Then we should be prompted for a password\n        And the output should contain \"Passwords did not match\"\n        And the output should contain \"Journal encrypted\"\n        And the config for journal \"default\" should contain \"encrypt: true\"\n        When we run \"jrnl -1\" and enter \"swordfish\"\n        Then we should be prompted for a password\n        And the output should contain \"2013-06-10 15:40 Life is good\"\n\n"
  },
  {
    "path": "tests/bdd/features/search.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Searching in a journal\n\n    Scenario Outline: Displaying entries using -on today should display entries created today\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl today: Adding an entry right now.\"\n        Then we should get no error\n        When we run \"jrnl -on today\"\n        Then the output should contain \"Adding an entry right now.\"\n        But the output should not contain \"Everything is alright\"\n        And the output should not contain \"Life is good\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n\n    Scenario Outline: Displaying entries using -from day should display correct entries\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl yesterday: This thing happened yesterday\"\n        Then we should get no error\n        When we run \"jrnl today at 11:59pm: Adding an entry right now.\"\n        Then we should get no error\n        When we run \"jrnl tomorrow: A future entry.\"\n        Then we should get no error\n        When we run \"jrnl -from today\"\n        Then the output should contain \"2 entries found\"\n        And the output should contain \"Adding an entry right now.\"\n        And the output should contain \"A future entry.\"\n        And the output should not contain \"This thing happened yesterday\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n\n    Scenario Outline: Displaying entries using -from and -to day should display correct entries\n        Given we use the config \"<config_file>\"\n        And now is \"2022-03-10 02:32:00 PM\"\n        When we run \"jrnl yesterday: This thing happened yesterday\"\n        Then we should get no error\n        When we run \"jrnl today at 11:59pm: Adding an entry right now.\"\n        Then we should get no error\n        When we run \"jrnl tomorrow: A future entry.\"\n        Then we should get no error\n        When we run \"jrnl -from yesterday -to today\"\n        Then the output should contain \"2 entries found\"\n        And the output should contain \"This thing happened yesterday\"\n        And the output should contain \"Adding an entry right now.\"\n        And the output should not contain \"A future entry.\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n\n    Scenario Outline: Searching for a string\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -contains first --short\"\n        Then we should get no error\n        And the output should contain \"1 entry found\"\n        And the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            \"\"\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Searching for an unknown string\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -contains slkdfsdkfjsd\"\n        Then we should get no error\n        And the output should contain \"no entries found\"\n        And the output should not contain \"slkdfsdkfjsd\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Multiple -contains returns entries that match any\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -contains emojis -contains lorem --short\"\n        Then we should get no error\n        And the output should contain \"3 entries found\"\n        And the output should be\n            \"\"\"\n            2020-08-29 11:11 Entry the first.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Multiple -contains with -and returns only entries that match all\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -contains emojis -contains nulla -and --short\"\n        Then we should get no error\n        And the output should contain \"1 entry found\"\n        And the output should be\n            \"\"\"\n            2020-09-24 09:14 The third entry finally after weeks without writing.\n            \"\"\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Searching for a string within tag results\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl @tagone -contains maybe\"\n        Then we should get no error\n        And the output should contain \"maybe\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Searching for a string within AND tag results\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -and @tagone @tagtwo -contains maybe\"\n        Then we should get no error\n        And the output should contain \"1 entry found\"\n        And the output should contain \"maybe\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Searching for a string within NOT tag results\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -not @tagone -contains lonesome\"\n        Then we should get no error\n        And the output should contain \"1 entry found\"\n        And the output should contain \"lonesome\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n\n    Scenario Outline: Searching for unstarred entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -not -starred\"\n        Then we should get no error\n        And the output should contain \"2 entries found\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Searching for tagged entries\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -tagged\"\n        Then we should get no error\n        And the output should contain \"3 entries found\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Searching for untagged entries\n        Given we use the config \"empty_folder.yaml\"\n        When we run \"jrnl Tagged entry. This one has a @tag.\"\n        Then we should get no error\n        When we run \"jrnl Untagged entry. This one has no tag.\"\n        Then we should get no error\n        When we run \"jrnl -not -tagged\"\n        Then we should get no error\n        And the output should contain \"1 entry found\"\n        And the output should contain \"This one has no tag\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Searching for dates\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -on 2020-08-31 --short\"\n        Then the output should be \"2020-08-31 14:32 A second entry in what I hope to be a long series.\"\n        When we run \"jrnl -on 'august 31 2020' --short\"\n        Then the output should be \"2020-08-31 14:32 A second entry in what I hope to be a long series.\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario: Out of order entries to a Folder journal should be listed in date order\n        Given we use the config \"empty_folder.yaml\"\n        When we run \"jrnl 3/7/2014 4:37pm: Second entry of journal.\"\n        Then we should get no error\n        When we run \"jrnl 23 July 2013: Testing folder journal.\"\n        Then we should get no error\n        When we run \"jrnl -2\"\n        Then the output should be\n            \"\"\"\n            2013-07-23 09:00 Testing folder journal.\n\n            2014-03-07 16:37 Second entry of journal.\n            \"\"\"\n\n    Scenario Outline: Searching for all tags should show counts of each tag\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --tags\"\n        Then we should get no error\n        And the output should be\n            \"\"\"\n            @tagtwo              : 2\n            @tagone              : 2\n            @tagthree            : 1\n            @ipsum               : 1\n            \"\"\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Filtering journals should also filter tags\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -from 'september 2020' --tags\"\n        Then we should get no error\n        And the output should be\n            \"\"\"\n            @tagthree            : 1\n            @tagone              : 1\n            \"\"\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline:  Excluding a tag should filter out all entries with that tag\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --tags -not @tagtwo\"\n        Then the output should be\n            \"\"\"\n            @tagthree            : 1\n            @tagone              : 1\n            \"\"\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline:  Excluding multiple tags should filter out all entries with those tags\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl --tags -not @tagone -not @tagthree\"\n        Then the output should be\n            \"\"\"\n            @tagtwo              : 1\n            \"\"\"\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline:  Using -not should exclude all entries with that tag\n        # https://github.com/jrnl-org/jrnl/issues/1472\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -not @tagtwo\"\n        Then the output should not contain \"@tagtwo\"\n        And the editor should not have been called\n\n        Examples: configs\n        | config_file   |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario: DayOne tag searching should work with tags containing a mixture of upper and lower case.\n        # https://github.com/jrnl-org/jrnl/issues/354\n        Given we use the config \"dayone.yaml\"\n        When we run \"jrnl @plAy\"\n        Then the output should contain \"2013-05-17 11:39 This entry has tags!\"\n\n    Scenario: Loading a sample journal\n        Given we use the config \"simple.yaml\"\n        When we run \"jrnl -2\"\n        Then we should get no error\n        And the output should be\n            \"\"\"\n            2013-06-09 15:39 My first entry.\n            | Everything is alright\n\n            2013-06-10 15:40 Life is good.\n            | But I'm better.\n            \"\"\"\n\n    Scenario Outline: Searching by month\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -month 9 --short\"\n        Then the output should be \"2020-09-24 09:14 The third entry finally after weeks without writing.\"\n        When we run \"jrnl -month Sept --short\"\n        Then the output should be \"2020-09-24 09:14 The third entry finally after weeks without writing.\"\n        When we run \"jrnl -month September --short\"\n        Then the output should be \"2020-09-24 09:14 The third entry finally after weeks without writing.\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Searching by day\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -day 31 --short\"\n        Then the output should be \"2020-08-31 14:32 A second entry in what I hope to be a long series.\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Searching by year\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl 2019-01-01 01:01: I like this year.\"\n        And we run \"jrnl -year 2019 --short\"\n        Then the output should be \"2019-01-01 01:01 I like this year.\"\n        When we run \"jrnl -year 19 --short\"\n        Then the output should be \"2019-01-01 01:01 I like this year.\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Combining month, day, and year search terms\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -month 08 -day 29 --short\"\n        Then the output should be \"2020-08-29 11:11 Entry the first.\"\n        When we run \"jrnl -day 29 -year 2020 --short\"\n        Then the output should be \"2020-08-29 11:11 Entry the first.\"\n        When we run \"jrnl -month 09 -year 2020 --short\"\n        Then the output should be \"2020-09-24 09:14 The third entry finally after weeks without writing.\"\n        When we run \"jrnl -month 08 -day 29 -year 2020 --short\"\n        Then the output should be \"2020-08-29 11:11 Entry the first.\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Searching today in history\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And now is \"2020-08-31 02:32:00 PM\"\n        When we run \"jrnl 2019-08-31 01:01: Hi, from last year.\"\n        And we run \"jrnl -today-in-history --short\"\n        Then the output should contain \"2 entries found\"\n        And the output should be\n            \"\"\"\n            2019-08-31 01:01 Hi, from last year.\n            2020-08-31 14:32 A second entry in what I hope to be a long series.\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario: Loading a DayOne Journal\n        Given we use the config \"dayone.yaml\"\n        When we run \"jrnl -from 'feb 2013'\"\n        Then we should get no error\n        And the output should contain \"3 entries found\"\n        And the output should be\n            \"\"\"\n            2013-05-17 11:39 This entry has tags!\n\n            2013-06-17 20:38 This entry has a location.\n\n            2013-07-17 11:38 This entry is starred!\n            \"\"\"\n\n    Scenario Outline: Searching the most recent entry should not show found count\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -1\"\n        Then the error output should not contain \"1 entry found\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Searching for more entries than are in the journal should show found count\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl -4\"\n        Then the error output should contain \"3 entries found\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n"
  },
  {
    "path": "tests/bdd/features/star.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Starring entries\n\n    Scenario Outline: Starring an entry will mark it in the journal file\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl 20 july 2013 *: Best day of my life!\"\n        Then we should get no error\n        When we run \"jrnl -on 2013-07-20 -starred\"\n        Then the output should contain \"2013-07-20 09:00 Best day of my life!\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n\n    Scenario Outline: Filtering by starred entries will show only starred entries\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl -starred\"\n        Then the output should be empty\n        When we run \"jrnl 20 july 2013 *: Best day of my life!\"\n        When we run \"jrnl -starred\"\n        Then the output should be \"2013-07-20 09:00 Best day of my life!\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone_empty.yaml |\n\n    Scenario: Starring an entry will mark it in an encrypted journal\n        Given we use the config \"encrypted.yaml\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl 20 july 2013 *: Best day of my life!\"\n        Then we should get no error\n        When we run \"jrnl -on 2013-07-20 -starred\" and enter \"bad doggie no biscuit\"\n        Then the output should contain \"2013-07-20 09:00 Best day of my life!\"\n"
  },
  {
    "path": "tests/bdd/features/tag.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Tagging\n# See search.feature for tag-related searches\n# And format.feature for tag-related output\n\n    Scenario Outline: Tags should allow certain special characters such as /, +, #\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl 2020-09-26: This is an entry about @os/2 and @c++ and @c#\"\n        When we run \"jrnl --tags -on 2020-09-26\"\n        Then we should get no error\n        And the output should be\n            \"\"\"\n            @os/2                : 1\n            @c++                 : 1\n            @c#                  : 1\n            \"\"\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline: Emails addresses should not be parsed as tags\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl 2020-09-26: The email address test@example.com does not seem to work for me\"\n        When we run \"jrnl 2020-09-26: The email address test@example.org also does not work for me\"\n        When we run \"jrnl 2020-09-26: I tried test@example.org and test@example.edu too\"\n        When we run \"jrnl --tags -on 2020-09-26\"\n        Then we should get no error\n        And the output should be \"[No tags found in journal.]\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n\n    Scenario Outline:  Entry can start and end with tags\n        Given we use the config \"<config_file>\"\n        When we run \"jrnl 2020-09-26: @foo came over, we went to a @bar\"\n        When we run \"jrnl --tags -on 2020-09-26\"\n        Then the output should be\n            \"\"\"\n            @foo                 : 1\n            @bar                 : 1\n            \"\"\"\n\n        Examples: configs\n        | config_file        |\n        | basic_onefile.yaml |\n        | basic_folder.yaml  |\n        | basic_dayone.yaml  |\n"
  },
  {
    "path": "tests/bdd/features/template.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Using templates\n    Scenario Outline: Template contents should be used in new entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we append to the editor if opened\n            \"\"\"\n            This is an addition to a templated entry\n            \"\"\"\n        When we run \"jrnl --config-override template features/templates/basic.template\"\n        And we run \"jrnl -1\"\n        Then the output should contain \"This text is in the basic template\"\n        Then the output should contain \"This is an addition to a templated entry\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Templated entry should not be saved if template is unchanged\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --config-override template features/templates/basic.template\"\n        Then the output should contain \"No entry to save, because the template was not changed\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: --template nonexistent_file should throw an error\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --template this_template_does_not_exist.template\"\n        Then we should get an error\n        Then the error output should contain \"Unable to find a template file\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: --template local_filepath should be used in new entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --template features/templates/basic.template\"\n        Then the output should contain \"No entry to save, because the template was not changed\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: --template file_in_XDG_templates_dir should be used in new entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we copy the template \"basic.template\" to the default templates folder\n        When we run \"jrnl --template basic.template\"\n        Then the output should contain \"No entry to save, because the template was not changed\"\n\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n        | basic_dayone.yaml    |\n"
  },
  {
    "path": "tests/bdd/features/upgrade.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Upgrading Journals from 1.x.x to 2.x.x\n\n    Scenario: Upgrade and parse journals with square brackets\n        Given we use the config \"upgrade_from_195.json\"\n        When we run \"jrnl -9\" and enter \"Y\"\n        When we run \"jrnl -99 --short\"\n        Then the output should be\n            \"\"\"\n            2010-06-10 15:00 A life without chocolate is like a bad analogy.\n            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\n            \"\"\"\n        And the output should contain\n            \"\"\"\n            2010-06-10 15:00 A life without chocolate is like a bad analogy.\n            \"\"\"\n        And the output should contain\n            \"\"\"\n            2013-06-10 15:40 He said \"[this] is the best time to be alive\".\n            \"\"\"\n\n    Scenario: Upgrading a journal encrypted with jrnl 1.x\n        Given we use the config \"encrypted_old.json\"\n        When we run \"jrnl -n 1\" and enter\n            \"\"\"\n            Y\n            bad doggie no biscuit\n            bad doggie no biscuit\n            \"\"\"\n        Then we should be prompted for a password\n        And the output should contain \"2013-06-10 15:40 Life is good\"\n\n    Scenario: Upgrading a config without colors to colors\n        Given we use the config \"no_colors.yaml\"\n        When we run \"jrnl -n 1\"\n        Then the config should contain\n            \"\"\"\n            colors:\n                date: none\n                title: none\n                body: none\n                tags: none\n            \"\"\"\n\n    Scenario: Upgrade and parse journals with little endian date format\n        Given we use the config \"upgrade_from_195_little_endian_dates.json\"\n        When we run \"jrnl -9 --short\" and enter \"Y\"\n        Then the output should contain\n            \"\"\"\n            10.06.2010 15:00 A life without chocolate is like a bad analogy.\n            10.06.2013 15:40 He said \"[this] is the best time to be alive\".\n            \"\"\"\n\n    Scenario: Upgrade with missing journal\n        Given we use the config \"upgrade_from_195_with_missing_journal.json\"\n        When we run \"jrnl --list\" and enter \"Y\"\n        Then the output should contain \"features/journals/missing.journal does not exist\"\n        And we should get no error\n\n    Scenario: Upgrade with missing encrypted journal\n        Given we use the config \"upgrade_from_195_with_missing_encrypted_journal.json\"\n        When we run \"jrnl --list\" and enter\n            \"\"\"\n            Y\n            bad doggie no biscuit\n            \"\"\"\n        Then the output should contain \"features/journals/missing.journal does not exist\"\n        And the output should contain \"We're all done\"\n        And we should get no error\n"
  },
  {
    "path": "tests/bdd/features/write.feature",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nFeature: Writing new entries.\n\n    Scenario Outline: Multiline entry with punctuation should keep title punctuation\n        Given we use the config \"<config_file>\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl This is. the title\\\\n This is the second line\"\n        And we run \"jrnl -n 1\"\n        Then the output should contain \"This is. the title\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n        | encrypted.yaml    |\n\n    Scenario Outline: Single line entry with period should be split at period\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl This is. the title\"\n        And we run \"jrnl -1\"\n        Then the output should contain \"| the title\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: CJK entry should be split at fullwidth period without following space.\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl 七転び。八起き\"\n        And we run \"jrnl -1\"\n        Then the output should contain \"| 八起き\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Writing an entry from command line should store the entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa.\"\n        Then we should get no error\n        When we run \"jrnl -n 1\"\n        Then the output should contain \"2013-07-23 09:00 A cold and stormy day.\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n        | encrypted.yaml    |\n\n    Scenario Outline: Writing a partial entry from command line with edit flag should go to the editor\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl this is a partial --edit\"\n        Then we should get no error\n        Then the editor should have been called\n        And the editor file content should be\n            \"\"\"\n            this is a partial\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_dayone.yaml    |\n        | basic_folder.yaml    |\n\n    Scenario Outline: Clearing the editor's contents should yield \"No text received\" message\n        Given we use the config \"<config_file>\"\n        And we write nothing to the editor if opened\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --edit\"\n        Then the error output should contain \"No text received from editor. Were you trying to delete all the entries?\"\n        And the editor should have been called\n\n        Examples: configs\n        | config_file          |\n        | editor.yaml          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_dayone.yaml    |\n        | basic_folder.yaml    |\n\n    Scenario Outline: Writing an empty entry from the command line should yield \"No entry to save\" message\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl\" and enter \"\\x04\"\n        Then the error output should contain \"No entry to save, because no text was received\"\n        When we run \"jrnl\" and enter \" \\t \\n \\x04\"\n        Then the error output should contain \"No entry to save, because no text was received\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Writing an empty entry from the command line with no editor should yield nothing\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl --config-override editor ''\" and enter \"\"\n        Then the stdin prompt should have been called\n        And the output should be empty\n        And the error output should contain \"To finish writing, press\"\n        And the editor should not have been called\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: Writing an entry does not print the entire journal\n        # https://github.com/jrnl-org/jrnl/issues/87\n        Given we use the config \"<config_file>\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa.\"\n        Then we should get no error\n        When we run \"jrnl -n 1\"\n        Then the output should not contain \"Life is good\"\n\n        Examples: configs\n        | config_file              |\n        | editor.yaml              |\n        | editor_empty_folder.yaml |\n        | dayone.yaml              |\n        | encrypted.yaml           |\n\n    Scenario Outline: Embedded period stays in title\n        Given we use the config \"<config_file>\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl 04-24-2014: Created a new website - empty.com. Hope to get a lot of traffic.\"\n        Then we should get no error\n        When we run \"jrnl -1\"\n        Then the output should be\n            \"\"\"\n            2014-04-24 09:00 Created a new website - empty.com.\n            | Hope to get a lot of traffic.\n            \"\"\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n        | encrypted.yaml    |\n\n    Scenario Outline: Write and read emoji support\n        Given we use the config \"<config_file>\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl 23 july 2013: 🌞 sunny day. Saw an 🐘\"\n        Then we should get no error\n        When we run \"jrnl -n 1\"\n        Then the output should contain \"🌞\"\n        And the output should contain \"🐘\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | dayone.yaml       |\n        | encrypted.yaml    |\n\n    Scenario Outline: Writing an entry at the prompt (no editor) should store the entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"bad doggie no biscuit\" if prompted\n        When we run \"jrnl\" and type \"25 jul 2013: I saw Elvis. He's alive.\"\n        Then we should get no error\n        When we run \"jrnl -on '2013-07-25'\"\n        Then the output should contain \"2013-07-25 09:00 I saw Elvis.\"\n        And the output should contain \"| He's alive.\"\n\n        Examples: configs\n        | config_file       |\n        | simple.yaml       |\n        | empty_folder.yaml |\n        | encrypted.yaml    |\n\n    @todo\n    Scenario: Writing an entry at the prompt (no editor) in DayOne journal\n    # Need to test DayOne w/out an editor\n\n    Scenario: Writing into Dayone\n        Given we use the config \"dayone.yaml\"\n        When we run \"jrnl 01 may 1979: Being born hurts.\"\n        And we run \"jrnl -until 1980\"\n        Then the output should be \"1979-05-01 09:00 Being born hurts.\"\n\n    Scenario: Writing into Dayone adds extended metadata\n        Given we use the config \"dayone.yaml\"\n        When we run \"jrnl 01 may 1979: Being born hurts.\"\n        And we run \"jrnl --export json\"\n        Then we should get no error\n        And the output should be valid JSON\n        Given we parse the output as JSON\n        Then \"entries\" in the parsed output should have 5 elements\n        And \"entries.0.creator\" in the parsed output should be\n            \"\"\"\n            software_agent\n            os_agent\n            host_name\n            generation_date\n            device_agent\n            \"\"\"\n        And \"entries.0.creator.software_agent\" in the parsed output should contain\n            \"\"\"\n            jrnl\n            \"\"\"\n\n    Scenario: Title with an embedded period on DayOne journal\n        Given we use the config \"dayone.yaml\"\n        When we run \"jrnl 04-24-2014: Ran 6.2 miles today in 1:02:03. I am feeling sore because I forgot to stretch.\"\n        Then we should get no error\n        When we run \"jrnl -1\"\n        Then the output should be\n            \"\"\"\n            2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.\n            | I am feeling sore because I forgot to stretch.\n            \"\"\"\n\n    Scenario: Opening an folder that's not a DayOne folder should treat as folder journal\n        Given we use the config \"empty_folder.yaml\"\n        When we run \"jrnl 23 july 2013: Testing folder journal.\"\n        Then we should get no error\n        When we run \"jrnl -1\"\n        Then the output should be \"2013-07-23 09:00 Testing folder journal.\"\n\n    Scenario Outline: Count when adding a single entry via --edit\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we append to the editor if opened\n            \"\"\"\n            [2021-11-13] worked on jrnl tests\n            \"\"\"\n        When we run \"jrnl --edit\"\n        Then the error output should contain \"3 entries found\"\n        And the error output should contain \"1 entry added\"\n\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        #| basic_dayone.yaml    | @todo\n\n\n    Scenario Outline: Correctly count when adding multiple entries via --edit\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we append to the editor if opened\n            \"\"\"\n            [2021-11-11] worked on jrnl tests\n            [2021-11-12] worked on jrnl tests again\n            [2021-11-13] worked on jrnl tests a little bit more\n            \"\"\"\n        When we run \"jrnl --edit\"\n        Then the error output should contain \"3 entries found\"\n        And the error output should contain \"3 entries added\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        #| basic_dayone.yaml    | @todo\n\n\n    Scenario Outline: Correctly count when removing entries via --edit\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we write to the editor if opened\n            \"\"\"\n            [2021-11-13] I am replacing my whole journal with this entry\n            \"\"\"\n        When we run \"jrnl --edit\"\n        Then the output should contain \"2 entries deleted\"\n        And the output should contain \"1 entry modified\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        #| basic_dayone.yaml    | @todo\n\n\n    Scenario Outline: Correctly count modification when running --edit to replace a single entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we write to the editor if opened\n            \"\"\"\n            [2021-11-13] I am replacing the last entry with this entry\n            \"\"\"\n        When we run \"jrnl --edit -1\"\n        Then the error output should contain \"1 entry modified\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        #| basic_dayone.yaml    | @todo\n\n\n    Scenario Outline: Count modifications when editing whole journal and adding to last entry\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we append to the editor if opened\n            \"\"\"\n            This is a small addendum to my latest entry.\n            \"\"\"\n        When we run \"jrnl --edit\"\n        Then the error output should contain \"3 entries found\"\n        And the error output should contain \"1 entry modified\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario Outline: No \"Entry added\" message should appear when writing to the default journal\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl This is a new entry\"\n        Then the output should not contain \"Entry added\"\n        And we should get no error\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n\n    Scenario: An \"Entry added\" message should appear when writing to a non-default journal\n        Given we use the config \"multiple.yaml\"\n        And we use the password \"test\" if prompted\n        When we run \"jrnl work This is a new entry\"\n        Then the output should contain \"Entry added to work journal\"\n        And we should get no error\n\n    Scenario Outline: Tags are saved when an entry is edited with --edit and can be searched afterward\n        Given we use the config \"<config_file>\"\n        And we use the password \"test\" if prompted\n        And we append to the editor if opened\n            \"\"\"\n            @newtag\n            \"\"\"\n        When we run \"jrnl --edit -1\"\n        Then the error output should contain \"1 entry modified\"\n        When we run \"jrnl --tags @newtag\"\n        Then the output should contain\n            \"\"\"\n            1 entry found\n            \"\"\"\n\n        Examples: configs\n        | config_file          |\n        | basic_onefile.yaml   |\n        | basic_encrypted.yaml |\n        | basic_folder.yaml    |\n        | basic_dayone.yaml    |\n"
  },
  {
    "path": "tests/bdd/test_features.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom pytest_bdd import scenarios\n\nscenarios(\"features/actions.feature\")\nscenarios(\"features/build.feature\")\nscenarios(\"features/config_file.feature\")\nscenarios(\"features/core.feature\")\nscenarios(\"features/datetime.feature\")\nscenarios(\"features/delete.feature\")\nscenarios(\"features/change_time.feature\")\nscenarios(\"features/encrypt.feature\")\nscenarios(\"features/file_storage.feature\")\nscenarios(\"features/format.feature\")\nscenarios(\"features/import.feature\")\nscenarios(\"features/install.feature\")\nscenarios(\"features/multiple_journals.feature\")\nscenarios(\"features/override.feature\")\nscenarios(\"features/password.feature\")\nscenarios(\"features/search.feature\")\nscenarios(\"features/star.feature\")\nscenarios(\"features/tag.feature\")\nscenarios(\"features/template.feature\")\nscenarios(\"features/upgrade.feature\")\nscenarios(\"features/write.feature\")\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom pytest import mark\nfrom pytest import skip\n\nfrom jrnl.os_compat import on_posix\nfrom jrnl.os_compat import on_windows\n\npytest_plugins = [\n    \"tests.lib.fixtures\",\n    \"tests.lib.given_steps\",\n    \"tests.lib.when_steps\",\n    \"tests.lib.then_steps\",\n]\n\n\ndef pytest_bdd_apply_tag(tag, function):\n    # skip markers\n    if tag == \"skip_win\":\n        marker = mark.skipif(on_windows(), reason=\"Skip test on Windows\")\n    elif tag == \"skip_posix\":\n        marker = mark.skipif(on_posix(), reason=\"Skip test on Mac/Linux\")\n\n    # only on OS markers\n    elif tag == \"on_win\":\n        marker = mark.skipif(not on_windows(), reason=\"Skip test not on Windows\")\n    elif tag == \"on_posix\":\n        marker = mark.skipif(not on_posix(), reason=\"Skip test not on Mac/Linux\")\n    else:\n        # Fall back to pytest-bdd's default behavior\n        return None\n\n    marker(function)\n    return True\n\n\ndef pytest_runtest_setup(item):\n    markers = [mark.name for mark in item.iter_markers()]\n\n    on_win = on_windows()\n    on_nix = on_posix()\n\n    if \"skip_win\" in markers and on_win:\n        skip(\"Skip test on Windows\")\n\n    if \"skip_posix\" in markers and on_nix:\n        skip(\"Skip test on Mac/Linux\")\n\n    if \"on_win\" in markers and not on_win:\n        skip(\"Skip test not on Windows\")\n\n    if \"on_posix\" in markers and not on_nix:\n        skip(\"Skip test not on Mac/Linux\")\n"
  },
  {
    "path": "tests/data/configs/basic_dayone.yaml",
    "content": "colors:\n  date: none\n  title: none\n  body: none\n  tags: none\ndefault_hour: 9\ndefault_minute: 0\neditor: noop\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/basic_dayone.dayone\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/basic_encrypted.yaml",
    "content": "colors:\n  date: none\n  title: none\n  body: none\n  tags: none\ndefault_hour: 9\ndefault_minute: 0\neditor: noop\nencrypt: true\nhighlight: true\njournals:\n  default: features/journals/basic_encrypted.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/basic_folder.yaml",
    "content": "colors:\n  date: none\n  title: none\n  body: none\n  tags: none\ndefault_hour: 9\ndefault_minute: 0\neditor: noop\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/basic_folder\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/basic_onefile.yaml",
    "content": "colors:\n  date: none\n  title: none\n  body: none\n  tags: none\ndefault_hour: 9\ndefault_minute: 0\neditor: noop\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/basic_onefile.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/brackets.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/brackets.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/bug153.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/bug153.dayone\nlinewrap: 80\ntagsymbols: '@'\ntemplate: false\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/bug780.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/bug780.dayone\nlinewrap: 80\ntagsymbols: '@'\ntemplate: false\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/dayone.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: noop\ntemplate: false\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/dayone.dayone\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/dayone_empty.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: noop\ntemplate: false\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/dayone_empty.dayone\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/deletion.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/deletion.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/deletion_filters.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/deletion_filters.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/duplicate_keys.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\ntemplate: false\ntemplate: false\njournals:\n  default: \n    encrypt: false\n    journal: features/journals/simple.journal\n    journal: features/journals/simple.journal\n  ideas:\n    encrypt: false\n    journal: features/journals/does-not-exist.journal\n  simple: \n    encrypt: false\n    journal: features/journals/simple.journal\n    encrypt: false\n  work:\n    encrypt: false\n    journal: features/journals/work.journal\nlinewrap: 80\ntagsymbols: '@'\neditor: nano\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/editor-args.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: vim -f -c 'setf markdown'\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/editor.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"vim\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/editor_empty_folder.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: 'vim'\ntemplate: false\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/empty_folder\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/editor_encrypted.yaml",
    "content": "colors:\n  body: green\n  date: blue\n  tags: none\n  title: yellow\ndefault_hour: 9\ndefault_minute: 0\neditor: \"vim\"\nencrypt: true\ntemplate: false\nhighlight: true\njournals:\n  default: features/journals/encrypted.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/editor_markdown_extension.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\nencrypt: false\nhighlight: true\neditor: \"vim\"\njournals:\n  default: features/journals/editor_markdown_extension.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: features/templates/extension.md\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/empty_file.yaml",
    "content": ""
  },
  {
    "path": "tests/data/configs/empty_folder.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\ntemplate: false\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/empty_folder\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/encrypted.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: true\ntemplate: false\nhighlight: true\njournals:\n  default: features/journals/encrypted.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/encrypted_old.json",
    "content": "{\n    \"default_hour\": 9,\n    \"default_minute\": 0,\n    \"editor\": \"\",\n    \"encrypt\": true,\n    \"highlight\": true,\n    \"journals\": {\n      \"default\": \"features/journals/encrypted_jrnl-1-9-5.journal\"\n    },\n    \"linewrap\": 80,\n    \"tagsymbols\": \"@\",\n    \"timeformat\": \"%Y-%m-%d %H:%M\"\n}\n"
  },
  {
    "path": "tests/data/configs/encrypted_old.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: true\nhighlight: true\njournals:\n  default: features/journals/encrypted_jrnl1-9-5.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/format_md.yaml",
    "content": "colors:\n  body: none\n  date: none\n  tags: none\n  title: none\ndefault_hour: 9\ndefault_minute: 0\ndisplay_format: markdown\neditor: ''\nencrypt: false\nhighlight: true\nindent_character: '|'\njournals:\n  default: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: '@'\ntemplate: false\ntimeformat: '%Y-%m-%d %H:%M'\nversion: v2.4.5\n"
  },
  {
    "path": "tests/data/configs/format_text.yaml",
    "content": "colors:\n  body: none\n  date: none\n  tags: none\n  title: none\ndefault_hour: 9\ndefault_minute: 0\ndisplay_format: text\neditor: ''\nencrypt: false\nhighlight: true\nindent_character: '|'\njournals:\n  default: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: '@'\ntemplate: false\ntimeformat: '%Y-%m-%d %H:%M'\nversion: v2.4.5\n"
  },
  {
    "path": "tests/data/configs/invalid_color.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: not-a-color\n  title: also-not-a-color\n  body: still-no-color\n  tags: me-too\n"
  },
  {
    "path": "tests/data/configs/linewrap_auto.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/simple.journal\nlinewrap: auto\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/little_endian_dates.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/little_endian_dates.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%d.%m.%Y %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/markdown-headings-335.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\ntemplate: false\njournals:\n  default: features/journals/markdown-headings-335.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/missing_directory.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/missing_directory/simple.journal\n  endslash: features/journals/missing_folder/\n  endbackslash: features\\journals\\missing_folder\\\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/missing_journal.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/missing.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/mostlyreadabledates.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/mostlyreadabledates.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/multiline-tags.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/multiline-tags.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/multiline.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/multiline.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/multiple.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\ntemplate: false\njournals:\n  default: features/journals/simple.journal\n  ideas: features/journals/does-not-exist.journal\n  simple: features/journals/simple.journal\n  work: features/journals/work.journal\n  new_encrypted:\n    encrypt: true\n    journal: features/journals/new_encrypted.journal\n  ✨: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/no_colors.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/no_default_journal.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\ntemplate: false\nencrypt: false\nhighlight: true\njournals:\n  simple: features/journals/simple.journal\n  work: features/journals/work.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\n"
  },
  {
    "path": "tests/data/configs/simple.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/simple.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/tags-216.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\ntemplate: false\njournals:\n  default: features/journals/tags-216.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/tags-237.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\ntemplate: false\njournals:\n  default: features/journals/tags-237.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/tags.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: ''\nencrypt: false\nhighlight: true\ntemplate: false\njournals:\n  default: features/journals/tags.journal\nlinewrap: 80\ntagsymbols: '@'\ntimeformat: '%Y-%m-%d %H:%M'\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/unreadabledates.yaml",
    "content": "default_hour: 9\ndefault_minute: 0\neditor: \"\"\nencrypt: false\nhighlight: true\njournals:\n  default: features/journals/unreadabledates.journal\nlinewrap: 80\ntagsymbols: \"@\"\ntemplate: false\ntimeformat: \"%Y-%m-%d %H:%M\"\nindent_character: \"|\"\ncolors:\n  date: none\n  title: none\n  body: none\n  tags: none\n"
  },
  {
    "path": "tests/data/configs/upgrade_from_195.json",
    "content": "{\n\"default_hour\": 9,\n\"timeformat\": \"%Y-%m-%d %H:%M\",\n\"linewrap\": 80,\n\"encrypt\": false,\n\"editor\": \"\",\n\"default_minute\": 0,\n\"highlight\": true,\n\"journals\": {\"default\": \"features/journals/simple_jrnl-1-9-5.journal\"},\n\"tagsymbols\": \"@\"\n}\n"
  },
  {
    "path": "tests/data/configs/upgrade_from_195_little_endian_dates.json",
    "content": "{\n\"default_hour\": 9,\n\"timeformat\": \"%d.%m.%Y %H:%M\",\n\"linewrap\": 80,\n\"encrypt\": false,\n\"editor\": \"\",\n\"default_minute\": 0,\n\"highlight\": true,\n\"journals\": {\"default\": \"features/journals/simple_jrnl-1-9-5_little_endian_dates.journal\"},\n\"tagsymbols\": \"@\"\n}\n"
  },
  {
    "path": "tests/data/configs/upgrade_from_195_with_missing_encrypted_journal.json",
    "content": "{\n\"default_hour\": 9,\n\"timeformat\": \"%Y-%m-%d %H:%M\",\n\"linewrap\": 80,\n\"encrypt\": true,\n\"editor\": \"\",\n\"default_minute\": 0,\n\"highlight\": true,\n\"journals\": {\"default\": \"features/journals/encrypted_jrnl-1-9-5.journal\", \"missing\":  \"features/journals/missing.journal\"},\n\"tagsymbols\": \"@\"\n}\n"
  },
  {
    "path": "tests/data/configs/upgrade_from_195_with_missing_journal.json",
    "content": "{\n\"default_hour\": 9,\n\"timeformat\": \"%Y-%m-%d %H:%M\",\n\"linewrap\": 80,\n\"encrypt\": false,\n\"editor\": \"\",\n\"default_minute\": 0,\n\"highlight\": true,\n\"journals\": {\"default\": \"features/journals/simple_jrnl-1-9-5.journal\", \"missing\":  \"features/journals/missing.journal\"},\n\"tagsymbols\": \"@\"\n}\n"
  },
  {
    "path": "tests/data/journals/basic_dayone.dayone/entries/D04D335AFED711EABA18FAFFC2100C3D.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Creation Date</key>\n\t<date>2020-08-29T18:11:00Z</date>\n\t<key>Starred</key>\n\t<false/>\n\t<key>Entry Text</key>\n\t<string>Entry the first.\nLorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada\nquis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque\naugue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu\nconsequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In\ncommodo nulla nec tellus placerat, sed ultricies metus bibendum. Duis eget\nvenenatis erat. In at dolor dui. @tagone and maybe also @tagtwo.\n\nCurabitur accumsan nunc ac neque tristique, eleifend faucibus justo\nullamcorper. Suspendisse at mattis nunc. Nullam eget lacinia urna. Suspendisse\npotenti. Ut urna est, venenatis sed ante in, ultrices congue mi. Maecenas eget\nmolestie metus. Mauris porttitor dui ornare gravida porta. Quisque sed lectus\nhendrerit, lacinia ante eget, vulputate ante. Aliquam vitae erat non felis\nfeugiat sagittis. Phasellus quis arcu fringilla, mattis ligula id, vestibulum\nurna. Vivamus facilisis leo a mi tincidunt condimentum. Donec eu euismod enim.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu ligula eget\nvelit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac\nporta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per\nconubia nostra, per inceptos himenaeos.</string>\n\t<key>Time Zone</key>\n\t<string>America/Los_Angeles</string>\n\t<key>UUID</key>\n\t<string>D04D335AFED711EABA18FAFFC2100C3D</string>\n\t<key>Tags</key>\n\t<array>\n\t\t<string>ipsum</string>\n\t\t<string>tagone</string>\n\t\t<string>tagtwo</string>\n\t</array>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string></string>\n\t\t<key>Generation Date</key>\n\t\t<date>2020-09-25T02:35:45Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>iris.lan</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Darwin/19.3.0</string>\n\t\t<key>Software Agent</key>\n\t\t<string>jrnl/v2.4.5</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/basic_dayone.dayone/entries/FC8A86CAFED711EA8892FAFFC2100C3D.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Creation Date</key>\n\t<date>2020-08-31T21:32:00Z</date>\n\t<key>Starred</key>\n\t<false/>\n\t<key>Entry Text</key>\n\t<string>A second entry in what I hope to be a long series.\nSed sit amet metus et sapien feugiat elementum. Aliquam bibendum lobortis leo\nvitae tempus. Donec eleifend nec mi non volutpat. Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. Praesent ut sodales libero. Maecenas nisl lorem,\nvestibulum in tempus sit amet, fermentum ut arcu. Donec vel vestibulum lectus,\neget pretium enim. Maecenas diam nunc, imperdiet vitae pharetra sed, pretium id\nlectus. Donec eu metus et turpis tempor tristique ac non ex. In tellus arcu,\negestas at efficitur et, ultrices vel est. Sed commodo et nibh non elementum.\nMauris tempus vitae neque vel viverra. @tagtwo all by its lonesome.\n\nNulla mattis elementum magna, viverra pretium dui fermentum et. Cras vel\nvestibulum odio. Quisque sit amet turpis et urna finibus maximus. Interdum et\nmalesuada fames ac ante ipsum primis in faucibus. Fusce porttitor iaculis sem,\nnon dictum ipsum varius nec. Nulla eu erat at risus gravida blandit non vel\nante. Nam egestas ipsum leo, eu ultricies ipsum tincidunt vel. Morbi a commodo\neros.\n\nNullam dictum, nisl ac varius tempus, ex tortor fermentum nisl, non\ntempus dolor neque a lorem. Suspendisse a faucibus ex, vel ornare tortor.\nMaecenas tincidunt id felis quis semper. Pellentesque enim libero, fermentum\nquis metus id, rhoncus euismod magna. Nulla finibus velit eu purus bibendum\ninterdum. Integer id justo dui. Integer eu tellus in turpis bibendum blandit.\nQuisque auctor lacinia consectetur.</string>\n\t<key>Time Zone</key>\n\t<string>America/Los_Angeles</string>\n\t<key>UUID</key>\n\t<string>FC8A86CAFED711EA8892FAFFC2100C3D</string>\n\t<key>Tags</key>\n\t<array>\n\t\t<string>tagtwo</string>\n\t</array>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string></string>\n\t\t<key>Generation Date</key>\n\t\t<date>2020-09-25T02:36:59Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>iris.lan</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Darwin/19.3.0</string>\n\t\t<key>Software Agent</key>\n\t\t<string>jrnl/v2.4.5</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/basic_dayone.dayone/entries/FD8ABC8EFED711EABC35FAFFC2100C3D.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Creation Date</key>\n\t<date>2020-09-24T16:14:00Z</date>\n\t<key>Starred</key>\n\t<true/>\n\t<key>Entry Text</key>\n\t<string>The third entry finally after weeks without writing.\nI'm so excited about emojis. 💯 🎶 💩\n\nDonec semper pellentesque iaculis. Nullam cursus et justo sit amet venenatis.\nVivamus tempus ex dictum metus vehicula gravida. Aliquam sed sem dolor. Nulla\neget ultrices purus. Quisque at nunc at quam pharetra consectetur vitae quis\ndolor. Fusce ultricies purus eu est feugiat, quis scelerisque nibh malesuada.\nQuisque egestas semper nibh in hendrerit. Nam finibus ex in mi mattis\nvulputate. Sed mauris urna, consectetur in justo eu, volutpat accumsan justo.\nPhasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at\nante eget fringilla. @tagthree and also @tagone</string>\n\t<key>Time Zone</key>\n\t<string>America/Los_Angeles</string>\n\t<key>UUID</key>\n\t<string>FD8ABC8EFED711EABC35FAFFC2100C3D</string>\n\t<key>Tags</key>\n\t<array>\n\t\t<string>tagthree</string>\n\t\t<string>tagone</string>\n\t</array>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string></string>\n\t\t<key>Generation Date</key>\n\t\t<date>2020-09-25T02:37:01Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>iris.lan</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Darwin/19.3.0</string>\n\t\t<key>Software Agent</key>\n\t\t<string>jrnl/v2.4.5</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/basic_encrypted.journal",
    "content": "gAAAAABfb4gQBMqqGn_W8v_s7qCi14bX7inuCOKbsBqIUf7_ch14vTUp7lrysPFvhBp5vGijTwDIbk4LKoIISj8NwM31I8L0zEbMx9y6iyF_zseGGNxBvNN0wzAXa67bs-ohiQhhebcdIc_52sltxL2ELh8JAKUaXRwyapgnMgJ7z6deJppLK-B7RE7BiT0eKjWTDMd2x6cZDswvHs9opDp5yjuKWV5m7x6ggCKYgHT3savT9Tg7V0Fq6K3LGWaE59lCrqlAB0u6dnrDX3qcF4SKyckaniXzRShZGebdkUKDcLFun2V2syZwYQN772xjznIsJ16iXicox2uYKg8CnTefsyCwaOZyBvySGEy3CrlBiuIRIcxCtjKbYJ2B-Aq7LZitnBR7Ny_6_Wm8HsBf3N-cFCp4GShiCKrxuXKcOZ7vszG5EKb78JS85bb0mswU5CSdgp6UAHjIZqfJq00qQsViBCbXq3oklCPZXdQkOf5U0KpG2MVUiD-Zcn5Qj3gnUhSEr-5wKU9tWrE63MGPyE6KjZlArZX2W2LeGnW2CEYw9eREGon06AzLJ4mj3BgtjVWLIdGcCwORXvHRjUqazWgbEmXNVTbtp_cKnkW-rFzRBrUoVme9v-1Y3sH0VvHBq7QIj915VzBklzWs1qzIyTPZG5Db9LvdQ7SiV8slf1Jo7l-ayUUdVj6igvKZcgfB4RUHolJoMps5p4lZ5sPqv59KtSa8DCpuoRczIj71OCpuRVARZgy1m5sUD9xSMxOBdy46u1Jnry6iMtzXWI3mEZe5m7UhmW_L4Zcv4bbk8XjkBeHjPdgm2B69jkLmCBFecD5ztoGesCGt_pNo_sWSKqLHV1-coKFB2Nn__a4utU9NJNdeNRkr8_ahU6tn3jmaFjfQ7cKfrXG_NCcYBRX9fja8EQIeBEp_3TCoXQqhuV_bGsNPA2qL63Pt6YiRaUf1g9FNBqJRlKCSOYNixSXQZN_rTePzx0SQ0aIQhADWls62WX-LG5-byJcB6W2P_cH21hDOXkoNEIyLnCz9HQ6Yd6Fbv7298ps3F6jiUDdWES23zv8sDgBuKUN94qSN34j6MDYGFnGI9zsJ-Y-I2frdlLfWPx3pUL7afcKh1nRgXdjctsTSxU2BDrsu03eBz2IoZjoOR0U51IrNMOD1NNT3kctXxHLuOHSEkwAzS3doncQbdRLi5Gc1dQuOUa4sC-p8gVjUKXO-oi_49kp9Km2Ay9wFg0epBbXx2QMzyMsN2dXeSbHF-BDXD6sULaq5syC0fOHqaMLycTCMk2wLfNyXgEt05WvAiDn-LDsRdylMRW2hXp5HWq3Poaul-7VNg6UEMlwVfgJ-7hNreuO6IRtwmx6YdqMscw0ms6mU_MQZU_dTIPg3JU4KL0YyMqPBPSGNCx3gMp41O05Ubir45FoJSnT5Dkj4v3N0S87Ys3HuFLverASsGt9bkcSzd2uMKCJjkspemPPi9VhrY4IOO03DWSWbHmxYzFc1SJ-24WM8Ch404QKpe1qy5LNzFgLvDwQhSIHjluezHXqrD-DVh1lWNNY3WmHI2ubOZfaorvLKqzBPZ6AhpIa60rKjm0OZIQOmJwWXwkdnzut6m8PtoiLzRN897YMgeztf1nmDwp0xE-EhknVZ3WV3TeqgZJ5ykfHQ5BU8x0Db57-UtKSuesKbqPPdBe91OdsPpkGlyl6psHj1_gPm4nLvzXQePwiPaEemR_gYCWGPvl9l1ANJufgCV9qQTmZGof3fb9mjv-9lS-9l_m8KirPPRpSBToNeDtk50ceYUsOlDGzIyusppG9pOcIGyiln1IO5aZ8d4_1E83qjcHTSaKGizICZU7a-pt5STBPMesy3JgBm23A2jO4m68ayBRMcLnw_RirHvvBaj0C6UR2tac45F0Ob3PpXcvFuK0g54ziIAhzGqwF9I-LZ6asXQWMW4y4EBOak8JJBorkfztzfkMaIgGu-4ZoRKOkVfdr4uzcghk3r6KUxD4-nv1ioX69-G5RwhMHppYk7z8RXS1cq5FkvzXbfEQ-Uv6M-sx32DcUy9dH-ZYhc7UWm75JJfiNXLaXT_bsc6VqQ7KPkg2-RA7CywUFCW9S0S-XdO03VdwqlUVo7fp1SKywEfhZv_9bhDCdMJBwZmigv2KP9Iz7fF6LrpLwZkzHuQGFPcyTHFpsVIFrFyJjNYCXpET9y0Q5Vt4fnea5fy-9ZiCt3S8aS0YOFJ35_kM5i3ss8eFPL0v7fIQS3ZilzdGB3bWL0J7kppHN_ekHu-wVk3UZxauoFh7hXLjPcipua-FYUIklLjcK6DG1bYP7_q6OnkC8Jl650FNezeWPomHEv7l_DO3y0tjI6SGdWvL3ZJns7Xp3ew8KsCREAUO7ffqumD03uF9N-9uWbDDjM7rk0vcg0ggfOs9Ni725mxqYpu4R285XCOVWHDvw7iU6eAvE6ry8TDXQBbNgGjTuTYFYYli7GuOqMxFIe1op2s7sRnoJE8O0J76S6APhjhjcnZRSuONWkVG_5o83uFMPSF8DtqLwuRA5E8AGfIwAUcj324sw-DA0ixBGUqomb-osUIisv3x0b044xn-FvD-8R3PZDnPbPsao8XYNxfQWStrNcZSrX2Ua-WAcv9qbQ73_57RKW4pao4ajOu7K5800D231WGiIa6aJzDnFUlzXEzYxFQyx7qegkm_9rrEp_v8TC9mfAcjWX5DMrCkxUskx9YKDfpFYq4NuxO_414gReKzd-lmorfigvttgS10N1XD74SwFluXJv-bqTbI5-SuYAhDGMv1dqrn38i3rOMQqqnQomvaUJRprqxUsKz14sSE1Y-cNqq1FXzZ6vIJq-K3YTfFWPRLeqi6gHzqS_R2YBXXUduKuYgmakiVdP3bWc-Ca8WKh5sVi6P51MO-cS7i9AZWOaOz7F8PsB4JZxAJjSOr3NBmv3EEve9auTFCudRjfC6668I_NMHaTP5CCV4cuhuAxUuKUGgd6WFjDcvoYPyn_lu3bQiqD9MEag4CaJYI9PlraRv5mbqptwxv3pca7usd0GmXN_2No_nwxB4gVb48LsBBkH35njCa5iv2EKXUSOf0k3swaTSEahqbyI4EDzPXtU5uBO39iQzNpgfV_sUpnGdysjqueUVcdWGI_s5CnrNJ-_yDAY06AoXfLrjP8_3NXB2058xZ2rfmTNJNCULz9634dICJReXNnmplxIg3i6GbzFvjfNtqjrWr_iqBShyIwuOUJRbXzdJNggx2BDNG-PEWDXl89SaudFICkDvyZKEcATIss6ZXfULIMfCrqmWmFwgXfNEd9TuvjqoxFlLSaY4UfDMiYa_arUMblFfoo5nV07GANhUoQd-6HRe7LjYeX5VRodOx6ZmZjIAUq-DYr-hatJJFR2tjT_qZht2MJeYT3GZ3o54m8zBBt0JTN7HVpKaOaM3A2hEM_Ah0QZ-DkLDxtCzMuv987GDiLT2-Riya97a47yHIJhZFzFpflW2FcuC8RFWXlfUKTQfZkFmxh3MUekUuS4yu4Z121xojVswk_4P7-FqLaSnGT2epI69I_cvalRx3wjds9-5TFYqf4GridlFBRx6Fv2fpNB9Zvp9k7NQ9oYcPuXGLoXH5kmWBagPhEGKHA_pjFUZmCuwUIoeP4nP8lhFrX8OGezsbSBG773CRJzEdfcgAc5G-p6M_24WZLZHDrsVBAvgrNt6R9eQbEviWU28t_417QCp-or9qqt4OTKv1dp_4MlZh8YBg2-dtpvzSc1l5e4kQFJu7oWlpbgsjB6pl1oRRKp1maedX-gOAf559zC4l85gfEpPln9Cnl6xvERQzfO0Ey4q91SdsgK7i7FBrKKmi2wGiemFvnaQsrjZ_IFujLo8-2c8g9zTiyH1knyoVOAAnQxqGpsz6z6PNfSxr3_G8tOlNFTV-yqN_LdVHMgXtXjn3U9koGsfMulyUcBDdR3d_0Yn6iEjBt77tbxKi2ry-0gQrB1fdGsgKjyE_tMrW8D_lQz0IXsVOzd2ixsFVXMFzD6OOD8JldV0FbA-VDAS-Tp_ezIZVp6lRq54XBgvsjzDyOmOgDbSOQN6SQmvxPnIsml1wgmtm80z-9gHBqmimHBtLKB6L7CtLmmPICMS2pX3eWOmakxscxqs8AVjijJdz_NYNfcdBeDj_fhm6dqD6iwk3EBZZfsrmMGdXtAMqf1r9ng9tsz-FriXwQiJ3IM3loBsk5DKr9CcaJtKSPuwDDlRynD2vwcD-XyF6YTQdSJa9fEcq-qXya2Scj4mqQ4RDemJgErdradRfwJfII3fWHh18XxmYVqi9Bwn3YRgwEadyo0-HjbNq6vJXi12igmP99ciRAfMVQLjfUfTwoOHj44Y2Ru_hPjJcvB6FIn6KLrrCSrZnrshFdFn4L36z1CrS8fbtdvrG3kdZQxsUJnMqttuwKRpLnDWTWkIwj_GRBFrzCFgbwGp1XYhemxggyKVuhZPfyyTIM9rhlPth6eGyrpYfap24Av_mGPRBLnzcjtpGbACGdKQL034kVmI7yENGvmY40KSrWsVG_BE9bSJhx0EptFsT2IxnxbuFD4hGb4fFag9V0BDiKpUoOZqIVqVO8cAp-5w4twvWZKkrhu16JNlLoXWMoFANrw-tp5LKSin1CUeRa4LWVI1GR8tRkIad_GnCHRv9JEMswlNy9wi2sDNsSxWT7WNasUW5-glgK9pR7d2pXGGOWfHj1U6CKIqmAiO3iw8igzhvyx_dAxMxPo"
  },
  {
    "path": "tests/data/journals/basic_folder/2020/08/29.txt",
    "content": "[2020-08-29 11:11:00 AM] Entry the first.\nLorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada\nquis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque\naugue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu\nconsequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In\ncommodo nulla nec tellus placerat, sed ultricies metus bibendum. Duis eget\nvenenatis erat. In at dolor dui. @tagone and maybe also @tagtwo.\n\nCurabitur accumsan nunc ac neque tristique, eleifend faucibus justo\nullamcorper. Suspendisse at mattis nunc. Nullam eget lacinia urna. Suspendisse\npotenti. Ut urna est, venenatis sed ante in, ultrices congue mi. Maecenas eget\nmolestie metus. Mauris porttitor dui ornare gravida porta. Quisque sed lectus\nhendrerit, lacinia ante eget, vulputate ante. Aliquam vitae erat non felis\nfeugiat sagittis. Phasellus quis arcu fringilla, mattis ligula id, vestibulum\nurna. Vivamus facilisis leo a mi tincidunt condimentum. Donec eu euismod enim.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu ligula eget\nvelit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac\nporta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per\nconubia nostra, per inceptos himenaeos.\n"
  },
  {
    "path": "tests/data/journals/basic_folder/2020/08/31.txt",
    "content": "[2020-08-31 02:32:00 PM] A second entry in what I hope to be a long series. *\nSed sit amet metus et sapien feugiat elementum. Aliquam bibendum lobortis leo\nvitae tempus. Donec eleifend nec mi non volutpat. Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. Praesent ut sodales libero. Maecenas nisl lorem,\nvestibulum in tempus sit amet, fermentum ut arcu. Donec vel vestibulum lectus,\neget pretium enim. Maecenas diam nunc, imperdiet vitae pharetra sed, pretium id\nlectus. Donec eu metus et turpis tempor tristique ac non ex. In tellus arcu,\negestas at efficitur et, ultrices vel est. Sed commodo et nibh non elementum.\nMauris tempus vitae neque vel viverra. @tagtwo all by its lonesome.\n\nNulla mattis elementum magna, viverra pretium dui fermentum et. Cras vel\nvestibulum odio. Quisque sit amet turpis et urna finibus maximus. Interdum et\nmalesuada fames ac ante ipsum primis in faucibus. Fusce porttitor iaculis sem,\nnon dictum ipsum varius nec. Nulla eu erat at risus gravida blandit non vel\nante. Nam egestas ipsum leo, eu ultricies ipsum tincidunt vel. Morbi a commodo\neros.\n\nNullam dictum, nisl ac varius tempus, ex tortor fermentum nisl, non\ntempus dolor neque a lorem. Suspendisse a faucibus ex, vel ornare tortor.\nMaecenas tincidunt id felis quis semper. Pellentesque enim libero, fermentum\nquis metus id, rhoncus euismod magna. Nulla finibus velit eu purus bibendum\ninterdum. Integer id justo dui. Integer eu tellus in turpis bibendum blandit.\nQuisque auctor lacinia consectetur.\n"
  },
  {
    "path": "tests/data/journals/basic_folder/2020/09/24.txt",
    "content": "[2020-09-24 09:14:00 AM] The third entry finally after weeks without writing.\nI'm so excited about emojis. 💯 🎶 💩\n\nDonec semper pellentesque iaculis. Nullam cursus et justo sit amet venenatis.\nVivamus tempus ex dictum metus vehicula gravida. Aliquam sed sem dolor. Nulla\neget ultrices purus. Quisque at nunc at quam pharetra consectetur vitae quis\ndolor. Fusce ultricies purus eu est feugiat, quis scelerisque nibh malesuada.\nQuisque egestas semper nibh in hendrerit. Nam finibus ex in mi mattis\nvulputate. Sed mauris urna, consectetur in justo eu, volutpat accumsan justo.\nPhasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at\nante eget fringilla. @tagthree and also @tagone\n"
  },
  {
    "path": "tests/data/journals/basic_folder/2020/09/should-be-ignored.txt",
    "content": "[2022-03-02 9:25:00 AM] This file should be ignored (month)\nThis text file is in a folder journal's month directory (\"2020/09\"), but it's not in the file name format used by jrnl for folder journal entries, so it should be ignored.\n\nThis file should not ever appear in a test."
  },
  {
    "path": "tests/data/journals/basic_folder/2020/should-be-ignored.txt",
    "content": "[2022-03-02 9:25:00 AM] This file should be ignored (year)\nThis text file is in a folder journal's year directory (\"2020\"), but it's not in the file name format used by jrnl for folder journal entries, so it should be ignored.\n\nThis file should not ever appear in a test."
  },
  {
    "path": "tests/data/journals/basic_folder/should-be-ignored.txt",
    "content": "[2022-03-02 9:25:00 AM] This file should be ignored (root)\nThis text file is in a folder journal's root directory, but it's not in the file name format used by jrnl for folder journal entries, so it should be ignored.\n\nThis file should not ever appear in a test."
  },
  {
    "path": "tests/data/journals/basic_onefile.journal",
    "content": "[2020-08-29 11:11] Entry the first.\n\nLorem @ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada\nquis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque\naugue et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu\nconsequat. Aenean ante ex, elementum ut interdum et, mattis eget lacus. In\ncommodo nulla nec tellus placerat, sed ultricies metus bibendum. Duis eget\nvenenatis erat. In at dolor dui. @tagone and maybe also @tagtwo.\n\nCurabitur accumsan nunc ac neque tristique, eleifend faucibus justo\nullamcorper. Suspendisse at mattis nunc. Nullam eget lacinia urna. Suspendisse\npotenti. Ut urna est, venenatis sed ante in, ultrices congue mi. Maecenas eget\nmolestie metus. Mauris porttitor dui ornare gravida porta. Quisque sed lectus\nhendrerit, lacinia ante eget, vulputate ante. Aliquam vitae erat non felis\nfeugiat sagittis. Phasellus quis arcu fringilla, mattis ligula id, vestibulum\nurna. Vivamus facilisis leo a mi tincidunt condimentum. Donec eu euismod enim.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu ligula eget\nvelit scelerisque fringilla. Phasellus pharetra justo et nulla fringilla, ac\nporta sapien accumsan. Class aptent taciti sociosqu ad litora torquent per\nconubia nostra, per inceptos himenaeos.\n\n[2020-08-31 14:32] A second entry in what I hope to be a long series. *\n\nSed sit amet metus et sapien feugiat elementum. Aliquam bibendum lobortis leo\nvitae tempus. Donec eleifend nec mi non volutpat. Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. Praesent ut sodales libero. Maecenas nisl lorem,\nvestibulum in tempus sit amet, fermentum ut arcu. Donec vel vestibulum lectus,\neget pretium enim. Maecenas diam nunc, imperdiet vitae pharetra sed, pretium id\nlectus. Donec eu metus et turpis tempor tristique ac non ex. In tellus arcu,\negestas at efficitur et, ultrices vel est. Sed commodo et nibh non elementum.\nMauris tempus vitae neque vel viverra. @tagtwo all by its lonesome.\n\nNulla mattis elementum magna, viverra pretium dui fermentum et. Cras vel\nvestibulum odio. Quisque sit amet turpis et urna finibus maximus. Interdum et\nmalesuada fames ac ante ipsum primis in faucibus. Fusce porttitor iaculis sem,\nnon dictum ipsum varius nec. Nulla eu erat at risus gravida blandit non vel\nante. Nam egestas ipsum leo, eu ultricies ipsum tincidunt vel. Morbi a commodo\neros.\n\nNullam dictum, nisl ac varius tempus, ex tortor fermentum nisl, non\ntempus dolor neque a lorem. Suspendisse a faucibus ex, vel ornare tortor.\nMaecenas tincidunt id felis quis semper. Pellentesque enim libero, fermentum\nquis metus id, rhoncus euismod magna. Nulla finibus velit eu purus bibendum\ninterdum. Integer id justo dui. Integer eu tellus in turpis bibendum blandit.\nQuisque auctor lacinia consectetur.\n\n[2020-09-24 09:14] The third entry finally after weeks without writing.\n\nI'm so excited about emojis. 💯 🎶 💩\n\nDonec semper pellentesque iaculis. Nullam cursus et justo sit amet venenatis.\nVivamus tempus ex dictum metus vehicula gravida. Aliquam sed sem dolor. Nulla\neget ultrices purus. Quisque at nunc at quam pharetra consectetur vitae quis\ndolor. Fusce ultricies purus eu est feugiat, quis scelerisque nibh malesuada.\nQuisque egestas semper nibh in hendrerit. Nam finibus ex in mi mattis\nvulputate. Sed mauris urna, consectetur in justo eu, volutpat accumsan justo.\nPhasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at\nante eget fringilla. @tagthree and also @tagone\n"
  },
  {
    "path": "tests/data/journals/brackets.journal",
    "content": "[2019-07-08 05:42] Entry subject\n[1] line starting with 1\n"
  },
  {
    "path": "tests/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D511.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>Creation Date</key>\n    <date>2013-10-27T02:27:27Z</date>\n    <key>Creator</key>\n    <dict>\n        <key>Device Agent</key>\n        <string>iPhone/iPhone3,1</string>\n        <key>Generation Date</key>\n        <date>2013-10-27T07:02:27Z</date>\n        <key>Host Name</key>\n        <string>omrt104001</string>\n        <key>OS Agent</key>\n        <string>iOS/7.0.3</string>\n        <key>Software Agent</key>\n        <string>Day One (iOS)/1.11.4</string>\n    </dict>\n    <key>Entry Text</key>\n    <string>Some text.</string>\n    <key>Location</key>\n    <dict>\n        <key>Administrative Area</key>\n        <string>Östergötlands län</string>\n        <key>Country</key>\n        <string>Sverige</string>\n        <key>Latitude</key>\n        <real>58.383400000000000</real>\n        <key>Locality</key>\n        <string>City</string>\n        <key>Longitude</key>\n        <real>15.577170000000000</real>\n        <key>Place Name</key>\n        <string>Street</string>\n    </dict>\n    <key>Starred</key>\n    <false/>\n    <key>Time Zone</key>\n    <string>Europe/Stockholm</string>\n    <key>UUID</key>\n    <string>B40EE704E15846DE8D45C44118A4D511</string>\n    <key>Weather</key>\n    <dict>\n        <key>Celsius</key>\n        <string>12</string>\n        <key>Description</key>\n        <string>Clear</string>\n        <key>Fahrenheit</key>\n        <string>54</string>\n        <key>IconName</key>\n        <string>sunnyn.png</string>\n    </dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/bug153.dayone/entries/B40EE704E15846DE8D45C44118A4D512.doentry",
    "content": "<dict>\n    <key>Creation Date</key>\n    <date>2013-10-27T02:27:27Z</date>\n    <key>Creator</key>\n    <dict>\n        <key>Device Agent</key>\n        <string>iPhone/iPhone3,1</string>\n        <key>Generation Date</key>\n        <date>2013-10-27T07:02:27Z</date>\n        <key>Host Name</key>\n        <string>omrt104001</string>\n        <key>OS Agent</key>\n        <string>iOS/7.0.3</string>\n        <key>Software Agent</key>\n        <string>Day One (iOS)/1.11.4</string>\n    </dict>\n    <key>Entry Text</key>\n    <string>This is not a valid plist.</string>\n    <key>Location</key>\n    <dict>\n        <key>Administrative Area</key>\n        <string>Östergötlands län</string>\n        <key>Country</key>\n        <string>Sverige</string>\n        <key>Latitude</key>\n        <real>58.383400000000000</real>\n        <key>Locality</key>\n        <string>City</string>\n        <key>Longitude</key>\n        <real>15.577170000000000</real>\n        <key>Place Name</key>\n        <string>Street</string>\n    </dict>\n    <key>Starred</key>\n    <false/>\n    <key>Time Zone</key>\n    <string>Europe/Stockholm</string>\n    <key>UUID</key>\n    <string>B40EE704E15846DE8D45C44118A4D511</string>\n    <key>Weather</key>\n    <dict>\n        <key>Celsius</key>\n        <string>12</string>\n        <key>Description</key>\n        <string>Clear</string>\n        <key>Fahrenheit</key>\n        <string>54</string>\n        <key>IconName</key>\n        <string>sunnyn.png</string>\n    </dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/bug780.dayone/entries/48A25033B34047C591160A4480197D8B.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Activity</key>\n\t<string>Stationary</string>\n\t<key>Creation Date</key>\n\t<date>2019-12-30T21:28:54Z</date>\n\t<key>Entry Text</key>\n\t<string></string>\n\t<key>Starred</key>\n\t<false />\n\t<key>UUID</key>\n\t<string>48A25033B34047C591160A4480197D8B</string>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string>PC</string>\n\t\t<key>Generation Date</key>\n\t\t<date>2019-12-30T21:28:54Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>LE-TREPORT</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Microsoft Windows/10 Home</string>\n\t\t<key>Software Agent</key>\n\t\t<string>Journaley/2.1</string>\n\t</dict>\n\t<key>Tags</key>\n\t<array>\n\t\t<string>i_have_no_body</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/dayone.dayone/entries/044F3747A38546168B572C2E3F217FA2.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Creation Date</key>\n\t<date>2013-05-17T18:39:20Z</date>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string>Macintosh/MacBookAir5,2</string>\n\t\t<key>Generation Date</key>\n\t\t<date>2013-08-17T18:39:20Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>Egeria</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Mac OS X/10.8.4</string>\n\t\t<key>Software Agent</key>\n\t\t<string>Day One (Mac)/1.8</string>\n\t</dict>\n\t<key>Entry Text</key>\n\t<string>This entry has tags!</string>\n\t<key>Starred</key>\n\t<false/>\n\t<key>Tags</key>\n\t<array>\n\t\t<string>work</string>\n\t\t<string>PLaY</string>\n\t</array>\n\t<key>Time Zone</key>\n\t<string>America/Los_Angeles</string>\n\t<key>UUID</key>\n\t<string>044F3747A38546168B572C2E3F217FA2</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/dayone.dayone/entries/0BDDD6CDA43C4A9AA2681517CC35AD9D.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Creation Date</key>\n\t<date>2013-06-17T18:38:29Z</date>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string>Macintosh/MacBookAir5,2</string>\n\t\t<key>Generation Date</key>\n\t\t<date>2013-08-17T18:38:29Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>Egeria</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Mac OS X/10.8.4</string>\n\t\t<key>Software Agent</key>\n\t\t<string>Day One (Mac)/1.8</string>\n\t</dict>\n\t<key>Entry Text</key>\n\t<string>This entry has a location.</string>\n\t<key>Location</key>\n\t<dict>\n\t\t<key>Administrative Area</key>\n\t\t<string>California</string>\n\t\t<key>Country</key>\n\t\t<string>Germany</string>\n\t\t<key>Latitude</key>\n\t\t<real>52.4979764</real>\n\t\t<key>Locality</key>\n\t\t<string>Berlin</string>\n\t\t<key>Longitude</key>\n\t\t<real>13.2404758</real>\n\t\t<key>Place Name</key>\n\t\t<string>Abandoned Spy Tower</string>\n\t</dict>\n\t<key>Starred</key>\n\t<false/>\n\t<key>Tags</key>\n\t<array/>\n\t<key>Time Zone</key>\n\t<string>Europe/Berlin</string>\n\t<key>UUID</key>\n\t<string>0BDDD6CDA43C4A9AA2681517CC35AD9D</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/dayone.dayone/entries/422BC895507944A291E6FC44FC6B8BFC.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Creation Date</key>\n\t<date>2013-07-17T18:38:08Z</date>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string>Macintosh/MacBookAir5,2</string>\n\t\t<key>Generation Date</key>\n\t\t<date>2013-08-17T18:38:08Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>Egeria</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Mac OS X/10.8.4</string>\n\t\t<key>Software Agent</key>\n\t\t<string>Day One (Mac)/1.8</string>\n\t</dict>\n\t<key>Entry Text</key>\n\t<string>This entry is starred!</string>\n\t<key>Starred</key>\n\t<true/>\n\t<key>Tags</key>\n\t<array/>\n\t<key>Time Zone</key>\n\t<string>America/Los_Angeles</string>\n\t<key>UUID</key>\n\t<string>422BC895507944A291E6FC44FC6B8BFC</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/dayone.dayone/entries/4BB1F46946AD439996C9B59DE7C4DDC1.doentry",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>Creation Date</key>\n\t<date>2013-01-17T18:37:50Z</date>\n\t<key>Creator</key>\n\t<dict>\n\t\t<key>Device Agent</key>\n\t\t<string>Macintosh/MacBookAir5,2</string>\n\t\t<key>Generation Date</key>\n\t\t<date>2013-08-17T18:37:50Z</date>\n\t\t<key>Host Name</key>\n\t\t<string>Egeria</string>\n\t\t<key>OS Agent</key>\n\t\t<string>Mac OS X/10.8.4</string>\n\t\t<key>Software Agent</key>\n\t\t<string>Day One (Mac)/1.8</string>\n\t</dict>\n\t<key>Entry Text</key>\n\t<string>This is a DayOne entry without Timezone.</string>\n\t<key>Starred</key>\n\t<false/>\n\t<key>Tags</key>\n\t<array/>\n\t<key>UUID</key>\n\t<string>4BB1F46946AD439996C9B59DE7C4DDC1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "tests/data/journals/dayone_empty.dayone/entries/empty.txt",
    "content": "This file exists to preserve the directory structure, but should be ignored by jrnl.\r\n"
  },
  {
    "path": "tests/data/journals/deletion.journal",
    "content": "[2019-10-29 11:11] First entry.\n\n[2019-10-29 11:11] Second entry.\n\n[2019-10-29 11:13] Third entry."
  },
  {
    "path": "tests/data/journals/deletion_filters.journal",
    "content": "[2019-10-01 08:00] It's just another day in October.\nNot much to write about.\n\n[2020-01-01 08:00] Happy New Year! \nSo this is the New Year. @holidays\n\n[2020-03-01 08:00] It's just another day in March.\nA stick, a stone, it's the end of the road.\n\n[2020-05-01 09:00] Happy May Day!\n@holidays @springtime Several holidays fall on this date.\n\n[2020-05-02 12:10] Writing tests. *\n@springtime They will help prevent bugs.\n"
  },
  {
    "path": "tests/data/journals/empty_folder/empty",
    "content": "Nothing to see here\n"
  },
  {
    "path": "tests/data/journals/encrypted.journal",
    "content": "gAAAAABVIHB7tnwKExG7aC5ZbAbBL9SG2oY2GENeoOJ22i1PZigOvCYvrQN3kpsu0KGr7ay5K-_46R5YFlqJvtQ8anPH2FSITsaZy-l5Lz_5quw3rmzhLwAR1tc0icgtR4MEpXEdsuQ7cyb12Xq-JLDrnATs0id5Vow9Ri_tE7Xe4BXgXaySn3aRPwWKoninVxVPVvETY3MXHSUEXV9OZ-pH5kYBLGYbLA==\n"
  },
  {
    "path": "tests/data/journals/little_endian_dates.journal",
    "content": "[09.06.2013 15:39] My first entry.\nEverything is alright\n\n[10.07.2013 15:40] Life is good.\nBut I'm better.\n"
  },
  {
    "path": "tests/data/journals/markdown-headings-335.journal",
    "content": "[2015-04-14 13:23] Heading Test\n\nH1-1\n=\n\nH1-2\n===\n\nH1-3\n============================\n\nH2-1\n-\n\nH2-2\n---\n\nH2-3\n----------------------------------\n\nHorizontal Rules (ignore)\n\n---\n\n===\n\n# ATX H1\n\n## ATX H2\n\n### ATX H3\n\n#### ATX H4\n\n##### ATX H5\n\n###### ATX H6\n\nStuff\n\nMore stuff\nmore stuff again\n"
  },
  {
    "path": "tests/data/journals/mostlyreadabledates.journal",
    "content": "[2019-07-18 14:23] The first entry\nTime machines are possible. I know, because I've built one in my garage.\n\n[2019-07-19 14:23] The second entry\nI'm going to activate the machine. Nobody knows what comes next after this. Or before this?\n\n[2019-07 14:23] The third entry\nI've crossed so many timelines. Is there any going back?\n"
  },
  {
    "path": "tests/data/journals/multiline-tags.journal",
    "content": "[2013-06-09 15:39] Multiple @line entry with @tags.\nTag with @punctuation. afterwards\n@TagOnLineAloneWithOutPunctuation\n@TagOnLineAloneWithPunctuation.\nText before @tag. And After.\n@hi. Hello\nhi Hello"
  },
  {
    "path": "tests/data/journals/multiline.journal",
    "content": "[2013-06-09 15:39] Multiple line entry.\nThis is the first line.\nThis line doesn't have any ending punctuation\n\nThere is a blank line above this.\n"
  },
  {
    "path": "tests/data/journals/simple.journal",
    "content": "[2013-06-09 15:39] My first entry.\nEverything is alright\n\n[2013-06-10 15:40] Life is good.\nBut I'm better.\n"
  },
  {
    "path": "tests/data/journals/simple_jrnl-1-9-5.journal",
    "content": "2010-06-10 15:00 A life without chocolate is like a bad analogy.\n\n2013-06-10 15:40 He said \"[this] is the best time to be alive\".\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada\nquis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque\naugue et venenatis facilisis.\n\n[2019-08-03 12:55] Some chat log or something\n\nSuspendisse potenti. Sed dignissim sed nisl eu consequat. Aenean ante ex,\nelementum ut interdum et, mattis eget lacus. In commodo nulla nec tellus\nplacerat, sed ultricies metus bibendum. Duis eget venenatis erat. In at dolor\ndui.\n"
  },
  {
    "path": "tests/data/journals/simple_jrnl-1-9-5_little_endian_dates.journal",
    "content": "10.06.2010 15:00 A life without chocolate is like a bad analogy.\n\n10.06.2013 15:40 He said \"[this] is the best time to be alive\".\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada\nquis est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque\naugue et venenatis facilisis.\n\n[03.08.2019 12:55] Some chat log or something\n\nSuspendisse potenti. Sed dignissim sed nisl eu consequat. Aenean ante ex,\nelementum ut interdum et, mattis eget lacus. In commodo nulla nec tellus\nplacerat, sed ultricies metus bibendum. Duis eget venenatis erat. In at dolor\ndui.\n"
  },
  {
    "path": "tests/data/journals/tags-216.journal",
    "content": "[2013-06-10 15:40] I programmed for @OS/2.\nAlmost makes me want to go back to @C++, though. (Still better than @C#).\n"
  },
  {
    "path": "tests/data/journals/tags-237.journal",
    "content": "[2014-07-22 11:11] This entry has an email.\n@Newline tag should show as a tag.\nKyla's @email is kyla@clevelandunderdog.org and Guinness's is guinness@fortheloveofpits.org.\n"
  },
  {
    "path": "tests/data/journals/tags.journal",
    "content": "[2013-04-09 15:39] I have an @idea:\n(1) write a command line @journal software\n(2) ???\n(3) PROFIT!\n\n[2013-06-10 15:40] I met with @dan.\nAs alway's he shared his latest @idea on how to rule the world with me.\ninst\n"
  },
  {
    "path": "tests/data/journals/unreadabledates.journal",
    "content": "[ashasd7zdskhz7asdkjasd] Entry subject\nI've lost track of time.\n\n[sadfhakjsdf88sdf7sdff] Entry subject\nTime has no meaning.\n"
  },
  {
    "path": "tests/data/journals/work.journal",
    "content": ""
  },
  {
    "path": "tests/data/templates/basic.template",
    "content": "This text is in the basic template\r\n"
  },
  {
    "path": "tests/data/templates/sample.template",
    "content": "---\nextension: txt\n---\n\n{% block journal %}\n{% for entry in entries %}\n{% include entry %}\n{% endfor %}\n\n{% endblock %}\n\n{% block entry %}\n{{ entry.title }}\n{{ \"-\" * len(entry.title) }}\n\n{{ entry.body }}\n\n{% endblock %}\n`\n"
  },
  {
    "path": "tests/lib/fixtures.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport os\nimport sys\nimport tempfile\nfrom collections import defaultdict\nfrom collections.abc import Iterable\nfrom pathlib import Path\nfrom unittest.mock import Mock\nfrom unittest.mock import patch\n\nfrom keyring import backend\nfrom keyring import errors\nfrom pytest import fixture\nfrom rich.console import Console\n\nfrom jrnl.config import load_config\nfrom jrnl.os_compat import split_args\nfrom tests.lib.helpers import get_fixture\n\n\n# --- Keyring --- #\n@fixture\ndef keyring():\n    return NoKeyring()\n\n\n@fixture\ndef keyring_type():\n    return \"default\"\n\n\nclass TestKeyring(backend.KeyringBackend):\n    \"\"\"A test keyring that just stores its values in a hash\"\"\"\n\n    priority = 1\n    keys = defaultdict(dict)\n\n    def set_password(self, servicename, username, password):\n        self.keys[servicename][username] = password\n\n    def get_password(self, servicename, username):\n        return self.keys[servicename].get(username)\n\n    def delete_password(self, servicename, username):\n        self.keys[servicename][username] = None\n\n\nclass NoKeyring(backend.KeyringBackend):\n    \"\"\"A keyring that simulated an environment with no keyring backend.\"\"\"\n\n    priority = 2\n    keys = defaultdict(dict)\n\n    def set_password(self, servicename, username, password):\n        raise errors.NoKeyringError\n\n    def get_password(self, servicename, username):\n        raise errors.NoKeyringError\n\n    def delete_password(self, servicename, username):\n        raise errors.NoKeyringError\n\n\nclass FailedKeyring(backend.KeyringBackend):\n    \"\"\"A keyring that cannot be retrieved.\"\"\"\n\n    priority = 2\n\n    def set_password(self, servicename, username, password):\n        raise errors.KeyringError\n\n    def get_password(self, servicename, username):\n        raise errors.KeyringError\n\n    def delete_password(self, servicename, username):\n        raise errors.KeyringError\n\n\n# ----- Misc ----- #\n@fixture\ndef cli_run(\n    mock_factories,\n    mock_args,\n    mock_is_tty,\n    mock_config_path,\n    mock_editor,\n    mock_user_input,\n    mock_overrides,\n    mock_default_journal_path,\n    mock_default_templates_path,\n):\n    # Check if we need more mocks\n    mock_factories.update(mock_args)\n    mock_factories.update(mock_is_tty)\n    mock_factories.update(mock_overrides)\n    mock_factories.update(mock_editor)\n    mock_factories.update(mock_config_path)\n    mock_factories.update(mock_user_input)\n    mock_factories.update(mock_default_journal_path)\n    mock_factories.update(mock_default_templates_path)\n\n    return {\n        \"status\": 0,\n        \"stdout\": None,\n        \"stderr\": None,\n        \"mocks\": {},\n        \"mock_factories\": mock_factories,\n    }\n\n\n@fixture\ndef mock_factories():\n    return {}\n\n\n@fixture\ndef mock_args(cache_dir, request):\n    def _mock_args():\n        command = get_fixture(request, \"command\", \"\")\n\n        if cache_dir[\"exists\"]:\n            command = command.format(cache_dir=cache_dir[\"path\"])\n\n        args = split_args(command)\n\n        return patch(\"sys.argv\", [\"jrnl\"] + args)\n\n    return {\"args\": _mock_args}\n\n\n@fixture\ndef mock_is_tty(is_tty):\n    return {\"is_tty\": lambda: patch(\"sys.stdin.isatty\", return_value=is_tty)}\n\n\n@fixture\ndef mock_overrides(config_in_memory):\n    from jrnl.override import apply_overrides\n\n    def my_overrides(*args, **kwargs):\n        result = apply_overrides(*args, **kwargs)\n        config_in_memory[\"overrides\"] = result\n        return result\n\n    return {\n        \"overrides\": lambda: patch(\n            \"jrnl.controller.apply_overrides\", side_effect=my_overrides\n        )\n    }\n\n\n@fixture\ndef mock_config_path(request):\n    config_path = get_fixture(request, \"config_path\")\n\n    if not config_path:\n        return {}\n\n    return {\n        \"config_path_install\": lambda: patch(\n            \"jrnl.install.get_config_path\", return_value=config_path\n        ),\n        \"config_path_config\": lambda: patch(\n            \"jrnl.config.get_config_path\", return_value=config_path\n        ),\n    }\n\n\n@fixture\ndef mock_default_journal_path(temp_dir):\n    journal_path = os.path.join(temp_dir.name, \"journal.txt\")\n    return {\n        \"default_journal_path_install\": lambda: patch(\n            \"jrnl.install.get_default_journal_path\", return_value=journal_path\n        ),\n        \"default_journal_path_config\": lambda: patch(\n            \"jrnl.config.get_default_journal_path\", return_value=journal_path\n        ),\n    }\n\n\n@fixture\ndef mock_default_templates_path(temp_dir):\n    templates_path = os.path.join(temp_dir.name, \"templates\")\n    return {\n        \"get_templates_path\": lambda: patch(\n            \"jrnl.editor.get_templates_path\", return_value=templates_path\n        ),\n    }\n\n\n@fixture\ndef temp_dir():\n    return tempfile.TemporaryDirectory()\n\n\n@fixture\ndef working_dir(request):\n    return os.path.join(request.config.rootpath, \"tests\")\n\n\n@fixture\ndef toml_version(working_dir):\n    pyproject_path = os.path.join(working_dir, \"..\", \"pyproject.toml\")\n    if sys.version_info >= (3, 11):\n        import tomllib\n\n        with open(pyproject_path, \"rb\") as pyproject:\n            pyproject_contents = tomllib.load(pyproject)\n    else:\n        import toml\n\n        pyproject_contents = toml.load(pyproject_path)\n    return pyproject_contents[\"tool\"][\"poetry\"][\"version\"]\n\n\n@fixture\ndef input_method():\n    return \"\"\n\n\n@fixture\ndef all_input():\n    return \"\"\n\n\n@fixture\ndef command():\n    return \"\"\n\n\n@fixture\ndef cache_dir():\n    return {\"exists\": False, \"path\": \"\"}\n\n\n@fixture\ndef str_value():\n    return \"\"\n\n\n@fixture\ndef should_not():\n    return False\n\n\n@fixture\ndef mock_user_input(request, password_input, stdin_input):\n    def _mock_user_input():\n        # user_input needs to be here because we don't know it until cli_run starts\n        user_input = get_fixture(request, \"all_input\", None)\n\n        if user_input is None:\n            user_input = Exception(\"Unexpected call for user input\")\n        else:\n            user_input = iter(user_input.splitlines())\n\n        def mock_console_input(**kwargs):\n            pw = kwargs.get(\"password\", False)\n            if pw and not isinstance(password_input, Exception):\n                return password_input\n\n            if isinstance(user_input, Iterable):\n                input_line = next(user_input)\n                # A raw newline is used to indicate deliberate empty input\n                return \"\" if input_line == r\"\\n\" else input_line\n\n            # exceptions\n            return user_input if not pw else password_input\n\n        mock_console = Mock(wraps=Console(stderr=True))\n        mock_console.input = Mock(side_effect=mock_console_input)\n\n        return patch(\"jrnl.output._get_console\", return_value=mock_console)\n\n    return {\n        \"user_input\": _mock_user_input,\n        \"stdin_input\": lambda: patch(\"sys.stdin.read\", side_effect=stdin_input),\n    }\n\n\n@fixture\ndef password_input(request):\n    password_input = get_fixture(request, \"password\", None)\n    if password_input is None:\n        password_input = Exception(\"Unexpected call for password input\")\n    return password_input\n\n\n@fixture\ndef stdin_input(request, is_tty):\n    stdin_input = get_fixture(request, \"all_input\", None)\n    if stdin_input is None or is_tty:\n        stdin_input = Exception(\"Unexpected call for stdin input\")\n    else:\n        stdin_input = [stdin_input]\n    return stdin_input\n\n\n@fixture\ndef is_tty(input_method):\n    assert input_method in [\"\", \"enter\", \"pipe\", \"type\"]\n    return input_method not in [\"pipe\", \"type\"]\n\n\n@fixture\ndef config_on_disk(config_path):\n    return load_config(config_path)\n\n\n@fixture\ndef config_in_memory():\n    return dict()\n\n\n@fixture\ndef journal_name():\n    return None\n\n\n@fixture\ndef which_output_stream():\n    return None\n\n\n@fixture\ndef editor_input():\n    return None\n\n\n@fixture\ndef num_args():\n    return None\n\n\n@fixture\ndef parsed_output():\n    return {\"lang\": None, \"obj\": None}\n\n\n@fixture\ndef editor_state():\n    return {\n        \"command\": \"\",\n        \"intent\": {\"method\": \"r\", \"input\": None},\n        \"tmpfile\": {\"name\": None, \"content\": None},\n    }\n\n\n@fixture\ndef mock_editor(editor_state):\n    def _mock_editor(editor_command):\n        tmpfile = editor_command[-1]\n\n        editor_state[\"command\"] = editor_command\n        editor_state[\"tmpfile\"][\"name\"] = tmpfile\n\n        Path(tmpfile).touch()\n        with open(tmpfile, editor_state[\"intent\"][\"method\"]) as f:\n            # Touch the file so jrnl knows it was edited\n            if editor_state[\"intent\"][\"input\"] is not None:\n                f.write(editor_state[\"intent\"][\"input\"])\n\n            file_content = f.read()\n            editor_state[\"tmpfile\"][\"content\"] = file_content\n\n    return {\"editor\": lambda: patch(\"subprocess.call\", side_effect=_mock_editor)}\n"
  },
  {
    "path": "tests/lib/given_steps.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport json\nimport os\nimport random\nimport shutil\nimport string\nfrom datetime import datetime\nfrom unittest.mock import MagicMock\nfrom unittest.mock import patch\nfrom xml.etree import ElementTree as ET\n\nfrom pytest_bdd import given\nfrom pytest_bdd.parsers import parse\nfrom pytest_bdd.parsers import re\n\nfrom jrnl import __version__\nfrom jrnl.time import __get_pdt_calendar\nfrom tests.lib.fixtures import FailedKeyring\nfrom tests.lib.fixtures import NoKeyring\nfrom tests.lib.fixtures import TestKeyring\n\n\n@given(re(r\"we (?P<editor_method>\\w+) to the editor if opened\"))\ndef we_enter_editor_docstring(editor_method, editor_state, docstring):\n    we_enter_editor(editor_method, docstring, editor_state)\n\n\n@given(parse(\"we {editor_method} nothing to the editor if opened\"))\ndef we_enter_editor(editor_method, editor_input, editor_state):\n    file_method = editor_state[\"intent\"][\"method\"]\n    if editor_method == \"write\":\n        file_method = \"w+\"\n    elif editor_method == \"append\":\n        file_method = \"a+\"\n    else:\n        assert False, f\"Method '{editor_method}' not supported\"\n\n    editor_state[\"intent\"] = {\"method\": file_method, \"input\": editor_input}\n\n\n@given(parse('now is \"{date_str}\"'))\ndef now_is_str(date_str, mock_factories):\n    class DatetimeMagicMock(MagicMock):\n        # needed because jrnl does some reflection on datetime\n        def __instancecheck__(self, subclass):\n            return isinstance(subclass, datetime)\n\n    def mocked_now(tz=None):\n        now = datetime.strptime(date_str, \"%Y-%m-%d %I:%M:%S %p\")\n\n        if tz:\n            time_zone = datetime.utcnow().astimezone().tzinfo\n            now = now.replace(tzinfo=time_zone)\n\n        return now\n\n    # jrnl uses two different classes to parse dates, so both must be mocked\n    datetime_mock = DatetimeMagicMock(wraps=datetime)\n    datetime_mock.now.side_effect = mocked_now\n\n    pdt = __get_pdt_calendar()\n    calendar_mock = MagicMock(wraps=pdt)\n    calendar_mock.parse.side_effect = lambda date_str_input: pdt.parse(\n        date_str_input, mocked_now()\n    )\n\n    mock_factories[\"datetime\"] = lambda: patch(\"datetime.datetime\", new=datetime_mock)\n    mock_factories[\"calendar_parse\"] = lambda: patch(\n        \"jrnl.time.__get_pdt_calendar\", return_value=calendar_mock\n    )\n\n\n@given(\"we don't have a keyring\", target_fixture=\"keyring\")\ndef we_dont_have_keyring(keyring_type):\n    return NoKeyring()\n\n\n@given(\"we have a keyring\", target_fixture=\"keyring\")\n@given(parse(\"we have a {keyring_type} keyring\"), target_fixture=\"keyring\")\ndef we_have_type_of_keyring(keyring_type):\n    match keyring_type:\n        case \"failed\":\n            return FailedKeyring()\n\n        case _:\n            return TestKeyring()\n\n\n@given(parse(\"we use no config\"), target_fixture=\"config_path\")\ndef we_use_no_config(temp_dir):\n    os.chdir(temp_dir.name)  # @todo move this step to a more universal place\n    return os.path.join(temp_dir.name, \"non_existing_config.yaml\")\n\n\n@given(parse('we use the config \"{config_file}\"'), target_fixture=\"config_path\")\ndef we_use_the_config(request, temp_dir, working_dir, config_file):\n    # Move into temp dir as cwd\n    os.chdir(temp_dir.name)  # @todo move this step to a more universal place\n\n    # Copy the config file over\n    config_source = os.path.join(working_dir, \"data\", \"configs\", config_file)\n    config_dest = os.path.join(temp_dir.name, config_file)\n    shutil.copy2(config_source, config_dest)\n\n    # @todo make this only copy some journals over\n    # Copy all of the journals over\n    journal_source = os.path.join(working_dir, \"data\", \"journals\")\n    journal_dest = os.path.join(temp_dir.name, \"features\", \"journals\")\n    shutil.copytree(journal_source, journal_dest)\n\n    # @todo maybe only copy needed templates over?\n    # Copy all of the templates over\n    template_source = os.path.join(working_dir, \"data\", \"templates\")\n    template_dest = os.path.join(temp_dir.name, \"features\", \"templates\")\n    shutil.copytree(template_source, template_dest)\n\n    # @todo get rid of this by using default config values\n    # merge in version number\n    if (\n        config_file.endswith(\"yaml\")\n        and os.path.exists(config_dest)\n        and os.path.getsize(config_dest) > 0\n    ):\n        # Add jrnl version to file for 2.x journals\n        with open(config_dest, \"a\") as cf:\n            cf.write(\"version: {}\".format(__version__))\n\n    return config_dest\n\n\n@given(\n    parse('we copy the template \"{template_file}\" to the default templates folder'),\n    target_fixture=\"default_templates_path\",\n)\ndef we_copy_the_template(request, temp_dir, working_dir, template_file):\n    # Move into temp dir as cwd\n    os.chdir(temp_dir.name)  # @todo move this step to a more universal place\n\n    # Copy template over\n    template_source = os.path.join(working_dir, \"data\", \"templates\", template_file)\n    template_dest = os.path.join(temp_dir.name, \"templates\", template_file)\n    os.makedirs(os.path.dirname(template_dest), exist_ok=True)\n    shutil.copy2(template_source, template_dest)\n    return template_dest\n\n\n@given(parse('the config \"{config_file}\" exists'), target_fixture=\"config_path\")\ndef config_exists(config_file, temp_dir, working_dir):\n    config_source = os.path.join(working_dir, \"data\", \"configs\", config_file)\n    config_dest = os.path.join(temp_dir.name, config_file)\n    shutil.copy2(config_source, config_dest)\n\n\n@given(parse('we use the password \"{password}\" if prompted'), target_fixture=\"password\")\ndef use_password_forever(password):\n    return password\n\n\n@given(\"we create a cache directory\", target_fixture=\"cache_dir\")\ndef create_cache_dir(temp_dir):\n    random_str = \"\".join(random.choices(string.ascii_uppercase + string.digits, k=20))\n\n    dir_path = os.path.join(temp_dir.name, \"cache_\" + random_str)\n    os.mkdir(dir_path)\n    return {\"exists\": True, \"path\": dir_path}\n\n\n@given(parse(\"we parse the output as {language_name}\"), target_fixture=\"parsed_output\")\ndef parse_output_as_language(cli_run, language_name):\n    language_name = language_name.upper()\n    actual_output = cli_run[\"stdout\"]\n\n    if language_name == \"XML\":\n        parsed_output = ET.fromstring(actual_output)\n    elif language_name == \"JSON\":\n        parsed_output = json.loads(actual_output)\n    else:\n        assert False, f\"Language name {language_name} not recognized\"\n\n    return {\"lang\": language_name, \"obj\": parsed_output}\n\n\n@given(parse('the home directory is called \"{home_dir}\"'))\ndef home_directory(temp_dir, home_dir, monkeypatch):\n    home_path = os.path.join(temp_dir.name, home_dir)\n    monkeypatch.setenv(\"USERPROFILE\", home_path)  # for windows\n    monkeypatch.setenv(\"HOME\", home_path)  # for *nix\n"
  },
  {
    "path": "tests/lib/helpers.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport functools\nimport os\n\n\ndef does_directory_contain_files(file_list, directory_path):\n    if not os.path.isdir(directory_path):\n        return False\n\n    for file in file_list.split(\"\\n\"):\n        fullpath = directory_path + \"/\" + file\n        if not os.path.isfile(fullpath):\n            return False\n\n    return True\n\n\ndef does_directory_contain_n_files(directory_path, number):\n    count = 0\n    if not os.path.isdir(directory_path):\n        return False\n\n    files = [\n        f\n        for f in os.listdir(directory_path)\n        if os.path.isfile(os.path.join(directory_path, f))\n    ]\n    count = len(files)\n\n    return int(number) == count\n\n\ndef assert_equal_tags_ignoring_order(\n    actual_line, expected_line, actual_content, expected_content\n):\n    actual_tags = set(tag.strip() for tag in actual_line[len(\"tags: \") :].split(\",\"))\n    expected_tags = set(\n        tag.strip() for tag in expected_line[len(\"tags: \") :].split(\",\")\n    )\n    assert actual_tags == expected_tags, [\n        [actual_tags, expected_tags],\n        [expected_content, actual_content],\n    ]\n\n\n# @see: https://stackoverflow.com/a/65782539/569146\ndef get_nested_val(dictionary, path, *default):\n    try:\n        return functools.reduce(lambda x, y: x[y], path.split(\".\"), dictionary)\n    except KeyError:\n        if default:\n            return default[0]\n        raise\n\n\n# @see: https://stackoverflow.com/a/41599695/569146\ndef spy_wrapper(wrapped_function):\n    from unittest import mock\n\n    mock = mock.MagicMock()\n\n    def wrapper(self, *args, **kwargs):\n        mock(*args, **kwargs)\n        return wrapped_function(self, *args, **kwargs)\n\n    wrapper.mock = mock\n    return wrapper\n\n\ndef get_fixture(request, name, default=None):\n    try:\n        return request.getfixturevalue(name)\n    except LookupError:\n        return default\n"
  },
  {
    "path": "tests/lib/then_steps.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport json\nimport os\nimport re\nfrom xml.etree import ElementTree as ET\n\nfrom pytest_bdd import then\nfrom pytest_bdd.parsers import parse\nfrom pytest_bdd.parsers import re as pytest_bdd_parsers_re\nfrom ruamel.yaml import YAML\n\nfrom jrnl.config import scope_config\nfrom tests.lib.helpers import assert_equal_tags_ignoring_order\nfrom tests.lib.helpers import does_directory_contain_files\nfrom tests.lib.helpers import does_directory_contain_n_files\nfrom tests.lib.helpers import get_nested_val\nfrom tests.lib.type_builders import should_choice\n\nSHOULD_DICT = {\"Should\": should_choice}\n\n\n@then(\"we should get no error\")\ndef should_get_no_error(cli_run):\n    assert cli_run[\"status\"] == 0, cli_run[\"status\"]\n\n\n@then(\"we should get an error\")\ndef should_get_an_error(cli_run):\n    assert cli_run[\"status\"] != 0, cli_run[\"status\"]\n\n\n@then(parse(\"the output should match\"))\ndef output_should_match_docstring(cli_run, docstring):\n    output_should_match(docstring, cli_run)\n\n\n@then(parse('the output should match \"{regex}\"'))\ndef output_should_match(regex, cli_run):\n    out = cli_run[\"stdout\"]\n    matches = re.findall(regex, out)\n    assert matches, f\"\\nRegex didn't match:\\n{regex}\\n{str(out)}\\n{str(matches)}\"\n\n\n@then(parse(\"the output {it_should:Should} contain\", SHOULD_DICT))\n@then(\n    parse(\n        \"the {which_output_stream} output {it_should:Should} contain\",\n        SHOULD_DICT,\n    )\n)\ndef output_should_contain_docstring(which_output_stream, cli_run, it_should, docstring):\n    output_should_contain(docstring, which_output_stream, cli_run, it_should)\n\n\n@then(parse('the output {it_should:Should} contain \"{expected}\"', SHOULD_DICT))\n@then(\n    parse(\n        'the {which_output_stream} output {it_should:Should} contain \"{expected}\"',\n        SHOULD_DICT,\n    )\n)\ndef output_should_contain(expected, which_output_stream, cli_run, it_should):\n    output_str = (\n        f\"\\nEXPECTED:\\n{expected}\\n\\n\"\n        f\"ACTUAL STDOUT:\\n{cli_run['stdout']}\\n\\n\"\n        f\"ACTUAL STDERR:\\n{cli_run['stderr']}\"\n    )\n\n    assert expected\n    if which_output_stream is None:\n        assert ((expected in cli_run[\"stdout\"]) == it_should) or (\n            (expected in cli_run[\"stderr\"]) == it_should\n        ), output_str\n\n    elif which_output_stream == \"standard\":\n        assert (expected in cli_run[\"stdout\"]) == it_should, output_str\n\n    elif which_output_stream == \"error\":\n        assert (expected in cli_run[\"stderr\"]) == it_should, output_str\n\n    else:\n        assert (expected in cli_run[which_output_stream]) == it_should, output_str\n\n\n@then(parse(\"the output should not contain\"))\ndef output_should_not_contain_docstring(cli_run, docstring):\n    output_should_not_contain(docstring, cli_run)\n\n\n@then(parse('the output should not contain \"{expected_output}\"'))\ndef output_should_not_contain(expected_output, cli_run):\n    assert expected_output not in cli_run[\"stdout\"]\n\n\n@then(parse(\"the output should be\"))\ndef output_should_be_docstring(cli_run, docstring):\n    output_should_be(docstring, cli_run)\n\n\n@then(parse('the output should be \"{expected_output}\"'))\ndef output_should_be(expected_output, cli_run):\n    actual = cli_run[\"stdout\"].strip()\n    expected = expected_output.strip()\n    assert actual == expected\n\n\n@then(\"the output should be empty\")\ndef output_should_be_empty(cli_run):\n    actual = cli_run[\"stdout\"].strip()\n    assert actual == \"\"\n\n\n@then(parse('the output should contain the date \"{date}\"'))\ndef output_should_contain_date(date, cli_run):\n    assert date and date in cli_run[\"stdout\"]\n\n\n@then(\"the output should contain pyproject.toml version\")\ndef output_should_contain_version(cli_run, toml_version):\n    out = cli_run[\"stdout\"]\n    assert toml_version in out, toml_version\n\n\n@then(\"the version in the config file should be up-to-date\")\ndef config_file_version(config_on_disk, toml_version):\n    config_version = config_on_disk[\"version\"]\n    assert config_version == toml_version\n\n\n@then(parse(\"the output should be {width:d} columns wide\"))\ndef output_should_be_columns_wide(cli_run, width):\n    out = cli_run[\"stdout\"]\n    out_lines = out.splitlines()\n    for line in out_lines:\n        assert len(line) <= width\n\n\n@then(\n    parse(\n        'the default journal \"{journal_file}\" '\n        'should be in the \"{journal_dir}\" directory'\n    )\n)\ndef default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir):\n    default_journal_path = config_on_disk[\"journals\"][\"default\"][\"journal\"]\n    expected_journal_path = (\n        os.path.join(temp_dir.name, journal_file)\n        if journal_dir == \".\"\n        else os.path.join(temp_dir.name, journal_dir, journal_file)\n    )\n    # Use os.path.samefile here because both paths might not be fully expanded.\n    assert os.path.samefile(default_journal_path, expected_journal_path)\n\n\n@then(\n    parse(\n        'the config for journal \"{journal_name}\" {it_should:Should} contain',\n        SHOULD_DICT,\n    )\n)\n@then(parse(\"the config {it_should:Should} contain\", SHOULD_DICT))\ndef config_var_on_disk_docstring(config_on_disk, journal_name, it_should, docstring):\n    config_var_on_disk(config_on_disk, journal_name, it_should, docstring)\n\n\n@then(\n    parse(\n        'the config for journal \"{journal_name}\" '\n        '{it_should:Should} contain \"{some_yaml}\"',\n        SHOULD_DICT,\n    )\n)\n@then(parse('the config {it_should:Should} contain \"{some_yaml}\"', SHOULD_DICT))\ndef config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):\n    actual = config_on_disk\n    if journal_name:\n        actual = actual[\"journals\"][journal_name]\n\n    expected = YAML(typ=\"safe\").load(some_yaml)\n\n    actual_slice = actual\n    if isinstance(actual, dict):\n        # `expected` objects formatted in yaml only compare one level deep\n        actual_slice = {key: actual.get(key) for key in expected.keys()}\n\n    assert (expected == actual_slice) == it_should\n\n\n@then(\n    parse(\n        'the config in memory for journal \"{journal_name}\" '\n        \"{it_should:Should} contain\",\n        SHOULD_DICT,\n    )\n)\n@then(parse(\"the config in memory {it_should:Should} contain\", SHOULD_DICT))\ndef config_var_in_memory_docstring(\n    config_in_memory, journal_name, it_should, docstring\n):\n    config_var_in_memory(config_in_memory, journal_name, it_should, docstring)\n\n\n@then(\n    parse(\n        'the config in memory for journal \"{journal_name}\" '\n        '{it_should:Should} contain \"{some_yaml}\"',\n        SHOULD_DICT,\n    )\n)\n@then(\n    parse('the config in memory {it_should:Should} contain \"{some_yaml}\"', SHOULD_DICT)\n)\ndef config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):\n    actual = config_in_memory[\"overrides\"]\n    if journal_name:\n        actual = actual[\"journals\"][journal_name]\n\n    expected = YAML(typ=\"safe\").load(some_yaml)\n\n    actual_slice = actual\n    if isinstance(actual, dict):\n        # `expected` objects formatted in yaml only compare one level deep\n        actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()}\n\n    assert (expected == actual_slice) == it_should\n\n\n@then(\"we should be prompted for a password\")\ndef password_was_called(cli_run):\n    assert cli_run[\"mocks\"][\"user_input\"].return_value.input.called\n\n\n@then(\"we should not be prompted for a password\")\ndef password_was_not_called(cli_run):\n    assert not cli_run[\"mocks\"][\"user_input\"].return_value.input.called\n\n\n@then(parse(\"the cache directory should contain the files\"))\ndef assert_dir_contains_files(cache_dir, docstring):\n    assert does_directory_contain_files(docstring, cache_dir[\"path\"])\n\n\n@then(\n    pytest_bdd_parsers_re(r\"the cache directory should contain (?P<number>\\d+) files\")\n)\ndef assert_dir_contains_n_files(cache_dir, number):\n    assert does_directory_contain_n_files(cache_dir[\"path\"], number)\n\n\n@then(parse(\"the journal directory should contain\"))\ndef journal_directory_should_contain(config_on_disk, docstring):\n    scoped_config = scope_config(config_on_disk, \"default\")\n\n    assert does_directory_contain_files(docstring, scoped_config[\"journal\"])\n\n\n@then(parse('journal \"{journal_name}\" should not exist'))\ndef journal_directory_should_not_exist(config_on_disk, journal_name):\n    scoped_config = scope_config(config_on_disk, journal_name)\n\n    assert not does_directory_contain_files(\n        scoped_config[\"journal\"], \".\"\n    ), f'Journal \"{journal_name}\" does exist'\n\n\n@then(parse(\"the journal {it_should:Should} exist\", SHOULD_DICT))\ndef journal_should_not_exist(config_on_disk, it_should):\n    scoped_config = scope_config(config_on_disk, \"default\")\n    expected_path = scoped_config[\"journal\"]\n\n    contains_files = does_directory_contain_files(expected_path, \".\")\n\n    assert contains_files == it_should\n\n\n@then(\n    parse(\n        'the journal \"{journal_name}\" directory {it_should:Should} exist', SHOULD_DICT\n    )\n)\ndef directory_should_not_exist(config_on_disk, it_should, journal_name):\n    scoped_config = scope_config(config_on_disk, journal_name)\n    expected_path = scoped_config[\"journal\"]\n    dir_exists = os.path.isdir(expected_path)\n\n    assert dir_exists == it_should\n\n\n@then(parse('the content of file \"{file_path}\" in the cache should be'))\ndef content_of_file_should_be(file_path, cache_dir, docstring):\n    assert cache_dir[\"exists\"]\n    expected_content = docstring.strip().splitlines()\n\n    with open(os.path.join(cache_dir[\"path\"], file_path), \"r\") as f:\n        actual_content = f.read().strip().splitlines()\n\n    for actual_line, expected_line in zip(actual_content, expected_content):\n        if actual_line.startswith(\"tags: \") and expected_line.startswith(\"tags: \"):\n            assert_equal_tags_ignoring_order(\n                actual_line, expected_line, actual_content, expected_content\n            )\n        else:\n            assert actual_line.strip() == expected_line.strip(), [\n                [actual_line.strip(), expected_line.strip()],\n                [actual_content, expected_content],\n            ]\n\n\n@then(parse(\"the cache should contain the files\"))\ndef cache_dir_contains_files(cache_dir, docstring):\n    assert cache_dir[\"exists\"]\n\n    actual_files = os.listdir(cache_dir[\"path\"])\n    expected_files = docstring.split(\"\\n\")\n\n    # sort to deal with inconsistent default file ordering on different OS's\n    actual_files.sort()\n    expected_files.sort()\n\n    assert actual_files == expected_files, [actual_files, expected_files]\n\n\n@then(parse(\"the output should be valid {language_name}\"))\ndef assert_output_is_valid_language(cli_run, language_name):\n    language_name = language_name.upper()\n    if language_name == \"XML\":\n        xml_tree = ET.fromstring(cli_run[\"stdout\"])\n        assert xml_tree, \"Invalid XML\"\n    elif language_name == \"JSON\":\n        assert json.loads(cli_run[\"stdout\"]), \"Invalid JSON\"\n    else:\n        assert False, f\"Language name {language_name} not recognized\"\n\n\n@then(parse('\"{node_name}\" in the parsed output should have {number:d} elements'))\ndef assert_parsed_output_item_count(node_name, number, parsed_output):\n    lang = parsed_output[\"lang\"]\n    obj = parsed_output[\"obj\"]\n\n    if lang == \"XML\":\n        xml_node_names = (node.tag for node in obj)\n        assert node_name in xml_node_names, str(list(xml_node_names))\n\n        actual_entry_count = len(obj.find(node_name))\n        assert actual_entry_count == number, actual_entry_count\n\n    elif lang == \"JSON\":\n        my_obj = obj\n\n        for node in node_name.split(\".\"):\n            try:\n                my_obj = my_obj[int(node)]\n            except ValueError:\n                assert node in my_obj\n                my_obj = my_obj[node]\n\n        assert len(my_obj) == number, len(my_obj)\n\n    else:\n        assert False, f\"Language name {lang} not recognized\"\n\n\n@then(parse('\"{field_name}\" in the parsed output should {comparison}'))\ndef assert_output_field_content(field_name, comparison, parsed_output, docstring):\n    lang = parsed_output[\"lang\"]\n    obj = parsed_output[\"obj\"]\n    expected_keys = docstring.split(\"\\n\")\n    if len(expected_keys) == 1:\n        expected_keys = expected_keys[0]\n\n    if lang == \"XML\":\n        xml_node_names = (node.tag for node in obj)\n        assert field_name in xml_node_names, str(list(xml_node_names))\n\n        if field_name == \"tags\":\n            actual_tags = set(t.attrib[\"name\"] for t in obj.find(\"tags\"))\n            assert set(actual_tags) == set(expected_keys), [\n                actual_tags,\n                set(expected_keys),\n            ]\n        else:\n            assert False, \"This test only works for tags in XML\"\n\n    elif lang == \"JSON\":\n        my_obj = obj\n\n        for node in field_name.split(\".\"):\n            try:\n                my_obj = my_obj[int(node)]\n            except ValueError:\n                assert node in my_obj, [my_obj.keys(), node]\n                my_obj = my_obj[node]\n\n        if comparison == \"be\":\n            if isinstance(my_obj, str):\n                assert expected_keys == my_obj, [my_obj, expected_keys]\n            else:\n                assert set(expected_keys) == set(my_obj), [\n                    set(my_obj),\n                    set(expected_keys),\n                ]\n        elif comparison == \"contain\":\n            if isinstance(my_obj, str):\n                assert expected_keys in my_obj, [my_obj, expected_keys]\n            else:\n                assert all(elem in my_obj for elem in expected_keys), [\n                    my_obj,\n                    expected_keys,\n                ]\n    else:\n        assert False, f\"Language name {lang} not recognized\"\n\n\n@then(parse('there should be {number:d} \"{item}\" elements'))\ndef count_elements(number, item, cli_run):\n    actual_output = cli_run[\"stdout\"]\n    xml_tree = ET.fromstring(actual_output)\n    assert len(xml_tree.findall(\".//\" + item)) == number\n\n\n@then(parse(\"the editor {it_should:Should} have been called\", SHOULD_DICT))\n@then(\n    parse(\n        \"the editor {it_should:Should} have been called with {num_args} arguments\",\n        SHOULD_DICT,\n    )\n)\ndef count_editor_args(num_args, cli_run, editor_state, it_should):\n    assert cli_run[\"mocks\"][\"editor\"].called == it_should\n\n    if isinstance(num_args, int):\n        assert len(editor_state[\"command\"]) == int(num_args)\n\n\n@then(parse(\"the stdin prompt {it_should:Should} have been called\", SHOULD_DICT))\ndef stdin_prompt_called(cli_run, it_should):\n    assert cli_run[\"mocks\"][\"stdin_input\"].called == it_should\n\n\n@then(parse('the editor filename should end with \"{suffix}\"'))\ndef editor_filename_suffix(suffix, editor_state):\n    editor_filename = editor_state[\"tmpfile\"][\"name\"]\n\n    assert editor_state[\"tmpfile\"][\"name\"].endswith(suffix), (editor_filename, suffix)\n\n\n@then(parse(\"the editor file content should {comparison}\"))\ndef contains_editor_file_docstring(comparison, editor_state, docstring):\n    contains_editor_file(comparison, docstring, editor_state)\n\n\n@then(parse('the editor file content should {comparison} \"{str_value}\"'))\n@then(parse(\"the editor file content should {comparison} empty\"))\ndef contains_editor_file(comparison, str_value, editor_state):\n    content = editor_state[\"tmpfile\"][\"content\"]\n    # content = f'\\n\"\"\"\\n{content}\\n\"\"\"\\n'\n    if comparison == \"be\":\n        assert content == str_value\n    elif comparison == \"contain\":\n        assert str_value in content\n    else:\n        assert False, f\"Comparison '{comparison}' not supported\"\n"
  },
  {
    "path": "tests/lib/type_builders.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom parse_type import TypeBuilder\n\nshould_choice = TypeBuilder.make_enum(\n    {\n        \"should\": True,\n        \"should not\": False,\n    }\n)\n"
  },
  {
    "path": "tests/lib/when_steps.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport os\nfrom contextlib import ExitStack\n\nfrom pytest_bdd import when\nfrom pytest_bdd.parsers import parse\nfrom pytest_bdd.parsers import re\n\n# This is an undocumented and unsupported function:\n# https://github.com/pytest-dev/pytest-bdd/issues/684\ntry:\n    from pytest_bdd.compat import inject_fixture  # pytest_bdd 7.1.2 and later\nexcept ImportError:\n    from pytest_bdd.steps import inject_fixture  # pytest_bdd 7.1.1 and earlier\n\nfrom jrnl.main import run\n\n\n@when(parse('we change directory to \"{directory_name}\"'))\ndef when_we_change_directory(directory_name):\n    if not os.path.isdir(directory_name):\n        os.mkdir(directory_name)\n\n    os.chdir(directory_name)\n\n\n# These variables are used in the `@when(re(...))` section below\ncommand = '(?P<command>[^\"]*)'\ninput_method = \"(?P<input_method>enter|pipe|type)\"\nall_input = '(\"(?P<all_input>[^\"]*)\")'\n# Note: A line with only a raw newline r'\\n' is treated as\n# an empty line of input internally for testing purposes.\n\n\n@when(re(f'we run \"jrnl {command}\" and {input_method}'))\ndef we_run_jrnl_docstring(capsys, keyring, request, command, input_method, docstring):\n    we_run_jrnl(capsys, keyring, request, command, input_method, docstring)\n\n\n@when(re(f'we run \"jrnl ?{command}\" and {input_method} {all_input}'))\n@when(re(f'we run \"jrnl {command}\"(?! and)'))\n@when('we run \"jrnl\"')\ndef we_run_jrnl(capsys, keyring, request, command, input_method, all_input):\n    from keyring import set_keyring\n\n    set_keyring(keyring)\n\n    # fixture injection (pytest-bdd >=6.0)\n    inject_fixture(request, \"command\", command)\n    inject_fixture(request, \"input_method\", input_method)\n    inject_fixture(request, \"all_input\", all_input)\n\n    cli_run = request.getfixturevalue(\"cli_run\")\n\n    with ExitStack() as stack:\n        mocks = cli_run[\"mocks\"]\n        factories = cli_run[\"mock_factories\"]\n\n        for id in factories:\n            mocks[id] = stack.enter_context(factories[id]())\n\n        try:\n            cli_run[\"status\"] = run() or 0\n        except StopIteration:\n            # This happens when input is expected, but don't have any input left\n            pass\n        except SystemExit as e:\n            cli_run[\"status\"] = e.code\n\n    captured = capsys.readouterr()\n\n    cli_run[\"stdout\"] = captured.out\n    cli_run[\"stderr\"] = captured.err\n"
  },
  {
    "path": "tests/unit/test_color.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport pytest\nfrom colorama import Fore\nfrom colorama import Style\n\nfrom jrnl.color import colorize\n\n\n@pytest.fixture()\ndef data_fixture():\n    string = \"Zwei peanuts walked into a bar\"\n    yield string\n\n\ndef test_colorize(data_fixture):\n    string = data_fixture\n    colorized_string = colorize(string, \"BLUE\", True)\n\n    assert colorized_string == Style.BRIGHT + Fore.BLUE + string + Style.RESET_ALL\n"
  },
  {
    "path": "tests/unit/test_config_file.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport os\n\nimport pytest\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.install import find_alt_config\n\n\ndef test_find_alt_config(request):\n    work_config_path = os.path.join(\n        request.fspath.dirname, \"..\", \"data\", \"configs\", \"basic_onefile.yaml\"\n    )\n    found_alt_config = find_alt_config(work_config_path)\n    assert found_alt_config == work_config_path\n\n\ndef test_find_alt_config_not_exist(request):\n    bad_config_path = os.path.join(\n        request.fspath.dirname, \"..\", \"data\", \"configs\", \"does-not-exist.yaml\"\n    )\n    with pytest.raises(JrnlException) as ex:\n        found_alt_config = find_alt_config(bad_config_path)\n        assert found_alt_config is not None\n    assert isinstance(ex.value, JrnlException)\n"
  },
  {
    "path": "tests/unit/test_controller.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport random\nimport string\nfrom unittest import mock\n\nimport pytest\n\nimport jrnl\nfrom jrnl.args import parse_args\nfrom jrnl.controller import _display_search_results\n\n\n@pytest.fixture\ndef random_string():\n    return \"\".join(random.choices(string.ascii_uppercase + string.digits, k=25))\n\n\n@pytest.mark.parametrize(\"export_format\", [\"pretty\", \"short\"])\ndef test_display_search_results_pretty_short(export_format):\n    mock_args = parse_args([\"--format\", export_format])\n\n    test_journal = jrnl.journals.Journal()\n    test_journal.new_entry(\"asdf\")\n\n    test_journal.pprint = mock.Mock()\n\n    _display_search_results(mock_args, test_journal)\n\n    test_journal.pprint.assert_called_once()\n\n\n@pytest.mark.parametrize(\n    \"export_format\", [\"markdown\", \"json\", \"xml\", \"yaml\", \"fancy\", \"dates\"]\n)\n@mock.patch(\"jrnl.plugins.get_exporter\")\n@mock.patch(\"builtins.print\")\ndef test_display_search_results_builtin_plugins(\n    mock_print, mock_exporter, export_format, random_string\n):\n    test_filename = random_string\n    mock_args = parse_args([\"--format\", export_format, \"--file\", test_filename])\n\n    test_journal = jrnl.journals.Journal()\n    test_journal.new_entry(\"asdf\")\n\n    mock_export = mock.Mock()\n    mock_exporter.return_value.export = mock_export\n\n    _display_search_results(mock_args, test_journal)\n\n    mock_exporter.assert_called_once_with(export_format)\n    mock_export.assert_called_once_with(test_journal, test_filename)\n    mock_print.assert_called_once_with(mock_export.return_value)\n"
  },
  {
    "path": "tests/unit/test_editor.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\r\n# License: https://www.gnu.org/licenses/gpl-3.0.html\r\n\r\nimport os\r\nfrom unittest.mock import mock_open\r\nfrom unittest.mock import patch\r\n\r\nimport pytest\r\n\r\nfrom jrnl.editor import get_template_path\r\nfrom jrnl.editor import read_template_file\r\nfrom jrnl.exception import JrnlException\r\n\r\n\r\n@patch(\r\n    \"os.getcwd\", side_effect=\"/\"\r\n)  # prevent failures in CI if current directory has been deleted\r\n@patch(\"builtins.open\", side_effect=FileNotFoundError())\r\ndef test_read_template_file_with_no_file_raises_exception(mock_open, mock_getcwd):\r\n    with pytest.raises(JrnlException) as ex:\r\n        read_template_file(\"invalid_file.txt\")\r\n        assert isinstance(ex.value, JrnlException)\r\n\r\n\r\n@patch(\r\n    \"os.getcwd\", side_effect=\"/\"\r\n)  # prevent failures in CI if current directory has been deleted\r\n@patch(\"builtins.open\", new_callable=mock_open, read_data=\"template text\")\r\ndef test_read_template_file_with_valid_file_returns_text(mock_file, mock_getcwd):\r\n    assert read_template_file(\"valid_file.txt\") == \"template text\"\r\n\r\n\r\ndef test_get_template_path_when_exists_returns_correct_path():\r\n    with patch(\"os.path.exists\", return_value=True):\r\n        output = get_template_path(\"template\", \"templatepath\")\r\n\r\n    assert output == os.path.join(\"templatepath\", \"template\")\r\n\r\n\r\n@patch(\"jrnl.editor.absolute_path\")\r\ndef test_get_template_path_when_doesnt_exist_returns_correct_path(mock_absolute_paths):\r\n    with patch(\"os.path.exists\", return_value=False):\r\n        output = get_template_path(\"template\", \"templatepath\")\r\n\r\n    assert output == mock_absolute_paths.return_value\r\n"
  },
  {
    "path": "tests/unit/test_export.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom unittest import mock\n\nimport pytest\n\nfrom jrnl.exception import JrnlException\nfrom jrnl.plugins.fancy_exporter import check_provided_linewrap_viability\nfrom jrnl.plugins.yaml_exporter import YAMLExporter\n\n\n@pytest.fixture()\ndef datestr():\n    yield \"2020-10-20 16:59\"\n\n\ndef build_card_header(datestr):\n    top_left_corner = \"┎─╮\"\n    content = top_left_corner + datestr\n    return content\n\n\nclass TestFancy:\n    def test_too_small_linewrap(self, datestr):\n        journal = \"test_journal\"\n        content = build_card_header(datestr)\n\n        total_linewrap = 12\n\n        with pytest.raises(JrnlException):\n            check_provided_linewrap_viability(total_linewrap, [content], journal)\n\n\nclass TestYaml:\n    @mock.patch(\"builtins.open\")\n    def test_export_to_nonexisting_folder(self, mock_open):\n        with pytest.raises(JrnlException):\n            YAMLExporter.write_file(\"journal\", \"non-existing-path\")\n        mock_open.assert_not_called()\n"
  },
  {
    "path": "tests/unit/test_install.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport sys\nfrom unittest import mock\n\nimport pytest\n\n\n@pytest.mark.filterwarnings(\n    \"ignore:.*imp module is deprecated.*\"\n)  # ansiwrap spits out an unrelated warning\ndef test_initialize_autocomplete_runs_without_readline():\n    from jrnl import install\n\n    with mock.patch.dict(sys.modules, {\"readline\": None}):\n        install._initialize_autocomplete()  # should not throw exception\n"
  },
  {
    "path": "tests/unit/test_journals_folder_journal.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport pathlib\nfrom unittest import mock\n\nimport pytest\n\nfrom jrnl.journals.FolderJournal import Folder\n\n\n@pytest.mark.parametrize(\n    \"inputs_and_outputs\",\n    [\n        [\n            \"/2020/01\",\n            [\"02.txt\", \"03.txt\", \"31.txt\"],\n            [\"/2020/01/02.txt\", \"/2020/01/03.txt\", \"/2020/01/31.txt\"],\n        ],\n        [\n            \"/2020/02\",  # leap year\n            [\"02.txt\", \"03.txt\", \"28.txt\", \"29.txt\", \"31.txt\", \"39.txt\"],\n            [\n                \"/2020/02/02.txt\",\n                \"/2020/02/03.txt\",\n                \"/2020/02/28.txt\",\n                \"/2020/02/29.txt\",\n            ],\n        ],\n        [\n            \"/2100/02\",  # not a leap year\n            [\"01.txt\", \"28.txt\", \"29.txt\", \"39.txt\"],\n            [\"/2100/02/01.txt\", \"/2100/02/28.txt\"],\n        ],\n        [\n            \"/2023/04\",\n            [\"29.txt\", \"30.txt\", \"31.txt\", \"39.txt\"],\n            [\"/2023/04/29.txt\", \"/2023/04/30.txt\"],\n        ],\n    ],\n)\ndef test_get_day_files_expected_filtering(inputs_and_outputs):\n    year_month_path, glob_filenames, expected_output = inputs_and_outputs\n\n    year_month_path = pathlib.Path(year_month_path)\n\n    glob_files = map(lambda x: year_month_path / x, glob_filenames)\n    expected_output = list(map(lambda x: str(pathlib.PurePath(x)), expected_output))\n\n    with (\n        mock.patch(\"pathlib.Path.glob\", return_value=glob_files),\n        mock.patch.object(pathlib.Path, \"is_file\", return_value=True),\n    ):\n        actual_output = list(Folder._get_day_files(year_month_path))\n        actual_output.sort()\n\n        expected_output.sort()\n\n        assert actual_output == expected_output\n"
  },
  {
    "path": "tests/unit/test_os_compat.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom unittest import mock\n\nimport pytest\n\nfrom jrnl.os_compat import on_posix\nfrom jrnl.os_compat import on_windows\nfrom jrnl.os_compat import split_args\n\n\n@pytest.mark.parametrize(\n    \"systems\",\n    [\n        [\"linux\", False],\n        [\"win32\", True],\n        [\"cygwin\", False],\n        [\"msys\", False],\n        [\"darwin\", False],\n        [\"os2\", False],\n        [\"os2emx\", False],\n        [\"riscos\", False],\n        [\"atheos\", False],\n        [\"freebsd7\", False],\n        [\"freebsd8\", False],\n        [\"freebsdN\", False],\n        [\"openbsd6\", False],\n    ],\n)\ndef test_on_windows(systems):\n    osname, expected_on_windows = systems[0], systems[1]\n    with mock.patch(\"jrnl.os_compat.platform\", osname):\n        assert on_windows() == expected_on_windows\n\n\n@pytest.mark.parametrize(\n    \"systems\",\n    [\n        [\"linux\", True],\n        [\"win32\", False],\n        [\"cygwin\", True],\n        [\"msys\", True],\n        [\"darwin\", True],\n        [\"os2\", True],\n        [\"os2emx\", True],\n        [\"riscos\", True],\n        [\"atheos\", True],\n        [\"freebsd7\", True],\n        [\"freebsd8\", True],\n        [\"freebsdN\", True],\n        [\"openbsd6\", True],\n    ],\n)\ndef test_on_posix(systems):\n    osname, expected_on_posix = systems[0], systems[1]\n    with mock.patch(\"jrnl.os_compat.platform\", osname):\n        assert on_posix() == expected_on_posix\n\n\n@pytest.mark.parametrize(\n    \"args\",\n    [\n        [\"notepad\", [\"notepad\"]],\n        [\"subl -w\", [\"subl\", \"-w\"]],\n        [\n            '\"C:\\\\Program Files\\\\Sublime Text 3\\\\subl.exe\" -w',\n            ['\"C:\\\\Program Files\\\\Sublime Text 3\\\\subl.exe\"', \"-w\"],\n        ],\n    ],\n)\ndef test_split_args_on_windows(args):\n    input_arguments, expected_split_args = args[0], args[1]\n    with mock.patch(\"jrnl.os_compat.on_windows\", lambda: True):\n        assert split_args(input_arguments) == expected_split_args\n\n\n@pytest.mark.parametrize(\n    \"args\",\n    [\n        [\"vim\", [\"vim\"]],\n        [\n            'vim -f +Goyo +Limelight \"+set spell linebreak\"',\n            [\"vim\", \"-f\", \"+Goyo\", \"+Limelight\", '\"+set spell linebreak\"'],\n        ],\n    ],\n)\ndef test_split_args_on_not_windows(args):\n    input_arguments, expected_split_args = args[0], args[1]\n    with mock.patch(\"jrnl.os_compat.on_windows\", lambda: True):\n        assert split_args(input_arguments) == expected_split_args\n"
  },
  {
    "path": "tests/unit/test_output.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom unittest.mock import Mock\nfrom unittest.mock import patch\n\nfrom jrnl.messages import Message\nfrom jrnl.output import print_msg\n\n\n@patch(\"jrnl.output.print_msgs\")\ndef test_print_msg_calls_print_msgs_as_list_with_style(print_msgs):\n    test_msg = Mock(Message)\n    print_msg(test_msg)\n    print_msgs.assert_called_once_with([test_msg], style=test_msg.style)\n\n\n@patch(\"jrnl.output.print_msgs\")\ndef test_print_msg_calls_print_msgs_with_kwargs(print_msgs):\n    test_msg = Mock(Message)\n    kwargs = {\n        \"delimter\": \"test delimiter 🤡\",\n        \"get_input\": True,\n        \"hide_input\": True,\n        \"some_rando_arg\": \"💩\",\n    }\n    print_msg(test_msg, **kwargs)\n    print_msgs.assert_called_once_with([test_msg], style=test_msg.style, **kwargs)\n"
  },
  {
    "path": "tests/unit/test_override.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nfrom argparse import Namespace\n\nimport pytest\n\nfrom jrnl.override import _convert_dots_to_list\nfrom jrnl.override import _get_config_node\nfrom jrnl.override import _get_key_and_value_from_pair\nfrom jrnl.override import _recursively_apply\nfrom jrnl.override import apply_overrides\n\n\n@pytest.fixture()\ndef minimal_config():\n    cfg = {\n        \"colors\": {\"body\": \"red\", \"date\": \"green\"},\n        \"default\": \"/tmp/journal.jrnl\",\n        \"editor\": \"vim\",\n        \"journals\": {\"default\": \"/tmp/journals/journal.jrnl\"},\n    }\n    return cfg\n\n\ndef expected_args(overrides):\n    default_args = {\n        \"contains\": None,\n        \"debug\": False,\n        \"delete\": False,\n        \"edit\": False,\n        \"end_date\": None,\n        \"today_in_history\": False,\n        \"month\": None,\n        \"day\": None,\n        \"year\": None,\n        \"excluded\": [],\n        \"export\": False,\n        \"filename\": None,\n        \"limit\": None,\n        \"on_date\": None,\n        \"preconfig_cmd\": None,\n        \"postconfig_cmd\": None,\n        \"short\": False,\n        \"starred\": False,\n        \"start_date\": None,\n        \"strict\": False,\n        \"tags\": False,\n        \"text\": [],\n        \"config_override\": [],\n    }\n    return Namespace(**{**default_args, **overrides})\n\n\ndef test_apply_override(minimal_config):\n    overrides = {\"config_override\": [[\"editor\", \"nano\"]]}\n    apply_overrides(expected_args(overrides), minimal_config)\n    assert minimal_config[\"editor\"] == \"nano\"\n\n\ndef test_override_dot_notation(minimal_config):\n    overrides = {\"config_override\": [[\"colors.body\", \"blue\"]]}\n    apply_overrides(expected_args(overrides), minimal_config)\n    assert minimal_config[\"colors\"] == {\"body\": \"blue\", \"date\": \"green\"}\n\n\ndef test_multiple_overrides(minimal_config):\n    overrides = {\n        \"config_override\": [\n            [\"colors.title\", \"magenta\"],\n            [\"editor\", \"nano\"],\n            [\"journals.burner\", \"/tmp/journals/burner.jrnl\"],\n        ]\n    }\n\n    actual = apply_overrides(expected_args(overrides), minimal_config)\n    assert actual[\"editor\"] == \"nano\"\n    assert actual[\"colors\"][\"title\"] == \"magenta\"\n    assert \"burner\" in actual[\"journals\"]\n    assert actual[\"journals\"][\"burner\"] == \"/tmp/journals/burner.jrnl\"\n\n\ndef test_recursively_apply():\n    cfg = {\"colors\": {\"body\": \"red\", \"title\": \"green\"}}\n    cfg = _recursively_apply(cfg, [\"colors\", \"body\"], \"blue\")\n    assert cfg[\"colors\"][\"body\"] == \"blue\"\n\n\ndef test_get_config_node(minimal_config):\n    assert len(minimal_config.keys()) == 4\n    assert _get_config_node(minimal_config, \"editor\") == \"vim\"\n    assert _get_config_node(minimal_config, \"display_format\") is None\n\n\ndef test_get_kv_from_pair():\n    pair = {\"ab.cde\": \"fgh\"}\n    k, v = _get_key_and_value_from_pair(pair)\n    assert k == \"ab.cde\"\n    assert v == \"fgh\"\n\n\nclass TestDotNotationToList:\n    def test_unpack_dots_to_list(self):\n        keys = \"a.b.c.d.e.f\"\n        keys_list = _convert_dots_to_list(keys)\n        assert len(keys_list) == 6\n\n    def test_sequential_delimiters(self):\n        k = \"g.r..h.v\"\n        k_l = _convert_dots_to_list(k)\n        assert len(k_l) == 4\n"
  },
  {
    "path": "tests/unit/test_parse_args.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport shlex\n\nimport pytest\n\nfrom jrnl.args import parse_args\nfrom jrnl.config import make_yaml_valid_dict\n\n\ndef cli_as_dict(str):\n    cli = shlex.split(str)\n    args = parse_args(cli)\n    return vars(args)\n\n\ndef expected_args(**kwargs):\n    default_args = {\n        \"contains\": None,\n        \"debug\": False,\n        \"delete\": False,\n        \"change_time\": None,\n        \"edit\": False,\n        \"end_date\": None,\n        \"exclude_starred\": False,\n        \"exclude_tagged\": False,\n        \"today_in_history\": False,\n        \"month\": None,\n        \"day\": None,\n        \"year\": None,\n        \"excluded\": [],\n        \"export\": False,\n        \"filename\": None,\n        \"limit\": None,\n        \"on_date\": None,\n        \"preconfig_cmd\": None,\n        \"postconfig_cmd\": None,\n        \"short\": False,\n        \"starred\": False,\n        \"start_date\": None,\n        \"strict\": False,\n        \"tagged\": False,\n        \"tags\": False,\n        \"template\": None,\n        \"text\": [],\n        \"config_override\": [],\n        \"config_file_path\": \"\",\n    }\n    return {**default_args, **kwargs}\n\n\ndef test_empty():\n    assert cli_as_dict(\"\") == expected_args()\n\n\ndef test_contains_alone():\n    assert cli_as_dict(\"-contains whatever\") == expected_args(contains=[\"whatever\"])\n\n\ndef test_debug_alone():\n    assert cli_as_dict(\"--debug\") == expected_args(debug=True)\n\n\ndef test_delete_alone():\n    assert cli_as_dict(\"--delete\") == expected_args(delete=True)\n\n\ndef test_change_time_alone():\n    assert cli_as_dict(\"--change-time\") == expected_args(change_time=\"now\")\n    assert cli_as_dict(\"--change-time yesterday\") == expected_args(\n        change_time=\"yesterday\"\n    )\n\n\ndef test_diagnostic_alone():\n    from jrnl.commands import preconfig_diagnostic\n\n    assert cli_as_dict(\"--diagnostic\") == expected_args(\n        preconfig_cmd=preconfig_diagnostic\n    )\n\n\ndef test_edit_alone():\n    assert cli_as_dict(\"--edit\") == expected_args(edit=True)\n\n\ndef test_encrypt_alone():\n    from jrnl.commands import postconfig_encrypt\n\n    assert cli_as_dict(\"--encrypt\") == expected_args(postconfig_cmd=postconfig_encrypt)\n\n\ndef test_decrypt_alone():\n    from jrnl.commands import postconfig_decrypt\n\n    assert cli_as_dict(\"--decrypt\") == expected_args(postconfig_cmd=postconfig_decrypt)\n\n\ndef test_end_date_alone():\n    expected = expected_args(end_date=\"2020-01-01\")\n    assert expected == cli_as_dict(\"-until 2020-01-01\")\n    assert expected == cli_as_dict(\"-to 2020-01-01\")\n\n\ndef test_not_empty():\n    with pytest.raises(SystemExit) as wrapped_e:\n        cli_as_dict(\"-not\")\n    assert wrapped_e.value.code == 2\n\n\ndef test_not_alone():\n    assert cli_as_dict(\"-not test\") == expected_args(excluded=[\"test\"])\n\n\ndef test_not_multiple_alone():\n    assert cli_as_dict(\"-not one -not two\") == expected_args(excluded=[\"one\", \"two\"])\n    assert cli_as_dict(\"-not one -not two -not three\") == expected_args(\n        excluded=[\"one\", \"two\", \"three\"]\n    )\n\n\n@pytest.mark.parametrize(\n    \"cli\",\n    [\n        \"two -not one -not three\",\n        \"-not one two -not three\",\n        \"-not one -not three two\",\n    ],\n)\ndef test_not_mixed(cli):\n    result = expected_args(excluded=[\"one\", \"three\"], text=[\"two\"])\n    assert cli_as_dict(cli) == result\n\n\ndef test_not_interspersed():\n    result = expected_args(excluded=[\"one\", \"three\"], text=[\"two\", \"two\", \"two\"])\n    assert cli_as_dict(\"two -not one two -not three two\") == result\n\n\ndef test_export_alone():\n    assert cli_as_dict(\"--export json\") == expected_args(export=\"json\")\n\n\ndef test_import_alone():\n    from jrnl.commands import postconfig_import\n\n    assert cli_as_dict(\"--import\") == expected_args(postconfig_cmd=postconfig_import)\n\n\ndef test_file_flag_alone():\n    assert cli_as_dict(\"--file test.txt\") == expected_args(filename=\"test.txt\")\n    assert cli_as_dict(\"--file 'lorem ipsum.txt'\") == expected_args(\n        filename=\"lorem ipsum.txt\"\n    )\n\n\ndef test_limit_alone():\n    assert cli_as_dict(\"-n 5\") == expected_args(limit=5)\n    assert cli_as_dict(\"-n 999\") == expected_args(limit=999)\n\n\ndef test_limit_shorthand_alone():\n    assert cli_as_dict(\"-5\") == expected_args(limit=5)\n    assert cli_as_dict(\"-999\") == expected_args(limit=999)\n\n\ndef test_list_alone():\n    from jrnl.commands import postconfig_list\n\n    assert cli_as_dict(\"--ls\") == expected_args(postconfig_cmd=postconfig_list)\n\n\ndef test_on_date_alone():\n    assert cli_as_dict(\"-on 'saturday'\") == expected_args(on_date=\"saturday\")\n\n\ndef test_month_alone():\n    assert cli_as_dict(\"-month 1\") == expected_args(month=\"1\")\n    assert cli_as_dict(\"-month 01\") == expected_args(month=\"01\")\n    assert cli_as_dict(\"-month January\") == expected_args(month=\"January\")\n    assert cli_as_dict(\"-month Jan\") == expected_args(month=\"Jan\")\n\n\ndef test_day_alone():\n    assert cli_as_dict(\"-day 1\") == expected_args(day=\"1\")\n    assert cli_as_dict(\"-day 01\") == expected_args(day=\"01\")\n\n\ndef test_year_alone():\n    assert cli_as_dict(\"-year 2021\") == expected_args(year=\"2021\")\n    assert cli_as_dict(\"-year 21\") == expected_args(year=\"21\")\n\n\ndef test_today_in_history_alone():\n    assert cli_as_dict(\"-today-in-history\") == expected_args(today_in_history=True)\n\n\ndef test_short_alone():\n    assert cli_as_dict(\"--short\") == expected_args(short=True)\n\n\ndef test_starred_alone():\n    assert cli_as_dict(\"-starred\") == expected_args(starred=True)\n\n\ndef test_start_date_alone():\n    assert cli_as_dict(\"-from 2020-01-01\") == expected_args(start_date=\"2020-01-01\")\n    assert cli_as_dict(\"-from 'January 1st'\") == expected_args(start_date=\"January 1st\")\n\n\ndef test_and_alone():\n    assert cli_as_dict(\"-and\") == expected_args(strict=True)\n\n\ndef test_tags_alone():\n    assert cli_as_dict(\"--tags\") == expected_args(tags=True)\n\n\ndef test_text_alone():\n    assert cli_as_dict(\"lorem ipsum dolor sit amet\") == expected_args(\n        text=[\"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\"]\n    )\n\n\ndef test_version_alone():\n    from jrnl.commands import preconfig_version\n\n    assert cli_as_dict(\"--version\") == expected_args(preconfig_cmd=preconfig_version)\n\n\ndef test_editor_override():\n    parsed_args = cli_as_dict('--config-override editor \"nano\"')\n    assert parsed_args == expected_args(config_override=[[\"editor\", \"nano\"]])\n\n\ndef test_color_override():\n    assert cli_as_dict(\"--config-override colors.body blue\") == expected_args(\n        config_override=[[\"colors.body\", \"blue\"]]\n    )\n\n\ndef test_multiple_overrides():\n    parsed_args = cli_as_dict(\n        \"--config-override colors.title green \"\n        '--config-override editor \"nano\" '\n        '--config-override journal.scratchpad \"/tmp/scratchpad\"'\n    )\n    assert parsed_args == expected_args(\n        config_override=[\n            [\"colors.title\", \"green\"],\n            [\"editor\", \"nano\"],\n            [\"journal.scratchpad\", \"/tmp/scratchpad\"],\n        ]\n    )\n\n\n# @see https://github.com/jrnl-org/jrnl/issues/520\n@pytest.mark.parametrize(\n    \"cli\",\n    [\n        \"-and second @oldtag @newtag\",\n        \"second @oldtag @newtag -and\",\n        \"second -and @oldtag @newtag\",\n        \"second @oldtag -and @newtag\",\n    ],\n)\ndef test_and_ordering(cli):\n    result = expected_args(strict=True, text=[\"second\", \"@oldtag\", \"@newtag\"])\n    assert cli_as_dict(cli) == result\n\n\n# @see https://github.com/jrnl-org/jrnl/issues/520\n@pytest.mark.parametrize(\n    \"cli\",\n    [\n        \"--edit second @oldtag @newtag\",\n        \"second @oldtag @newtag --edit\",\n        \"second --edit @oldtag @newtag\",\n        \"second @oldtag --edit @newtag\",\n    ],\n)\ndef test_edit_ordering(cli):\n    result = expected_args(edit=True, text=[\"second\", \"@oldtag\", \"@newtag\"])\n    assert cli_as_dict(cli) == result\n\n\nclass TestDeserialization:\n    @pytest.mark.parametrize(\n        \"input_str\",\n        [\n            [\"editor\", \"nano\"],\n            [\"colors.title\", \"blue\"],\n            [\"default\", \"/tmp/egg.txt\"],\n        ],\n    )\n    def test_deserialize_multiword_strings(self, input_str):\n        runtime_config = make_yaml_valid_dict(input_str)\n        assert runtime_config.__class__ is dict\n        assert input_str[0] in runtime_config\n        assert runtime_config[input_str[0]] == input_str[1]\n\n    def test_deserialize_multiple_datatypes(self):\n        cfg = make_yaml_valid_dict([\"linewrap\", \"23\"])\n        assert cfg[\"linewrap\"] == 23\n\n        cfg = make_yaml_valid_dict([\"encrypt\", \"false\"])\n        assert cfg[\"encrypt\"] is False\n\n        cfg = make_yaml_valid_dict([\"editor\", \"vi -c startinsert\"])\n        assert cfg[\"editor\"] == \"vi -c startinsert\"\n\n        cfg = make_yaml_valid_dict([\"highlight\", \"true\"])\n        assert cfg[\"highlight\"] is True\n"
  },
  {
    "path": "tests/unit/test_path.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport random\nimport string\nfrom os import getenv\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom jrnl.path import absolute_path\nfrom jrnl.path import expand_path\nfrom jrnl.path import home_dir\n\n\n@pytest.fixture\ndef home_dir_str(monkeypatch):\n    username = \"username\"\n    monkeypatch.setenv(\"USERPROFILE\", username)  # for windows\n    monkeypatch.setenv(\"HOME\", username)  # for *nix\n    return username\n\n\n@pytest.fixture\ndef random_test_var(monkeypatch):\n    name = f\"JRNL_TEST_{''.join(random.sample(string.ascii_uppercase, 10))}\"\n    val = \"\".join(random.sample(string.ascii_lowercase, 25))\n    monkeypatch.setenv(name, val)\n    return (name, val)\n\n\ndef test_home_dir(home_dir_str):\n    assert home_dir() == home_dir_str\n\n\n@pytest.mark.on_posix\n@pytest.mark.parametrize(\n    \"path\",\n    [\"~\"],\n)\ndef test_expand_path_actually_expands_mac_linux(path):\n    # makes sure that path isn't being returns as-is\n    assert expand_path(path) != path\n\n\n@pytest.mark.on_win\n@pytest.mark.parametrize(\n    \"path\",\n    [\"~\", \"%USERPROFILE%\"],\n)\ndef test_expand_path_actually_expands_windows(path):\n    # makes sure that path isn't being returns as-is\n    assert expand_path(path) != path\n\n\n@pytest.mark.on_posix\n@pytest.mark.parametrize(\n    \"paths\",\n    [\n        [\"~\", \"HOME\"],\n    ],\n)\ndef test_expand_path_expands_into_correct_value_mac_linux(paths):\n    input_path, expected_path = paths[0], paths[1]\n    assert expand_path(input_path) == getenv(expected_path)\n\n\n@pytest.mark.on_win\n@pytest.mark.parametrize(\n    \"paths\",\n    [\n        [\"~\", \"USERPROFILE\"],\n        [\"%USERPROFILE%\", \"USERPROFILE\"],\n    ],\n)\ndef test_expand_path_expands_into_correct_value_windows(paths):\n    input_path, expected_path = paths[0], paths[1]\n    assert expand_path(input_path) == getenv(expected_path)\n\n\n@pytest.mark.on_posix\n@pytest.mark.parametrize(\"_\", range(25))\ndef test_expand_path_expands_into_random_env_value_mac_linux(_, random_test_var):\n    var_name, var_value = random_test_var[0], random_test_var[1]\n    assert expand_path(var_name) == var_name\n    assert expand_path(f\"${var_name}\") == var_value  # mac & linux\n    assert expand_path(f\"${var_name}\") == getenv(var_name)\n\n\n@pytest.mark.on_win\n@pytest.mark.parametrize(\"_\", range(25))\ndef test_expand_path_expands_into_random_env_value_windows(_, random_test_var):\n    var_name, var_value = random_test_var[0], random_test_var[1]\n    assert expand_path(var_name) == var_name\n    assert expand_path(f\"%{var_name}%\") == var_value  # windows\n    assert expand_path(f\"%{var_name}%\") == getenv(var_name)\n\n\n@patch(\"jrnl.path.expand_path\")\n@patch(\"os.path.abspath\")\ndef test_absolute_path(mock_abspath, mock_expand_path):\n    test_val = \"test_value\"\n\n    assert absolute_path(test_val) == mock_abspath.return_value\n    mock_expand_path.assert_called_with(test_val)\n    mock_abspath.assert_called_with(mock_expand_path.return_value)\n"
  },
  {
    "path": "tests/unit/test_time.py",
    "content": "# Copyright © 2012-2023 jrnl contributors\n# License: https://www.gnu.org/licenses/gpl-3.0.html\n\nimport datetime\n\nimport pytest\n\nfrom jrnl import time\n\n\ndef test_default_hour_is_added():\n    assert time.parse(\n        \"2020-06-20\", inclusive=False, default_hour=9, default_minute=0, bracketed=False\n    ) == datetime.datetime(2020, 6, 20, 9)\n\n\ndef test_default_minute_is_added():\n    assert time.parse(\n        \"2020-06-20\",\n        inclusive=False,\n        default_hour=0,\n        default_minute=30,\n        bracketed=False,\n    ) == datetime.datetime(2020, 6, 20, 0, 30)\n\n\n@pytest.mark.parametrize(\n    \"inputs\",\n    [\n        [2000, 2, 29, True],\n        [2023, 1, 0, False],\n        [2023, 1, 1, True],\n        [2023, 4, 31, False],\n        [2023, 12, 31, True],\n        [2023, 12, 32, False],\n        [2023, 13, 1, False],\n        [2100, 2, 27, True],\n        [2100, 2, 28, True],\n        [2100, 2, 29, False],\n    ],\n)\ndef test_is_valid_date(inputs):\n    year, month, day, expected_result = inputs\n    assert time.is_valid_date(year, month, day) == expected_result\n"
  }
]