Merge branch 'develop' into folder-journal-file-read-1692

This commit is contained in:
Micah Jerome Ellison 2023-03-06 11:19:52 -08:00
commit a21f3e19df
17 changed files with 175 additions and 140 deletions

View file

@ -6,7 +6,9 @@
**Implemented enhancements:** **Implemented enhancements:**
- Display locations of config file and documentation after initial install [\#1694](https://github.com/jrnl-org/jrnl/issues/1694)
- Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521) - Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521)
- Add message with config location and docs location when installation is complete [\#1695](https://github.com/jrnl-org/jrnl/pull/1695) ([micahellison](https://github.com/micahellison))
- Prompt to include colors in config when first running jrnl [\#1687](https://github.com/jrnl-org/jrnl/pull/1687) ([micahellison](https://github.com/micahellison)) - Prompt to include colors in config when first running jrnl [\#1687](https://github.com/jrnl-org/jrnl/pull/1687) ([micahellison](https://github.com/micahellison))
- 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)) - Search for entries with no tags or stars with `-not -starred` and `-not -tagged` [\#1663](https://github.com/jrnl-org/jrnl/pull/1663) ([cjcon90](https://github.com/cjcon90))
- Refactor flow for easier access to some files \(avoid things like `jrnl.Journal.Journal` and `jrnl.jrnl` co-existing\) [\#1662](https://github.com/jrnl-org/jrnl/pull/1662) ([wren](https://github.com/wren)) - 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))
@ -30,6 +32,7 @@
**Build:** **Build:**
- Support pytest-bdd 6 [\#1534](https://github.com/jrnl-org/jrnl/issues/1534)
- Update copyright notices for 2023 [\#1660](https://github.com/jrnl-org/jrnl/pull/1660) ([wren](https://github.com/wren)) - Update copyright notices for 2023 [\#1660](https://github.com/jrnl-org/jrnl/pull/1660) ([wren](https://github.com/wren))
- Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren)) - Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren))
- Add `simplify` plugin to linting checks [\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren)) - Add `simplify` plugin to linting checks [\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren))
@ -39,9 +42,13 @@
- Document template extension behavior [\#1677](https://github.com/jrnl-org/jrnl/issues/1677) - Document template extension behavior [\#1677](https://github.com/jrnl-org/jrnl/issues/1677)
- Visual Studio Code may store unencrypted temporary files [\#1675](https://github.com/jrnl-org/jrnl/issues/1675) - Visual Studio Code may store unencrypted temporary files [\#1675](https://github.com/jrnl-org/jrnl/issues/1675)
- Document `-tagged`, `-not -tagged`, and `-not -starred` [\#1668](https://github.com/jrnl-org/jrnl/issues/1668)
- Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651) - Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651)
- Update console examples on jrnl.sh front page [\#1622](https://github.com/jrnl-org/jrnl/issues/1622)
- Update documentation front page text [\#1698](https://github.com/jrnl-org/jrnl/pull/1698) ([micahellison](https://github.com/micahellison))
- Support mkdocs 1.4.2 and fix its missing breadcrumb [\#1691](https://github.com/jrnl-org/jrnl/pull/1691) ([micahellison](https://github.com/micahellison)) - Support mkdocs 1.4.2 and fix its missing breadcrumb [\#1691](https://github.com/jrnl-org/jrnl/pull/1691) ([micahellison](https://github.com/micahellison))
- Document temporary file extension behavior when using template [\#1686](https://github.com/jrnl-org/jrnl/pull/1686) ([micahellison](https://github.com/micahellison)) - Document temporary file extension behavior when using template [\#1686](https://github.com/jrnl-org/jrnl/pull/1686) ([micahellison](https://github.com/micahellison))
- Document `-tagged`, `-not -tagged`, and `-not -starred` [\#1684](https://github.com/jrnl-org/jrnl/pull/1684) ([micahellison](https://github.com/micahellison))
- Update documentation about privacy and security in VSCode [\#1680](https://github.com/jrnl-org/jrnl/pull/1680) ([giuseppedandrea](https://github.com/giuseppedandrea)) - Update documentation about privacy and security in VSCode [\#1680](https://github.com/jrnl-org/jrnl/pull/1680) ([giuseppedandrea](https://github.com/giuseppedandrea))
- Update documentation on temporary files naming [\#1673](https://github.com/jrnl-org/jrnl/pull/1673) ([giuseppedandrea](https://github.com/giuseppedandrea)) - Update documentation on temporary files naming [\#1673](https://github.com/jrnl-org/jrnl/pull/1673) ([giuseppedandrea](https://github.com/giuseppedandrea))
- Update docs to include time and title in arguments with `--edit` [\#1657](https://github.com/jrnl-org/jrnl/pull/1657) ([pconrad-fb](https://github.com/pconrad-fb)) - 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))

View file

@ -76,8 +76,11 @@ entries, such as `yesterday`, `today`, `Tuesday`, or `2021-08-01`.
| -contains TEXT | Show entries containing specific text (put quotes around text with spaces) | | -contains TEXT | Show entries containing specific text (put quotes around text with spaces) |
| -and | Show only entries that match all conditions, like saying "x AND y" (default: OR) | | -and | Show only entries that match all conditions, like saying "x AND y" (default: OR) |
| -starred | Show only starred entries (marked with *) | | -starred | Show only starred entries (marked with *) |
| -tagged | Show only tagged entries (marked with the [configured tagsymbols](reference-config-file.md#tagsymbols)) |
| -n [NUMBER] | Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same effect) | | -n [NUMBER] | Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same effect) |
| -not [TAG] | Exclude entries with this tag | | -not [TAG] | Exclude entries with this tag |
| -not -starred | Exclude entries that are starred |
| -not -tagged | Exclude entries that are tagged |
## Searching Options ## Searching Options
These help you do various tasks with the selected entries from your search. These help you do various tasks with the selected entries from your search.

View file

@ -76,22 +76,22 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
<section> <section>
<i class="icon future"></i> <i class="icon future"></i>
<h3>Future-proof.</h3> <h3>Future-proof.</h3>
<p>Your journals are stored in plain-text files that will still be readable in 50 years when all your fancy iPad apps will have gone the way of the Dodo.</p> <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>
</section> </section>
<section> <section>
<i class="icon secure"></i> <i class="icon secure"></i>
<h3>Secure.</h3> <h3>Secure.</h3>
<p>Encrypt your journals with industry-strength AES encryption. The NSA won't be able to read your dirty secrets.</p> <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>
</section> </section>
<section> <section>
<i class="icon sync"></i> <i class="icon sync"></i>
<h3>Accessible anywhere.</h3> <h3>Accessible anywhere.</h3>
<p>Sync your journals with Dropbox and capture your thoughts where ever you are.</p> <p>Sync your journal files with other tools like Dropbox to capture your thoughts wherever you are.</p>
</section> </section>
<section> <section>
<i class="icon github"></i> <i class="icon github"></i>
<h3>Free &amp; Open Source.</h3> <h3>Free &amp; Open Source.</h3>
<p>jrnl is made by a bunch of really friendly and remarkably attractive people. Maybe even <a href="https://www.github.com/jrnl-org/jrnl" title="Fork jrnl on GitHub">you</a>?</p> <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>
</section> </section>
<section> <section>
<i class="icon folders"></i> <i class="icon folders"></i>
@ -107,17 +107,17 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
<script> <script>
new Typed("#typed", { new Typed("#typed", {
strings: [ strings: [
"jrnl today: Started writing my memoirs. On the command line. Like a boss.", "jrnl Started writing my memoirs on the command line. 🎉🔥💻🔥🎉",
"jrnl yesterday 2pm: used jrnl to keep track of accomplished tasks. The done.txt for my todo.txt", "jrnl yesterday 2pm: used jrnl to keep track of accomplished tasks. The done.txt for my todo.txt",
"jrnl <b>-from</b> 2009 <b>-until</b> may<br /><i>`(Displays all entries from January 2009 to last may)`</i>", "jrnl <b>-from</b> 2019 <b>-until</b> may<br /><i>`(displays all entries from January 2019 to last May)`</i>",
"jrnl A day on the beach with @beth and @frank. Taggidy-tag-tag.", "jrnl A day on the beach with @beth and @frank. Tagging them so I can easily look this up later.",
"jrnl <b>--tags</b><br /><i>`@idea 7<br />@beth 5</i>`", "jrnl <b>--tags</b><br /><i>`@frank 7<br />@beth 5</i>`",
"jrnl <b>--format</b> json<br /><i>`(Outputs your entire journal as json)</i>`", "jrnl <b>--format</b> json<br /><i>`(Outputs your entire journal as json)</i>`",
"jrnl <b>--encrypt</b><br /><i>`(AES encryption. Crack this, NSA)</i>`" "jrnl <b>--encrypt</b><br /><i>`(AES encryption. Don't lose your password!)</i>`"
], ],
typeSpeed: 35, typeSpeed: 20, // less is faster
backSpeed: 15, backSpeed: 10,
backDelay: 2000, backDelay: 2500,
loop: true, loop: true,
showCursor: false showCursor: false
}); });

View file

@ -154,6 +154,15 @@ def install() -> dict:
default_config["colors"] = get_default_colors() default_config["colors"] = get_default_colors()
save_config(default_config) save_config(default_config)
print_msg(
Message(
MsgText.InstallComplete,
MsgStyle.NORMAL,
params={"config_path": get_config_path()},
)
)
return default_config return default_config

View file

@ -263,7 +263,9 @@ class Journal:
start_date = time.parse(start_date) start_date = time.parse(start_date)
# If strict mode is on, all tags have to be present in entry # If strict mode is on, all tags have to be present in entry
has_tags = self.search_tags.issubset if strict else self.search_tags.intersection has_tags = (
self.search_tags.issubset if strict else self.search_tags.intersection
)
def excluded(tags): def excluded(tags):
return 0 < len([tag for tag in tags if tag in excluded_tags]) return 0 < len([tag for tag in tags if tag in excluded_tags])

View file

@ -28,6 +28,11 @@ class MsgText(Enum):
AllDoneUpgrade = "We're all done here and you can start enjoying jrnl 2" AllDoneUpgrade = "We're all done here and you can start enjoying jrnl 2"
InstallComplete = """
jrnl configuration created at {config_path}
For advanced features, read the docs at https://jrnl.sh
"""
# --- Prompts --- # # --- Prompts --- #
InstallJournalPathQuestion = """ InstallJournalPathQuestion = """
Path to your journal file (leave blank for {default_journal_path}): Path to your journal file (leave blank for {default_journal_path}):

50
poetry.lock generated
View file

@ -345,14 +345,6 @@ python-dateutil = ">=2.8.1"
[package.extras] [package.extras]
dev = ["flake8", "markdown", "twine", "wheel"] dev = ["flake8", "markdown", "twine", "wheel"]
[[package]]
name = "glob2"
version = "0.7"
description = "Version of the glob module that can capture patterns and supports recursive wildcards"
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.4" version = "3.4"
@ -819,14 +811,6 @@ python-versions = "*"
[package.extras] [package.extras]
tests = ["pytest"] tests = ["pytest"]
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "pycodestyle" name = "pycodestyle"
version = "2.8.0" version = "2.8.0"
@ -900,19 +884,18 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.
[[package]] [[package]]
name = "pytest-bdd" name = "pytest-bdd"
version = "5.0.0" version = "6.1.1"
description = "BDD for pytest" description = "BDD for pytest"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7,<4.0"
[package.dependencies] [package.dependencies]
glob2 = "*"
Mako = "*" Mako = "*"
parse = "*" parse = "*"
parse-type = "*" parse-type = "*"
py = "*" pytest = ">=6.2.0"
pytest = ">=4.3" typing-extensions = "*"
[[package]] [[package]]
name = "pytest-clarity" name = "pytest-clarity"
@ -1153,6 +1136,14 @@ python-versions = ">=3.7"
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
[[package]]
name = "typing-extensions"
version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "tzdata" name = "tzdata"
version = "2022.7" version = "2022.7"
@ -1249,7 +1240,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = ">=3.10.0, <3.13" python-versions = ">=3.10.0, <3.13"
content-hash = "ef292b09387c666923e871b66f975c2d44f646ae478dbaf62e295fbb84d7aca1" content-hash = "dfc32ee61025dae6033987a8ff8290d4c2a34197502b8030cef02db58b86baf1"
[metadata.files] [metadata.files]
ansiwrap = [ ansiwrap = [
@ -1564,9 +1555,6 @@ ghp-import = [
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
] ]
glob2 = [
{file = "glob2-0.7.tar.gz", hash = "sha256:85c3dbd07c8aa26d63d7aacee34fa86e9a91a3873bc30bf62ec46e531f92ab8c"},
]
idna = [ idna = [
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
@ -1766,10 +1754,6 @@ pure-eval = [
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
] ]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pycodestyle = [ pycodestyle = [
{file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
@ -1795,8 +1779,8 @@ pytest = [
{file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"},
] ]
pytest-bdd = [ pytest-bdd = [
{file = "pytest-bdd-5.0.0.tar.gz", hash = "sha256:fab7093ed3d5e51ee0c68de093c90e4f40de345bd9a54a188b2991ce2a2a39cf"}, {file = "pytest_bdd-6.1.1-py3-none-any.whl", hash = "sha256:57eba5878d77036f356a85fb1d108cb061d8af4fb4d032b1a424fa9abe9e498b"},
{file = "pytest_bdd-5.0.0-py3-none-any.whl", hash = "sha256:c7cf12209606421f61f36b5dc63beccd0c82d29446c0592cf68af2dad0a9761d"}, {file = "pytest_bdd-6.1.1.tar.gz", hash = "sha256:138af3592bcce5d4684b0d690777cf199b39ce45d423ca28086047ffe6111010"},
] ]
pytest-clarity = [ pytest-clarity = [
{file = "pytest-clarity-1.0.1.tar.gz", hash = "sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772"}, {file = "pytest-clarity-1.0.1.tar.gz", hash = "sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772"},
@ -1947,6 +1931,10 @@ traitlets = [
{file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"},
{file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"},
] ]
typing-extensions = [
{file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
{file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
]
tzdata = [ tzdata = [
{file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"}, {file = "tzdata-2022.7-py2.py3-none-any.whl", hash = "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d"},
{file = "tzdata-2022.7.tar.gz", hash = "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa"}, {file = "tzdata-2022.7.tar.gz", hash = "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa"},

View file

@ -52,9 +52,10 @@ flake8-simplify = ">=0.19"
ipdb = "*" ipdb = "*"
isort = ">=5.10" isort = ">=5.10"
mkdocs = ">=1.4" mkdocs = ">=1.4"
parse-type = ">=0.6.0"
poethepoet = "*" poethepoet = "*"
pytest = ">=6.2" pytest = ">=6.2"
pytest-bdd = ">=4.0.1,<6.0" pytest-bdd = ">=6.0"
pytest-clarity = "*" pytest-clarity = "*"
pytest-xdist = ">=2.5.0" pytest-xdist = ">=2.5.0"
requests = "*" requests = "*"
@ -174,8 +175,9 @@ isolated_build = True
[testenv] [testenv]
deps = deps =
pytest >= 6.2 pytest >= 6.2
pytest-bdd >=4.0.1,<6.0 pytest-bdd >=6.0
pytest-xdist >=2.5.0 pytest-xdist >=2.5.0
parse-type >=0.6.0
toml >=0.10 toml >=0.10
commands = pytest {posargs} commands = pytest {posargs}

View file

@ -25,13 +25,16 @@ Feature: Change entry times in journal
Scenario Outline: Change flag changes prompted entries Scenario Outline: Change flag changes prompted entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl -1" When we run "jrnl --short"
Then the output should contain "2020-09-24 09:14 The third entry finally" Then the output should be
2020-08-29 11:11 Entry the first.
2020-08-31 14:32 A second entry in what I hope to be a long series.
2020-09-24 09:14 The third entry finally after weeks without writing.
When we run "jrnl --change-time '2022-04-23 10:30'" and enter When we run "jrnl --change-time '2022-04-23 10:30'" and enter
Y Y
N N
Y Y
When we run "jrnl -99 --short" When we run "jrnl --short"
Then the output should be Then the output should be
2020-08-31 14:32 A second entry in what I hope to be a long series. 2020-08-31 14:32 A second entry in what I hope to be a long series.
2022-04-23 10:30 Entry the first. 2022-04-23 10:30 Entry the first.

View file

@ -6,7 +6,9 @@ Feature: Installing jrnl
\n \n
\n \n
\n \n
Then the output should contain "Journal 'default' created" Then the output should contain "jrnl configuration created at"
And the output should contain "For advanced features, read the docs at https://jrnl.sh"
And the output should contain "Journal 'default' created"
And the default journal "journal.txt" should be in the "." directory And the default journal "journal.txt" should be in the "." directory
And the config should contain "encrypt: false" And the config should contain "encrypt: false"
And the version in the config file should be up-to-date And the version in the config file should be up-to-date

View file

@ -125,7 +125,7 @@ Feature: Searching in a journal
| basic_dayone.yaml | | basic_dayone.yaml |
Scenario: Searching for unstarred entries Scenario Outline: Searching for unstarred entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl -not -starred" When we run "jrnl -not -starred"
@ -138,7 +138,7 @@ Feature: Searching in a journal
| basic_folder.yaml | | basic_folder.yaml |
| basic_dayone.yaml | | basic_dayone.yaml |
Scenario: Searching for tagged entries Scenario Outline: Searching for tagged entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl -tagged" When we run "jrnl -tagged"
@ -151,7 +151,7 @@ Feature: Searching in a journal
| basic_folder.yaml | | basic_folder.yaml |
| basic_dayone.yaml | | basic_dayone.yaml |
Scenario: Searching for untagged entries Scenario Outline: Searching for untagged entries
Given we use the config "empty_folder.yaml" Given we use the config "empty_folder.yaml"
When we run "jrnl Tagged entry. This one has a @tag." When we run "jrnl Tagged entry. This one has a @tag."
Then we should get no error Then we should get no error

View file

@ -201,6 +201,16 @@ def input_method():
return "" return ""
@fixture
def all_input():
return ""
@fixture
def command():
return ""
@fixture @fixture
def cache_dir(): def cache_dir():
return {"exists": False, "path": ""} return {"exists": False, "path": ""}
@ -221,13 +231,15 @@ def mock_user_input(request, password_input, stdin_input):
def _mock_user_input(): def _mock_user_input():
# user_input needs to be here because we don't know it until cli_run starts # user_input needs to be here because we don't know it until cli_run starts
user_input = get_fixture(request, "all_input", None) user_input = get_fixture(request, "all_input", None)
if user_input is None: if user_input is None:
user_input = Exception("Unexpected call for user input") user_input = Exception("Unexpected call for user input")
else: else:
user_input = iter(user_input.splitlines()) user_input = iter(user_input.splitlines())
def mock_console_input(**kwargs): def mock_console_input(**kwargs):
if kwargs["password"] and not isinstance(password_input, Exception): pw = kwargs.get("password", False)
if pw and not isinstance(password_input, Exception):
return password_input return password_input
if isinstance(user_input, Iterable): if isinstance(user_input, Iterable):
@ -236,7 +248,7 @@ def mock_user_input(request, password_input, stdin_input):
return "" if input_line == r"\n" else input_line return "" if input_line == r"\n" else input_line
# exceptions # exceptions
return user_input if not kwargs["password"] else password_input return user_input if not pw else password_input
mock_console = Mock(wraps=Console(stderr=True)) mock_console = Mock(wraps=Console(stderr=True))
mock_console.input = Mock(side_effect=mock_console_input) mock_console.input = Mock(side_effect=mock_console_input)

View file

@ -19,7 +19,6 @@ from jrnl.time import __get_pdt_calendar
from tests.lib.fixtures import FailedKeyring from tests.lib.fixtures import FailedKeyring
from tests.lib.fixtures import NoKeyring from tests.lib.fixtures import NoKeyring
from tests.lib.fixtures import TestKeyring from tests.lib.fixtures import TestKeyring
from tests.lib.helpers import get_fixture
@given(parse("we {editor_method} to the editor if opened\n{editor_input}")) @given(parse("we {editor_method} to the editor if opened\n{editor_input}"))
@ -84,16 +83,16 @@ def we_have_type_of_keyring(keyring_type):
return TestKeyring() return TestKeyring()
@given(parse('we use the config "{config_file}"'), target_fixture="config_path")
@given(parse("we use no config"), target_fixture="config_path") @given(parse("we use no config"), target_fixture="config_path")
def we_use_the_config(request, temp_dir, working_dir): def we_use_no_config(temp_dir):
config_file = get_fixture(request, "config_file") os.chdir(temp_dir.name) # @todo move this step to a more universal place
return os.path.join(temp_dir.name, "non_existing_config.yaml")
@given(parse('we use the config "{config_file}"'), target_fixture="config_path")
def we_use_the_config(request, temp_dir, working_dir, config_file):
# Move into temp dir as cwd # Move into temp dir as cwd
os.chdir(temp_dir.name) os.chdir(temp_dir.name) # @todo move this step to a more universal place
if not config_file:
return os.path.join(temp_dir.name, "non_existing_config.yaml")
# Copy the config file over # Copy the config file over
config_source = os.path.join(working_dir, "data", "configs", config_file) config_source = os.path.join(working_dir, "data", "configs", config_file)
@ -133,7 +132,7 @@ def config_exists(config_file, temp_dir, working_dir):
shutil.copy2(config_source, config_dest) shutil.copy2(config_source, config_dest)
@given(parse('we use the password "{password}" if prompted')) @given(parse('we use the password "{password}" if prompted'), target_fixture="password")
def use_password_forever(password): def use_password_forever(password):
return password return password

View file

@ -32,17 +32,6 @@ def does_directory_contain_n_files(directory_path, number):
return int(number) == count return int(number) == count
def parse_should_or_should_not(should_or_should_not):
if should_or_should_not == "should":
return True
elif should_or_should_not == "should not":
return False
else:
raise Exception(
"should_or_should_not valid values are 'should' or 'should not'"
)
def assert_equal_tags_ignoring_order( def assert_equal_tags_ignoring_order(
actual_line, expected_line, actual_content, expected_content actual_line, expected_line, actual_content, expected_content
): ):
@ -81,7 +70,7 @@ def spy_wrapper(wrapped_function):
def get_fixture(request, name, default=None): def get_fixture(request, name, default=None):
result = default try:
if name in request.node.fixturenames: return request.getfixturevalue(name)
result = request.getfixturevalue(name) except LookupError:
return result return default

View file

@ -15,7 +15,9 @@ from tests.lib.helpers import assert_equal_tags_ignoring_order
from tests.lib.helpers import does_directory_contain_files from tests.lib.helpers import does_directory_contain_files
from tests.lib.helpers import does_directory_contain_n_files from tests.lib.helpers import does_directory_contain_n_files
from tests.lib.helpers import get_nested_val from tests.lib.helpers import get_nested_val
from tests.lib.helpers import parse_should_or_should_not from tests.lib.type_builders import should_choice
SHOULD_DICT = {"Should": should_choice}
@then("we should get no error") @then("we should get no error")
@ -31,40 +33,38 @@ def output_should_match(regex, cli_run):
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}" assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}"
@then(parse("the output {should_or_should_not} contain\n{expected_output}")) @then(parse("the output {it_should:Should} contain\n{expected_output}", SHOULD_DICT))
@then(parse('the output {should_or_should_not} contain "{expected_output}"')) @then(parse('the output {it_should:Should} contain "{expected_output}"', SHOULD_DICT))
@then( @then(
parse( parse(
"the {which_output_stream} output {should_or_should_not} contain\n{expected_output}" "the {which_output_stream} output {it_should:Should} contain\n{expected_output}",
SHOULD_DICT,
) )
) )
@then( @then(
parse( parse(
'the {which_output_stream} output {should_or_should_not} contain "{expected_output}"' 'the {which_output_stream} output {it_should:Should} contain "{expected_output}"',
SHOULD_DICT,
) )
) )
def output_should_contain( def output_should_contain(expected_output, which_output_stream, cli_run, it_should):
expected_output, which_output_stream, cli_run, should_or_should_not
):
we_should = parse_should_or_should_not(should_or_should_not)
output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}" output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}"
assert expected_output assert expected_output
if which_output_stream is None: if which_output_stream is None:
assert ((expected_output in cli_run["stdout"]) == we_should) or ( assert ((expected_output in cli_run["stdout"]) == it_should) or (
(expected_output in cli_run["stderr"]) == we_should (expected_output in cli_run["stderr"]) == it_should
), output_str ), output_str
elif which_output_stream == "standard": elif which_output_stream == "standard":
assert (expected_output in cli_run["stdout"]) == we_should, output_str assert (expected_output in cli_run["stdout"]) == it_should, output_str
elif which_output_stream == "error": elif which_output_stream == "error":
assert (expected_output in cli_run["stderr"]) == we_should, output_str assert (expected_output in cli_run["stderr"]) == it_should, output_str
else: else:
assert ( assert (
expected_output in cli_run[which_output_stream] expected_output in cli_run[which_output_stream]
) == we_should, output_str ) == it_should, output_str
@then(parse("the output should not contain\n{expected_output}")) @then(parse("the output should not contain\n{expected_output}"))
@ -78,7 +78,7 @@ def output_should_not_contain(expected_output, cli_run):
def output_should_be(expected_output, cli_run): def output_should_be(expected_output, cli_run):
actual = cli_run["stdout"].strip() actual = cli_run["stdout"].strip()
expected = expected_output.strip() expected = expected_output.strip()
assert expected == actual assert actual == expected
@then("the output should be empty") @then("the output should be empty")
@ -130,19 +130,19 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir
@then( @then(
parse( parse(
'the config for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"' 'the config for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
SHOULD_DICT,
) )
) )
@then( @then(
parse( parse(
'the config for journal "{journal_name}" {should_or_should_not} contain\n{some_yaml}' 'the config for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
SHOULD_DICT,
) )
) )
@then(parse('the config {should_or_should_not} contain "{some_yaml}"')) @then(parse('the config {it_should:Should} contain "{some_yaml}"', SHOULD_DICT))
@then(parse("the config {should_or_should_not} contain\n{some_yaml}")) @then(parse("the config {it_should:Should} contain\n{some_yaml}", SHOULD_DICT))
def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_yaml): def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
we_should = parse_should_or_should_not(should_or_should_not)
actual = config_on_disk actual = config_on_disk
if journal_name: if journal_name:
actual = actual["journals"][journal_name] actual = actual["journals"][journal_name]
@ -154,26 +154,28 @@ def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_
# `expected` objects formatted in yaml only compare one level deep # `expected` objects formatted in yaml only compare one level deep
actual_slice = {key: actual.get(key, None) for key in expected.keys()} actual_slice = {key: actual.get(key, None) for key in expected.keys()}
assert (expected == actual_slice) == we_should assert (expected == actual_slice) == it_should
@then( @then(
parse( parse(
'the config in memory for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"' 'the config in memory for journal "{journal_name}" {it_should:Should} contain "{some_yaml}"',
SHOULD_DICT,
) )
) )
@then( @then(
parse( parse(
'the config in memory for journal "{journal_name}" {should_or_should_not} contain\n{some_yaml}' 'the config in memory for journal "{journal_name}" {it_should:Should} contain\n{some_yaml}',
SHOULD_DICT,
) )
) )
@then(parse('the config in memory {should_or_should_not} contain "{some_yaml}"')) @then(
@then(parse("the config in memory {should_or_should_not} contain\n{some_yaml}")) parse('the config in memory {it_should:Should} contain "{some_yaml}"', SHOULD_DICT)
def config_var_in_memory( )
config_in_memory, journal_name, should_or_should_not, some_yaml @then(
): parse("the config in memory {it_should:Should} contain\n{some_yaml}", SHOULD_DICT)
we_should = parse_should_or_should_not(should_or_should_not) )
def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
actual = config_in_memory["overrides"] actual = config_in_memory["overrides"]
if journal_name: if journal_name:
actual = actual["journals"][journal_name] actual = actual["journals"][journal_name]
@ -185,7 +187,7 @@ def config_var_in_memory(
# `expected` objects formatted in yaml only compare one level deep # `expected` objects formatted in yaml only compare one level deep
actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()} actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()}
assert (expected == actual_slice) == we_should assert (expected == actual_slice) == it_should
@then("we should be prompted for a password") @then("we should be prompted for a password")
@ -224,31 +226,27 @@ def journal_directory_should_not_exist(config_on_disk, journal_name):
), f'Journal "{journal_name}" does exist' ), f'Journal "{journal_name}" does exist'
@then(parse("the journal {should_or_should_not} exist")) @then(parse("the journal {it_should:Should} exist", SHOULD_DICT))
def journal_should_not_exist(config_on_disk, should_or_should_not): def journal_should_not_exist(config_on_disk, it_should):
scoped_config = scope_config(config_on_disk, "default") scoped_config = scope_config(config_on_disk, "default")
expected_path = scoped_config["journal"] expected_path = scoped_config["journal"]
contains_files = does_directory_contain_files(expected_path, ".") contains_files = does_directory_contain_files(expected_path, ".")
if should_or_should_not == "should": assert contains_files == it_should
assert contains_files
elif should_or_should_not == "should not":
assert not contains_files
else:
raise Exception(
"should_or_should_not valid values are 'should' or 'should not'"
)
@then(parse('the journal "{journal_name}" directory {should_or_should_not} exist')) @then(
def directory_should_not_exist(config_on_disk, should_or_should_not, journal_name): parse(
'the journal "{journal_name}" directory {it_should:Should} exist', SHOULD_DICT
)
)
def directory_should_not_exist(config_on_disk, it_should, journal_name):
scoped_config = scope_config(config_on_disk, journal_name) scoped_config = scope_config(config_on_disk, journal_name)
expected_path = scoped_config["journal"] expected_path = scoped_config["journal"]
we_should = parse_should_or_should_not(should_or_should_not)
dir_exists = os.path.isdir(expected_path) dir_exists = os.path.isdir(expected_path)
assert dir_exists == we_should assert dir_exists == it_should
@then(parse('the content of file "{file_path}" in the cache should be\n{file_content}')) @then(parse('the content of file "{file_path}" in the cache should be\n{file_content}'))
@ -383,26 +381,23 @@ def count_elements(number, item, cli_run):
assert len(xml_tree.findall(".//" + item)) == number assert len(xml_tree.findall(".//" + item)) == number
@then(parse("the editor {should_or_should_not} have been called")) @then(parse("the editor {it_should:Should} have been called", SHOULD_DICT))
@then( @then(
parse( parse(
"the editor {should_or_should_not} have been called with {num_args} arguments" "the editor {it_should:Should} have been called with {num_args} arguments",
SHOULD_DICT,
) )
) )
def count_editor_args(num_args, cli_run, editor_state, should_or_should_not): def count_editor_args(num_args, cli_run, editor_state, it_should):
we_should = parse_should_or_should_not(should_or_should_not) assert cli_run["mocks"]["editor"].called == it_should
assert cli_run["mocks"]["editor"].called == we_should
if isinstance(num_args, int): if isinstance(num_args, int):
assert len(editor_state["command"]) == int(num_args) assert len(editor_state["command"]) == int(num_args)
@then(parse("the stdin prompt {should_or_should_not} have been called")) @then(parse("the stdin prompt {it_should:Should} have been called", SHOULD_DICT))
def stdin_prompt_called(cli_run, should_or_should_not): def stdin_prompt_called(cli_run, it_should):
we_should = parse_should_or_should_not(should_or_should_not) assert cli_run["mocks"]["stdin_input"].called == it_should
assert cli_run["mocks"]["stdin_input"].called == we_should
@then(parse('the editor filename should end with "{suffix}"')) @then(parse('the editor filename should end with "{suffix}"'))

View file

@ -0,0 +1,11 @@
# Copyright © 2012-2023 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
from parse_type import TypeBuilder
should_choice = TypeBuilder.make_enum(
{
"should": True,
"should not": False,
}
)

View file

@ -7,6 +7,7 @@ from contextlib import ExitStack
from pytest_bdd import when from pytest_bdd import when
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re from pytest_bdd.parsers import re
from pytest_bdd.steps import inject_fixture
from jrnl.main import run from jrnl.main import run
@ -29,13 +30,20 @@ all_input = '("(?P<all_input>[^"]*)")'
@when(parse('we run "jrnl {command}" and {input_method}\n{all_input}')) @when(parse('we run "jrnl {command}" and {input_method}\n{all_input}'))
@when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}')) @when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}'))
@when(parse('we run "jrnl {command}"')) @when(re(f'we run "jrnl {command}"(?! and)'))
@when('we run "jrnl"') @when('we run "jrnl"')
def we_run_jrnl(cli_run, capsys, keyring): def we_run_jrnl(capsys, keyring, request, command, input_method, all_input):
from keyring import set_keyring from keyring import set_keyring
set_keyring(keyring) set_keyring(keyring)
# fixture injection (pytest-bdd >=6.0)
inject_fixture(request, "command", command)
inject_fixture(request, "input_method", input_method)
inject_fixture(request, "all_input", all_input)
cli_run = request.getfixturevalue("cli_run")
with ExitStack() as stack: with ExitStack() as stack:
mocks = cli_run["mocks"] mocks = cli_run["mocks"]
factories = cli_run["mock_factories"] factories = cli_run["mock_factories"]