mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
Merge branch 'develop' into develop
This commit is contained in:
commit
dbc98749cc
30 changed files with 2204 additions and 1615 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -6,7 +6,11 @@
|
|||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Display locations of config file and documentation after initial install [\#1694](https://github.com/jrnl-org/jrnl/issues/1694)
|
||||
- Don't import cryptography package if not needed [\#1521](https://github.com/jrnl-org/jrnl/issues/1521)
|
||||
- Add message with config location and docs location when installation is complete [\#1695](https://github.com/jrnl-org/jrnl/pull/1695) ([micahellison](https://github.com/micahellison))
|
||||
- Prompt to include colors in config when first running jrnl [\#1687](https://github.com/jrnl-org/jrnl/pull/1687) ([micahellison](https://github.com/micahellison))
|
||||
- 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))
|
||||
- Add more type hints [\#1642](https://github.com/jrnl-org/jrnl/pull/1642) ([outa](https://github.com/outa))
|
||||
- Add `rich` handler to debug logging [\#1627](https://github.com/jrnl-org/jrnl/pull/1627) ([wren](https://github.com/wren))
|
||||
|
@ -14,7 +18,11 @@
|
|||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Templated entries should not be saved if the raw text is identical to the original template [\#1652](https://github.com/jrnl-org/jrnl/issues/1652)
|
||||
- jrnl does not update version key in config file [\#1638](https://github.com/jrnl-org/jrnl/issues/1638)
|
||||
- jrnl should not create 0-length "encrypted" file on startup [\#1493](https://github.com/jrnl-org/jrnl/issues/1493)
|
||||
- Save empty journal on install instead of just creating a zero-length file [\#1690](https://github.com/jrnl-org/jrnl/pull/1690) ([micahellison](https://github.com/micahellison))
|
||||
- Don't save templated journal entries if the received raw text is the same as the template itself [\#1653](https://github.com/jrnl-org/jrnl/pull/1653) ([Briscoooe](https://github.com/Briscoooe))
|
||||
- Add tag to XML file when edited DayOne entry and is searchable afterward [\#1648](https://github.com/jrnl-org/jrnl/pull/1648) ([jonakeys](https://github.com/jonakeys))
|
||||
- Update version key in config file after version changes [\#1646](https://github.com/jrnl-org/jrnl/pull/1646) ([jonakeys](https://github.com/jonakeys))
|
||||
|
||||
|
@ -24,6 +32,7 @@
|
|||
|
||||
**Build:**
|
||||
|
||||
- Support pytest-bdd 6 [\#1534](https://github.com/jrnl-org/jrnl/issues/1534)
|
||||
- Update copyright notices for 2023 [\#1660](https://github.com/jrnl-org/jrnl/pull/1660) ([wren](https://github.com/wren))
|
||||
- Fix bug where changelog is always slightly out of date on release tags [\#1631](https://github.com/jrnl-org/jrnl/pull/1631) ([wren](https://github.com/wren))
|
||||
- Add `simplify` plugin to linting checks [\#1630](https://github.com/jrnl-org/jrnl/pull/1630) ([wren](https://github.com/wren))
|
||||
|
@ -31,7 +40,17 @@
|
|||
|
||||
**Documentation:**
|
||||
|
||||
- Document template extension behavior [\#1677](https://github.com/jrnl-org/jrnl/issues/1677)
|
||||
- Visual Studio Code may store unencrypted temporary files [\#1675](https://github.com/jrnl-org/jrnl/issues/1675)
|
||||
- Document `-tagged`, `-not -tagged`, and `-not -starred` [\#1668](https://github.com/jrnl-org/jrnl/issues/1668)
|
||||
- Documentation Change [\#1651](https://github.com/jrnl-org/jrnl/issues/1651)
|
||||
- Update console examples on jrnl.sh front page [\#1622](https://github.com/jrnl-org/jrnl/issues/1622)
|
||||
- Update documentation front page text [\#1698](https://github.com/jrnl-org/jrnl/pull/1698) ([micahellison](https://github.com/micahellison))
|
||||
- Support mkdocs 1.4.2 and fix its missing breadcrumb [\#1691](https://github.com/jrnl-org/jrnl/pull/1691) ([micahellison](https://github.com/micahellison))
|
||||
- Document temporary file extension behavior when using template [\#1686](https://github.com/jrnl-org/jrnl/pull/1686) ([micahellison](https://github.com/micahellison))
|
||||
- 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 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))
|
||||
- Fix markup in "Advanced Usage" doc [\#1655](https://github.com/jrnl-org/jrnl/pull/1655) ([multani](https://github.com/multani))
|
||||
- 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))
|
||||
|
|
|
@ -37,6 +37,9 @@ jrnl yesterday: All my troubles seemed so far away. --edit
|
|||
|
||||
All 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.
|
||||
|
||||
Please see [this section](./privacy-and-security.md#editor-history) about how
|
||||
your editor might leak sensitive information and how to mitigate that risk.
|
||||
|
||||
## Sublime Text
|
||||
|
||||
To use [Sublime Text](https://www.sublimetext.com/), install the command line
|
||||
|
@ -71,6 +74,17 @@ back to journal. In the case of MacVim, this is `-f`:
|
|||
editor: "mvim -f"
|
||||
```
|
||||
|
||||
## Vim/Neovim
|
||||
|
||||
To use any of the Vim derivatives as editor in Linux, simply set the `editor`
|
||||
to the executable:
|
||||
|
||||
```yaml
|
||||
editor: "vim"
|
||||
# or
|
||||
editor: "nvim"
|
||||
```
|
||||
|
||||
## iA Writer
|
||||
|
||||
On OS X, you can use the fabulous [iA
|
||||
|
|
|
@ -78,8 +78,125 @@ unencrypted temporary remains on your disk. If your computer were to shut off
|
|||
during this time, or the `jrnl` process were killed unexpectedly, then the
|
||||
unencrypted temporary file will remain on your disk. You can mitigate this
|
||||
issue by only saving with your editor right before closing it. You can also
|
||||
manually delete these files (i.e. files named `jrnl_*.txt`) from your temporary
|
||||
folder.
|
||||
manually delete these files from your temporary folder. By default, they
|
||||
are named `jrnl*.jrnl`, but if you use a
|
||||
[template](reference-config-file.md#template), they will have the same
|
||||
extension as the template.
|
||||
|
||||
## Editor history
|
||||
|
||||
Some editors keep usage history stored on disk for future use. This can be a
|
||||
security risk in the sense that sensitive information can leak via recent
|
||||
search patterns or editor commands.
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
Visual Studio Code stores the contents of saved files to allow you to restore or
|
||||
review the contents later. You can disable this feature for all files by unchecking
|
||||
the `workbench.localHistory.enabled` setting in the
|
||||
[Settings editor](https://code.visualstudio.com/docs/getstarted/settings#_settings-editor).
|
||||
|
||||
Alternatively, you can disable this feature for specific files by configuring a
|
||||
[pattern](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)
|
||||
in the `workbench.localHistory.exclude` setting. To exclude unencrypted temporary files generated
|
||||
by `jrnl`, you can set the `**/jrnl*.jrnl` (unless you are using a
|
||||
[template](reference-config-file.md#template)) pattern for the `workbench.localHistory.exclude` setting
|
||||
in the [Settings editor](https://code.visualstudio.com/docs/getstarted/settings#_settings-editor).
|
||||
|
||||
!!! note
|
||||
On Windows, the history location is typically found at
|
||||
`%APPDATA%\Code\User\History`.
|
||||
|
||||
Visual Studio Code also creates a copy of all unsaved files that are open.
|
||||
It stores these copies in a backup location that's automatically cleaned when
|
||||
you save the file. However, if your computer shuts off before you save the file,
|
||||
or the Visual Studio Code process stops unexpectedly, then an unencrypted
|
||||
temporary file may remain on your disk. You can manually delete these files
|
||||
from the backup location.
|
||||
|
||||
!!! note
|
||||
On Windows, the backup location is typically found at
|
||||
`%APPDATA%\Code\Backups`.
|
||||
|
||||
### Vim
|
||||
|
||||
Vim stores progress data in a so called Viminfo file located at `~/.viminfo`
|
||||
which contains all sorts of user data including command line history, search
|
||||
string history, search/substitute patterns, contents of register etc. Also to
|
||||
be able to recover opened files after an unexpected application close Vim uses
|
||||
swap files.
|
||||
|
||||
These options as well as other leaky features can be disabled by setting the
|
||||
`editor` key in the Jrnl settings like this:
|
||||
|
||||
``` yaml
|
||||
editor: "vim -c 'set viminfo= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure'"
|
||||
```
|
||||
|
||||
To disable all plugins and custom configurations and start Vim with the default
|
||||
configuration `-u NONE` can be passed on the command line as well. This will
|
||||
ensure that any rogue plugins or other difficult to catch information leaks are
|
||||
eliminated. The downside to this is that the editor experience will decrease
|
||||
quite a bit.
|
||||
|
||||
To instead let Vim automatically detect when a Jrnl file is being edited an
|
||||
autocommand can be used. Place this in your `~/.vimrc`:
|
||||
|
||||
``` vim
|
||||
autocmd BufNewFile,BufReadPre *.jrnl setlocal viminfo= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure
|
||||
```
|
||||
|
||||
!!! note
|
||||
If you're using a [template](reference-config-file.md#template), you will
|
||||
have to use the template's file extension instead of `.jrnl`.
|
||||
|
||||
See `:h <option>` in Vim for more information about the options mentioned.
|
||||
|
||||
### Neovim
|
||||
|
||||
Neovim strives to be mostly compatible with Vim and has therefore similar
|
||||
functionality as Vim. One difference in Neovim is that the Viminfo file is
|
||||
instead called the ShaDa ("shared data") file which resides in
|
||||
`~/.local/state/nvim` (`~/.local/share/nvim` pre Neovim v0.8.0). The ShaDa file
|
||||
can be disabled in the same way as for Vim.
|
||||
|
||||
``` yaml
|
||||
editor: "nvim -c 'set shada= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure'"
|
||||
```
|
||||
|
||||
`-u NONE` can be passed here as well to start a session with the default configs.
|
||||
|
||||
As for Vim above we can create an autocommand in Vimscript:
|
||||
|
||||
``` vim
|
||||
autocmd BufNewFile,BufReadPre *.jrnl setlocal shada= noswapfile noundofile nobackup nowritebackup noshelltemp history=0 nomodeline secure
|
||||
```
|
||||
|
||||
or the same but in Lua:
|
||||
|
||||
``` lua
|
||||
vim.api.nvim_create_autocmd( {"BufNewFile","BufReadPre" }, {
|
||||
group = vim.api.nvim_create_augroup("PrivateJrnl", {}),
|
||||
pattern = "*.jrnl",
|
||||
callback = function()
|
||||
vim.o.shada = ""
|
||||
vim.o.swapfile = false
|
||||
vim.o.undofile = false
|
||||
vim.o.backup = false
|
||||
vim.o.writebackup = false
|
||||
vim.o.shelltemp = false
|
||||
vim.o.history = 0
|
||||
vim.o.modeline = false
|
||||
vim.o.secure = true
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
!!! note
|
||||
If you're using a [template](reference-config-file.md#template), you will
|
||||
have to use the template's file extension instead of `.jrnl`.
|
||||
|
||||
Please see `:h <option>` in Neovim for more information about the options mentioned.
|
||||
|
||||
## Plausible deniability
|
||||
|
||||
|
@ -100,7 +217,6 @@ In Windows, the keychain is the Windows Credential Manager (WCM), which can't be
|
|||
and can be accessed by any other application running under your username. If this is
|
||||
a concern for you, you may not want to store your password.
|
||||
|
||||
|
||||
## Notice any other risks?
|
||||
|
||||
Please let the maintainers know by [filing an issue on GitHub](https://github.com/jrnl-org/jrnl/issues).
|
||||
|
|
|
@ -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) |
|
||||
| -and | Show only entries that match all conditions, like saying "x AND y" (default: OR) |
|
||||
| -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) |
|
||||
| -not [TAG] | Exclude entries with this tag |
|
||||
| -not -starred | Exclude entries that are starred |
|
||||
| -not -tagged | Exclude entries that are tagged |
|
||||
|
||||
## Searching Options
|
||||
These help you do various tasks with the selected entries from your search.
|
||||
|
|
|
@ -59,7 +59,9 @@ value for journals that already have data in them.
|
|||
|
||||
### template
|
||||
The path to a text file to use as a template for new entries. Only works when you
|
||||
have the `editor` field configured.
|
||||
have the `editor` field configured. If you use a template, the editor's
|
||||
[temporary files](privacy-and-security.md#files-in-transit-from-editor-to-jrnl)
|
||||
will have the same extension as the template.
|
||||
|
||||
### tagsymbols
|
||||
Symbols to be interpreted as tags.
|
||||
|
|
|
@ -118,7 +118,7 @@ div.rst-content {
|
|||
background-size: 100%;
|
||||
}
|
||||
|
||||
a.icon-home:before {
|
||||
.wy-side-nav-search a.icon-home:before {
|
||||
display: block;
|
||||
width: 84px;
|
||||
height: 70px;
|
||||
|
|
49
docs_theme/breadcrumbs.html
Normal file
49
docs_theme/breadcrumbs.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<!--
|
||||
Copied from https://github.com/mkdocs/mkdocs/blob/master/mkdocs/themes/readthedocs/breadcrumbs.html
|
||||
Then lightly modified for accessibility
|
||||
-->
|
||||
|
||||
<div role="navigation" aria-label="breadcrumbs navigation">
|
||||
<ul class="wy-breadcrumbs">
|
||||
<li><a href="{{ nav.homepage.url|url }}" class="icon icon-home" aria-label="{% trans %}Docs{% endtrans %}"></a> »</li>
|
||||
{%- if page %}
|
||||
{%- for doc in page.ancestors[::-1] %}
|
||||
{%- if doc.link %}
|
||||
<li><a href="{{ doc.link|e }}">{{ doc.title }}</a> »</li>
|
||||
{%- else %}
|
||||
<li>{{ doc.title }} »</li>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
<li>{{ page.title }}</li>
|
||||
{%- endif %}
|
||||
<li class="wy-breadcrumbs-aside">
|
||||
{%- block repo %}
|
||||
{%- if page and page.edit_url %}
|
||||
{%- if config.repo_name|lower == 'github' %}
|
||||
<a href="{{ page.edit_url }}" class="icon icon-github"> {% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>
|
||||
{%- elif config.repo_name|lower == 'bitbucket' %}
|
||||
<a href="{{ page.edit_url }}" class="icon icon-bitbucket"> {% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>
|
||||
{%- elif config.repo_name|lower == 'gitlab' %}
|
||||
<a href="{{ page.edit_url }}" class="icon icon-gitlab"> {% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>
|
||||
{%- elif config.repo_name %}
|
||||
<a href="{{ page.edit_url }}">{% trans repo_name=config.repo_name %}Edit on {{ repo_name }}{% endtrans %}</a>
|
||||
{%- else %}
|
||||
<a href="{{ page.edit_url }}">{% trans %}Edit{% endtrans %}</a>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
</li>
|
||||
</ul>
|
||||
{%- if config.theme.prev_next_buttons_location|lower in ['top', 'both']
|
||||
and page and (page.next_page or page.previous_page) %}
|
||||
<div class="rst-breadcrumbs-buttons" role="navigation" aria-label="{% trans %}Breadcrumb Navigation{% endtrans %}">
|
||||
{%- if page.previous_page %}
|
||||
<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>
|
||||
{%- endif %}
|
||||
{%- if page.next_page %}
|
||||
<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>
|
||||
{%- endif %}
|
||||
</div>
|
||||
{%- endif %}
|
||||
<hr/>
|
||||
</div>
|
|
@ -76,22 +76,22 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
|
|||
<section>
|
||||
<i class="icon future"></i>
|
||||
<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>
|
||||
<i class="icon secure"></i>
|
||||
<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—not even you, if you lose your password!</p>
|
||||
</section>
|
||||
<section>
|
||||
<i class="icon sync"></i>
|
||||
<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>
|
||||
<i class="icon github"></i>
|
||||
<h3>Free & 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>
|
||||
<i class="icon folders"></i>
|
||||
|
@ -107,17 +107,17 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
|
|||
<script>
|
||||
new Typed("#typed", {
|
||||
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 <b>-from</b> 2009 <b>-until</b> may<br /><i>`(Displays all entries from January 2009 to last may)`</i>",
|
||||
"jrnl A day on the beach with @beth and @frank. Taggidy-tag-tag.",
|
||||
"jrnl <b>--tags</b><br /><i>`@idea 7<br />@beth 5</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. Tagging them so I can easily look this up later.",
|
||||
"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>--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,
|
||||
backSpeed: 15,
|
||||
backDelay: 2000,
|
||||
typeSpeed: 20, // less is faster
|
||||
backSpeed: 10,
|
||||
backDelay: 2500,
|
||||
loop: true,
|
||||
showCursor: false
|
||||
});
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
mkdocs==1.2.4
|
||||
mkdocs>=1.4
|
||||
jinja2==3.1.2
|
||||
|
|
61
jrnl/args.py
61
jrnl/args.py
|
@ -28,6 +28,43 @@ class WrappingFormatter(argparse.RawTextHelpFormatter):
|
|||
return text
|
||||
|
||||
|
||||
class IgnoreNoneAppendAction(argparse._AppendAction):
|
||||
"""
|
||||
Pass -not without a following string and avoid appending
|
||||
a None value to the excluded list
|
||||
"""
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
if values is not None:
|
||||
super().__call__(parser, namespace, values, option_string)
|
||||
|
||||
|
||||
def parse_not_arg(
|
||||
args: list[str], parsed_args: argparse.Namespace, parser: argparse.ArgumentParser
|
||||
) -> argparse.Namespace:
|
||||
"""
|
||||
It's possible to use -not as a precursor to -starred and -tagged
|
||||
to reverse their behaviour, however this requires some extra logic
|
||||
to parse, and to ensure we still do not allow passing an empty -not
|
||||
"""
|
||||
|
||||
parsed_args.exclude_starred = False
|
||||
parsed_args.exclude_tagged = False
|
||||
|
||||
if "-not-starred" in "".join(args):
|
||||
parsed_args.starred = False
|
||||
parsed_args.exclude_starred = True
|
||||
if "-not-tagged" in "".join(args):
|
||||
parsed_args.tagged = False
|
||||
parsed_args.exclude_tagged = True
|
||||
if "-not" in args and not any(
|
||||
[parsed_args.exclude_starred, parsed_args.exclude_tagged, parsed_args.excluded]
|
||||
):
|
||||
parser.error("argument -not: expected 1 argument")
|
||||
|
||||
return parsed_args
|
||||
|
||||
|
||||
def parse_args(args: list[str] = []) -> argparse.Namespace:
|
||||
"""
|
||||
Argument parsing that is doable before the config is available.
|
||||
|
@ -177,7 +214,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
composing.add_argument(
|
||||
"--template",
|
||||
dest="template",
|
||||
help="Path to template file. Can be a local path, absolute path, or a path relative to $JRNL_TEMPLATE_DIR",
|
||||
help="Path to template file. Can be a local path, absolute path, or a path relative to $XDG_DATA_HOME/jrnl/templates/",
|
||||
)
|
||||
|
||||
read_msg = (
|
||||
|
@ -242,6 +279,12 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
action="store_true",
|
||||
help="Show only starred entries (marked with *)",
|
||||
)
|
||||
reading.add_argument(
|
||||
"-tagged",
|
||||
dest="tagged",
|
||||
action="store_true",
|
||||
help="Show only entries that have at least one tag",
|
||||
)
|
||||
reading.add_argument(
|
||||
"-n",
|
||||
dest="limit",
|
||||
|
@ -254,11 +297,15 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
reading.add_argument(
|
||||
"-not",
|
||||
dest="excluded",
|
||||
nargs=1,
|
||||
nargs="?",
|
||||
default=[],
|
||||
metavar="TAG",
|
||||
action="extend",
|
||||
help="Exclude entries with this tag",
|
||||
metavar="TAG/FLAG",
|
||||
action=IgnoreNoneAppendAction,
|
||||
help=(
|
||||
"If passed a string, will exclude entries with that tag. "
|
||||
"Can be also used before -starred or -tagged flags, to exclude "
|
||||
"starred or tagged entries respectively."
|
||||
),
|
||||
)
|
||||
|
||||
search_options_msg = """ These help you do various tasks with the selected entries from your search.
|
||||
|
@ -393,5 +440,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
# Handle '-123' as a shortcut for '-n 123'
|
||||
num = re.compile(r"^-(\d+)$")
|
||||
args = [num.sub(r"-n \1", arg) for arg in args]
|
||||
parsed_args = parser.parse_intermixed_args(args)
|
||||
parsed_args = parse_not_arg(args, parsed_args, parser)
|
||||
|
||||
return parser.parse_intermixed_args(args)
|
||||
return parsed_args
|
||||
|
|
|
@ -113,14 +113,23 @@ def get_default_config() -> dict[str, Any]:
|
|||
"linewrap": 79,
|
||||
"indent_character": "|",
|
||||
"colors": {
|
||||
"date": "none",
|
||||
"title": "none",
|
||||
"body": "none",
|
||||
"date": "none",
|
||||
"tags": "none",
|
||||
"title": "none",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_default_colors() -> dict[str, Any]:
|
||||
return {
|
||||
"body": "none",
|
||||
"date": "black",
|
||||
"tags": "yellow",
|
||||
"title": "cyan",
|
||||
}
|
||||
|
||||
|
||||
def get_default_journal_path() -> str:
|
||||
journal_data_path = xdg.BaseDirectory.save_data_path(XDG_RESOURCE) or home_dir()
|
||||
return os.path.join(journal_data_path, DEFAULT_JOURNAL_NAME)
|
||||
|
|
|
@ -90,6 +90,8 @@ def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool:
|
|||
args.edit,
|
||||
args.change_time,
|
||||
args.excluded,
|
||||
args.exclude_starred,
|
||||
args.exclude_tagged,
|
||||
args.export,
|
||||
args.end_date,
|
||||
args.today_in_history,
|
||||
|
@ -102,6 +104,7 @@ def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool:
|
|||
args.starred,
|
||||
args.start_date,
|
||||
args.strict,
|
||||
args.tagged,
|
||||
args.tags,
|
||||
)
|
||||
)
|
||||
|
@ -206,7 +209,9 @@ def write_mode(args: "Namespace", config: dict, journal: Journal, **kwargs) -> N
|
|||
# Read template file and pass as raw text into the composer
|
||||
template_data = _read_template_file(args.template, config["template"])
|
||||
raw = _write_in_editor(config, template_data)
|
||||
|
||||
if raw == template_data:
|
||||
logging.error("Write mode: raw text was the same as the template")
|
||||
raise JrnlException(Message(MsgText.NoChangesToTemplate, MsgStyle.NORMAL))
|
||||
elif args.text:
|
||||
logging.debug(f"Write mode: cli text detected: {args.text}")
|
||||
raw = " ".join(args.text).strip()
|
||||
|
@ -317,7 +322,10 @@ def _has_search_args(args: "Namespace") -> bool:
|
|||
args.end_date,
|
||||
args.strict,
|
||||
args.starred,
|
||||
args.tagged,
|
||||
args.excluded,
|
||||
args.exclude_starred,
|
||||
args.exclude_tagged,
|
||||
args.contains,
|
||||
args.limit,
|
||||
)
|
||||
|
@ -343,7 +351,10 @@ def _filter_journal_entries(args: "Namespace", journal: Journal, **kwargs) -> No
|
|||
end_date=args.end_date,
|
||||
strict=args.strict,
|
||||
starred=args.starred,
|
||||
tagged=args.tagged,
|
||||
exclude=args.excluded,
|
||||
exclude_starred=args.exclude_starred,
|
||||
exclude_tagged=args.exclude_tagged,
|
||||
contains=args.contains,
|
||||
)
|
||||
journal.limit(args.limit)
|
||||
|
|
|
@ -12,6 +12,7 @@ from rich.pretty import pretty_repr
|
|||
from jrnl import __version__
|
||||
from jrnl.config import DEFAULT_JOURNAL_KEY
|
||||
from jrnl.config import get_config_path
|
||||
from jrnl.config import get_default_colors
|
||||
from jrnl.config import get_default_config
|
||||
from jrnl.config import get_default_journal_path
|
||||
from jrnl.config import load_config
|
||||
|
@ -147,7 +148,21 @@ def install() -> dict:
|
|||
default_config["encrypt"] = True
|
||||
print_msg(Message(MsgText.JournalEncrypted, MsgStyle.NORMAL))
|
||||
|
||||
# Use colors?
|
||||
use_colors = yesno(Message(MsgText.UseColorsQuestion), default=True)
|
||||
if use_colors:
|
||||
default_config["colors"] = get_default_colors()
|
||||
|
||||
save_config(default_config)
|
||||
|
||||
print_msg(
|
||||
Message(
|
||||
MsgText.InstallComplete,
|
||||
MsgStyle.NORMAL,
|
||||
params={"config_path": get_config_path()},
|
||||
)
|
||||
)
|
||||
|
||||
return default_config
|
||||
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ class Journal:
|
|||
},
|
||||
)
|
||||
)
|
||||
self.write()
|
||||
|
||||
text = self._load(filename)
|
||||
text = self._decrypt(text)
|
||||
|
@ -229,16 +230,19 @@ class Journal:
|
|||
|
||||
def filter(
|
||||
self,
|
||||
tags: list = [],
|
||||
month: str | int | None = None,
|
||||
day: str | int | None = None,
|
||||
year: str | None = None,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
starred: bool = False,
|
||||
strict: bool = False,
|
||||
contains: bool = None,
|
||||
exclude: list = [],
|
||||
tags=[],
|
||||
month=None,
|
||||
day=None,
|
||||
year=None,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
starred=False,
|
||||
tagged=False,
|
||||
exclude_starred=False,
|
||||
exclude_tagged=False,
|
||||
strict=False,
|
||||
contains=None,
|
||||
exclude=[],
|
||||
):
|
||||
"""Removes all entries from the journal that don't match the filter.
|
||||
|
||||
|
@ -259,7 +263,9 @@ class Journal:
|
|||
start_date = time.parse(start_date)
|
||||
|
||||
# If strict mode is on, all tags have to be present in entry
|
||||
tagged = 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):
|
||||
return 0 < len([tag for tag in tags if tag in excluded_tags])
|
||||
|
@ -275,8 +281,9 @@ class Journal:
|
|||
result = [
|
||||
entry
|
||||
for entry in self.entries
|
||||
if (not tags or tagged(entry.tags))
|
||||
and (not starred or entry.starred)
|
||||
if (not tags or has_tags(entry.tags))
|
||||
and (not (starred or exclude_starred) or entry.starred == starred)
|
||||
and (not (tagged or exclude_tagged) or bool(entry.tags) == tagged)
|
||||
and (not month or entry.date.month == compare_d.month)
|
||||
and (not day or entry.date.day == compare_d.day)
|
||||
and (not year or entry.date.year == compare_d.year)
|
||||
|
|
|
@ -28,6 +28,11 @@ class MsgText(Enum):
|
|||
|
||||
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 --- #
|
||||
InstallJournalPathQuestion = """
|
||||
Path to your journal file (leave blank for {default_journal_path}):
|
||||
|
@ -37,6 +42,9 @@ class MsgText(Enum):
|
|||
EncryptJournalQuestion = """
|
||||
Do you want to encrypt your journal? (You can always change this later)
|
||||
"""
|
||||
UseColorsQuestion = """
|
||||
Do you want jrnl to use colors when displaying entries? (You can always change this later)
|
||||
"""
|
||||
YesOrNoPromptDefaultYes = "[Y/n]"
|
||||
YesOrNoPromptDefaultNo = "[y/N]"
|
||||
ContinueUpgrade = "Continue upgrading jrnl?"
|
||||
|
@ -162,7 +170,9 @@ class MsgText(Enum):
|
|||
NoTextReceived = """
|
||||
No entry to save, because no text was received
|
||||
"""
|
||||
|
||||
NoChangesToTemplate = """
|
||||
No entry to save, because the template was not changed
|
||||
"""
|
||||
# --- Upgrade --- #
|
||||
JournalFailedUpgrade = """
|
||||
The following journal{s} failed to upgrade:
|
||||
|
|
|
@ -5,6 +5,9 @@ theme:
|
|||
custom_dir: docs_theme
|
||||
static_templates:
|
||||
- index.html
|
||||
watch:
|
||||
- docs
|
||||
- docs_theme
|
||||
extra_css:
|
||||
- https://fonts.googleapis.com/css?family=Open+Sans:300,600
|
||||
- assets/colors.css
|
||||
|
|
3117
poetry.lock
generated
3117
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -51,10 +51,11 @@ flake8-type-checking = ">=2.2.0"
|
|||
flake8-simplify = ">=0.19"
|
||||
ipdb = "*"
|
||||
isort = ">=5.10"
|
||||
mkdocs = ">=1.0,<1.3"
|
||||
mkdocs = ">=1.4"
|
||||
parse-type = ">=0.6.0"
|
||||
poethepoet = "*"
|
||||
pytest = ">=6.2"
|
||||
pytest-bdd = ">=4.0.1,<6.0"
|
||||
pytest-bdd = ">=6.0"
|
||||
pytest-clarity = "*"
|
||||
pytest-xdist = ">=2.5.0"
|
||||
requests = "*"
|
||||
|
@ -174,8 +175,9 @@ isolated_build = True
|
|||
[testenv]
|
||||
deps =
|
||||
pytest >= 6.2
|
||||
pytest-bdd >=4.0.1,<6.0
|
||||
pytest-bdd >=6.0
|
||||
pytest-xdist >=2.5.0
|
||||
parse-type >=0.6.0
|
||||
toml >=0.10
|
||||
|
||||
commands = pytest {posargs}
|
||||
|
|
|
@ -25,13 +25,16 @@ Feature: Change entry times in journal
|
|||
Scenario Outline: Change flag changes prompted entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -1"
|
||||
Then the output should contain "2020-09-24 09:14 The third entry finally"
|
||||
When we run "jrnl --short"
|
||||
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
|
||||
Y
|
||||
N
|
||||
Y
|
||||
When we run "jrnl -99 --short"
|
||||
When we run "jrnl --short"
|
||||
Then the output should be
|
||||
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.
|
||||
|
|
|
@ -57,6 +57,7 @@ Feature: Journals iteracting with the file system in a way that users can see
|
|||
When we run "jrnl hello world" and enter
|
||||
test.txt
|
||||
n
|
||||
\n
|
||||
Then the output should contain "Journal 'default' created"
|
||||
When we change directory to "subfolder"
|
||||
And we run "jrnl -n 1"
|
||||
|
|
|
@ -5,7 +5,10 @@ Feature: Installing jrnl
|
|||
When we run "jrnl hello world" and enter
|
||||
\n
|
||||
\n
|
||||
Then the output should contain "Journal 'default' created"
|
||||
\n
|
||||
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 config should contain "encrypt: false"
|
||||
And the version in the config file should be up-to-date
|
||||
|
@ -15,6 +18,7 @@ Feature: Installing jrnl
|
|||
When we run "jrnl hello world" and enter
|
||||
default/custom.txt
|
||||
n
|
||||
\n
|
||||
Then the output should contain "Journal 'default' created"
|
||||
And the default journal "custom.txt" should be in the "default" directory
|
||||
And the config should contain "encrypt: false"
|
||||
|
@ -26,6 +30,7 @@ Feature: Installing jrnl
|
|||
When we run "jrnl hello world" and enter
|
||||
~/custom.txt
|
||||
n
|
||||
\n
|
||||
Then the output should contain "Journal 'default' created"
|
||||
And the default journal "custom.txt" should be in the "home" directory
|
||||
And the config should contain "encrypt: false"
|
||||
|
@ -36,10 +41,57 @@ Feature: Installing jrnl
|
|||
When we run "jrnl hello world" and enter
|
||||
encrypted.txt
|
||||
y
|
||||
\n
|
||||
Then the output should contain "Journal will be encrypted"
|
||||
And the default journal "encrypted.txt" should be in the "." directory
|
||||
And the config should contain "encrypt: true"
|
||||
And the version in the config file should be up-to-date
|
||||
When we run "jrnl"
|
||||
Then we should be prompted for a password
|
||||
|
||||
|
||||
Scenario: Install jrnl with colors by default
|
||||
Given we use no config
|
||||
When we run "jrnl hello world" and enter
|
||||
\n
|
||||
\n
|
||||
\n
|
||||
Then the output should contain "Journal 'default' created"
|
||||
And the config should contain
|
||||
colors:
|
||||
body: none
|
||||
date: black
|
||||
tags: yellow
|
||||
title: cyan
|
||||
|
||||
Scenario: Install jrnl without colors
|
||||
Given we use no config
|
||||
When we run "jrnl hello world" and enter
|
||||
\n
|
||||
\n
|
||||
N
|
||||
Then the output should contain "Journal 'default' created"
|
||||
And the config should contain
|
||||
colors:
|
||||
body: none
|
||||
date: none
|
||||
tags: none
|
||||
title: none
|
||||
|
||||
Scenario: Install jrnl with encrypted default journal with no entries
|
||||
Given we use no config
|
||||
When we run "jrnl -1" and enter
|
||||
encrypted.txt
|
||||
y
|
||||
n
|
||||
test
|
||||
test
|
||||
n
|
||||
Then the error output should contain "Journal will be encrypted"
|
||||
And the default journal "encrypted.txt" should be in the "." directory
|
||||
And the config should contain "encrypt: true"
|
||||
And the version in the config file should be up-to-date
|
||||
When we run "jrnl -1" and enter
|
||||
test
|
||||
Then we should be prompted for a password
|
||||
And the error output should contain "no entries found"
|
||||
And the error output should not contain "Wrong password, try again"
|
||||
|
|
|
@ -124,6 +124,50 @@ Feature: Searching in a journal
|
|||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
|
||||
Scenario Outline: Searching for unstarred entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -not -starred"
|
||||
Then we should get no error
|
||||
And the output should contain "2 entries found"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: Searching for tagged entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -tagged"
|
||||
Then we should get no error
|
||||
And the output should contain "3 entries found"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: Searching for untagged entries
|
||||
Given we use the config "empty_folder.yaml"
|
||||
When we run "jrnl Tagged entry. This one has a @tag."
|
||||
Then we should get no error
|
||||
When we run "jrnl Untagged entry. This one has no tag."
|
||||
Then we should get no error
|
||||
When we run "jrnl -not -tagged"
|
||||
Then we should get no error
|
||||
And the output should contain "1 entry found"
|
||||
And the output should contain "This one has no tag"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: Searching for dates
|
||||
Given we use the config "<config_file>"
|
||||
When we run "jrnl -on 2020-08-31 --short"
|
||||
|
|
|
@ -6,9 +6,12 @@ Feature: Using templates
|
|||
Scenario Outline: Template contents should be used in new entry
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
And we append to the editor if opened
|
||||
This is an addition to a templated entry
|
||||
When we run "jrnl --config-override template features/templates/basic.template"
|
||||
And we run "jrnl -1"
|
||||
Then the output should contain "This text is in the basic template"
|
||||
Then the output should contain "This is an addition to a templated entry"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -88,3 +91,4 @@ Feature: Using templates
|
|||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
|
|
@ -201,6 +201,16 @@ def input_method():
|
|||
return ""
|
||||
|
||||
|
||||
@fixture
|
||||
def all_input():
|
||||
return ""
|
||||
|
||||
|
||||
@fixture
|
||||
def command():
|
||||
return ""
|
||||
|
||||
|
||||
@fixture
|
||||
def cache_dir():
|
||||
return {"exists": False, "path": ""}
|
||||
|
@ -221,13 +231,15 @@ def mock_user_input(request, password_input, stdin_input):
|
|||
def _mock_user_input():
|
||||
# user_input needs to be here because we don't know it until cli_run starts
|
||||
user_input = get_fixture(request, "all_input", None)
|
||||
|
||||
if user_input is None:
|
||||
user_input = Exception("Unexpected call for user input")
|
||||
else:
|
||||
user_input = iter(user_input.splitlines())
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 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.input = Mock(side_effect=mock_console_input)
|
||||
|
|
|
@ -19,7 +19,6 @@ from jrnl.time import __get_pdt_calendar
|
|||
from tests.lib.fixtures import FailedKeyring
|
||||
from tests.lib.fixtures import NoKeyring
|
||||
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}"))
|
||||
|
@ -84,16 +83,16 @@ def we_have_type_of_keyring(keyring_type):
|
|||
return TestKeyring()
|
||||
|
||||
|
||||
@given(parse('we use the config "{config_file}"'), target_fixture="config_path")
|
||||
@given(parse("we use no config"), target_fixture="config_path")
|
||||
def we_use_the_config(request, temp_dir, working_dir):
|
||||
config_file = get_fixture(request, "config_file")
|
||||
def we_use_no_config(temp_dir):
|
||||
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
|
||||
os.chdir(temp_dir.name)
|
||||
|
||||
if not config_file:
|
||||
return os.path.join(temp_dir.name, "non_existing_config.yaml")
|
||||
os.chdir(temp_dir.name) # @todo move this step to a more universal place
|
||||
|
||||
# Copy the config file over
|
||||
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)
|
||||
|
||||
|
||||
@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):
|
||||
return password
|
||||
|
||||
|
|
|
@ -32,17 +32,6 @@ def does_directory_contain_n_files(directory_path, number):
|
|||
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(
|
||||
actual_line, expected_line, actual_content, expected_content
|
||||
):
|
||||
|
@ -81,7 +70,7 @@ def spy_wrapper(wrapped_function):
|
|||
|
||||
|
||||
def get_fixture(request, name, default=None):
|
||||
result = default
|
||||
if name in request.node.fixturenames:
|
||||
result = request.getfixturevalue(name)
|
||||
return result
|
||||
try:
|
||||
return request.getfixturevalue(name)
|
||||
except LookupError:
|
||||
return default
|
||||
|
|
|
@ -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_n_files
|
||||
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")
|
||||
|
@ -36,40 +38,38 @@ def output_should_match(regex, cli_run):
|
|||
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}"
|
||||
|
||||
|
||||
@then(parse("the output {should_or_should_not} contain\n{expected_output}"))
|
||||
@then(parse('the output {should_or_should_not} contain "{expected_output}"'))
|
||||
@then(parse("the output {it_should:Should} contain\n{expected_output}", SHOULD_DICT))
|
||||
@then(parse('the output {it_should:Should} contain "{expected_output}"', SHOULD_DICT))
|
||||
@then(
|
||||
parse(
|
||||
"the {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(
|
||||
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(
|
||||
expected_output, which_output_stream, cli_run, should_or_should_not
|
||||
):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
def output_should_contain(expected_output, which_output_stream, cli_run, it_should):
|
||||
output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}"
|
||||
assert expected_output
|
||||
if which_output_stream is None:
|
||||
assert ((expected_output in cli_run["stdout"]) == we_should) or (
|
||||
(expected_output in cli_run["stderr"]) == we_should
|
||||
assert ((expected_output in cli_run["stdout"]) == it_should) or (
|
||||
(expected_output in cli_run["stderr"]) == it_should
|
||||
), output_str
|
||||
|
||||
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":
|
||||
assert (expected_output in cli_run["stderr"]) == we_should, output_str
|
||||
assert (expected_output in cli_run["stderr"]) == it_should, output_str
|
||||
|
||||
else:
|
||||
assert (
|
||||
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}"))
|
||||
|
@ -83,7 +83,7 @@ def output_should_not_contain(expected_output, cli_run):
|
|||
def output_should_be(expected_output, cli_run):
|
||||
actual = cli_run["stdout"].strip()
|
||||
expected = expected_output.strip()
|
||||
assert expected == actual
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@then("the output should be empty")
|
||||
|
@ -135,19 +135,19 @@ def default_journal_location(journal_file, journal_dir, config_on_disk, temp_dir
|
|||
|
||||
@then(
|
||||
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(
|
||||
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 {should_or_should_not} contain\n{some_yaml}"))
|
||||
def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_yaml):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
@then(parse('the config {it_should:Should} contain "{some_yaml}"', SHOULD_DICT))
|
||||
@then(parse("the config {it_should:Should} contain\n{some_yaml}", SHOULD_DICT))
|
||||
def config_var_on_disk(config_on_disk, journal_name, it_should, some_yaml):
|
||||
actual = config_on_disk
|
||||
if journal_name:
|
||||
actual = actual["journals"][journal_name]
|
||||
|
@ -159,26 +159,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
|
||||
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(
|
||||
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(
|
||||
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(parse("the config in memory {should_or_should_not} contain\n{some_yaml}"))
|
||||
def config_var_in_memory(
|
||||
config_in_memory, journal_name, should_or_should_not, some_yaml
|
||||
):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
@then(
|
||||
parse('the config in memory {it_should:Should} contain "{some_yaml}"', SHOULD_DICT)
|
||||
)
|
||||
@then(
|
||||
parse("the config in memory {it_should:Should} contain\n{some_yaml}", SHOULD_DICT)
|
||||
)
|
||||
def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
|
||||
actual = config_in_memory["overrides"]
|
||||
if journal_name:
|
||||
actual = actual["journals"][journal_name]
|
||||
|
@ -190,7 +192,7 @@ def config_var_in_memory(
|
|||
# `expected` objects formatted in yaml only compare one level deep
|
||||
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")
|
||||
|
@ -229,31 +231,27 @@ def journal_directory_should_not_exist(config_on_disk, journal_name):
|
|||
), f'Journal "{journal_name}" does exist'
|
||||
|
||||
|
||||
@then(parse("the journal {should_or_should_not} exist"))
|
||||
def journal_should_not_exist(config_on_disk, should_or_should_not):
|
||||
@then(parse("the journal {it_should:Should} exist", SHOULD_DICT))
|
||||
def journal_should_not_exist(config_on_disk, it_should):
|
||||
scoped_config = scope_config(config_on_disk, "default")
|
||||
expected_path = scoped_config["journal"]
|
||||
|
||||
contains_files = does_directory_contain_files(expected_path, ".")
|
||||
|
||||
if should_or_should_not == "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'"
|
||||
)
|
||||
assert contains_files == it_should
|
||||
|
||||
|
||||
@then(parse('the journal "{journal_name}" directory {should_or_should_not} exist'))
|
||||
def directory_should_not_exist(config_on_disk, should_or_should_not, journal_name):
|
||||
@then(
|
||||
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)
|
||||
expected_path = scoped_config["journal"]
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
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}'))
|
||||
|
@ -388,26 +386,23 @@ def count_elements(number, item, cli_run):
|
|||
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(
|
||||
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):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
assert cli_run["mocks"]["editor"].called == we_should
|
||||
def count_editor_args(num_args, cli_run, editor_state, it_should):
|
||||
assert cli_run["mocks"]["editor"].called == it_should
|
||||
|
||||
if isinstance(num_args, int):
|
||||
assert len(editor_state["command"]) == int(num_args)
|
||||
|
||||
|
||||
@then(parse("the stdin prompt {should_or_should_not} have been called"))
|
||||
def stdin_prompt_called(cli_run, should_or_should_not):
|
||||
we_should = parse_should_or_should_not(should_or_should_not)
|
||||
|
||||
assert cli_run["mocks"]["stdin_input"].called == we_should
|
||||
@then(parse("the stdin prompt {it_should:Should} have been called", SHOULD_DICT))
|
||||
def stdin_prompt_called(cli_run, it_should):
|
||||
assert cli_run["mocks"]["stdin_input"].called == it_should
|
||||
|
||||
|
||||
@then(parse('the editor filename should end with "{suffix}"'))
|
||||
|
|
11
tests/lib/type_builders.py
Normal file
11
tests/lib/type_builders.py
Normal 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,
|
||||
}
|
||||
)
|
|
@ -7,6 +7,7 @@ from contextlib import ExitStack
|
|||
from pytest_bdd import when
|
||||
from pytest_bdd.parsers import parse
|
||||
from pytest_bdd.parsers import re
|
||||
from pytest_bdd.steps import inject_fixture
|
||||
|
||||
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(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"')
|
||||
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
|
||||
|
||||
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:
|
||||
mocks = cli_run["mocks"]
|
||||
factories = cli_run["mock_factories"]
|
||||
|
|
|
@ -23,6 +23,8 @@ def expected_args(**kwargs):
|
|||
"change_time": None,
|
||||
"edit": False,
|
||||
"end_date": None,
|
||||
"exclude_starred": False,
|
||||
"exclude_tagged": False,
|
||||
"today_in_history": False,
|
||||
"month": None,
|
||||
"day": None,
|
||||
|
@ -38,6 +40,7 @@ def expected_args(**kwargs):
|
|||
"starred": False,
|
||||
"start_date": None,
|
||||
"strict": False,
|
||||
"tagged": False,
|
||||
"tags": False,
|
||||
"template": None,
|
||||
"text": [],
|
||||
|
|
Loading…
Add table
Reference in a new issue