mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
Merge branch 'develop' into folder-journal-file-read-1692
This commit is contained in:
commit
034aaed346
30 changed files with 2058 additions and 1690 deletions
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -1,8 +1,26 @@
|
|||
# Changelog
|
||||
|
||||
## [Unreleased](https://github.com/jrnl-org/jrnl/)
|
||||
## [v4.0-beta2](https://pypi.org/project/jrnl/v4.0-beta2/) (2023-04-22)
|
||||
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3...HEAD)
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta...v4.0-beta2)
|
||||
|
||||
**Documentation:**
|
||||
|
||||
- Fix various typos [\#1718](https://github.com/jrnl-org/jrnl/pull/1718) ([hezhizhen](https://github.com/hezhizhen))
|
||||
|
||||
**Packaging:**
|
||||
|
||||
- Update dependency cryptography to v40.0.2 [\#1723](https://github.com/jrnl-org/jrnl/pull/1723) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flakeheaven to v3.3.0 [\#1722](https://github.com/jrnl-org/jrnl/pull/1722) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest to v7.3.1 [\#1720](https://github.com/jrnl-org/jrnl/pull/1720) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency black to v23.3.0 [\#1715](https://github.com/jrnl-org/jrnl/pull/1715) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flake8-type-checking to v2.4.0 [\#1714](https://github.com/jrnl-org/jrnl/pull/1714) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13.3.4 [\#1713](https://github.com/jrnl-org/jrnl/pull/1713) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tox to v4.4.12 [\#1712](https://github.com/jrnl-org/jrnl/pull/1712) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
|
||||
## [v4.0-beta](https://pypi.org/project/jrnl/v4.0-beta/) (2023-03-25)
|
||||
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3...v4.0-beta)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
|
@ -10,6 +28,7 @@
|
|||
- 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))
|
||||
- Add ability to use template with `--template` [\#1667](https://github.com/jrnl-org/jrnl/pull/1667) ([alichtman](https://github.com/alichtman))
|
||||
- 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))
|
||||
|
@ -18,10 +37,14 @@
|
|||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Combinations of `--change-time`, `--delete`, and `--edit` don't work consistently [\#1696](https://github.com/jrnl-org/jrnl/issues/1696)
|
||||
- jrnl doesn't display count of entries deleted after deleting entries with `--delete` [\#1666](https://github.com/jrnl-org/jrnl/issues/1666)
|
||||
- Templated entries should not be saved if the raw text is identical to the original template [\#1652](https://github.com/jrnl-org/jrnl/issues/1652)
|
||||
- Adding an entry with a combination of flags causes a journal overwrite [\#1639](https://github.com/jrnl-org/jrnl/issues/1639)
|
||||
- jrnl does not update version key in config file [\#1638](https://github.com/jrnl-org/jrnl/issues/1638)
|
||||
- jrnl should not create 0-length "encrypted" file on startup [\#1493](https://github.com/jrnl-org/jrnl/issues/1493)
|
||||
- Save empty journal on install instead of just creating a zero-length file [\#1690](https://github.com/jrnl-org/jrnl/pull/1690) ([micahellison](https://github.com/micahellison))
|
||||
- Allow combinations of `--change-time`, `--delete`, and `--edit` while correctly counting the number of entries affected [\#1669](https://github.com/jrnl-org/jrnl/pull/1669) ([wren](https://github.com/wren))
|
||||
- Don't save templated journal entries if the received raw text is the same as the template itself [\#1653](https://github.com/jrnl-org/jrnl/pull/1653) ([Briscoooe](https://github.com/Briscoooe))
|
||||
- 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))
|
||||
|
@ -57,6 +80,16 @@
|
|||
|
||||
**Packaging:**
|
||||
|
||||
- Update dependency cryptography to v40 [\#1710](https://github.com/jrnl-org/jrnl/pull/1710) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency poethepoet to v0.19.0 [\#1709](https://github.com/jrnl-org/jrnl/pull/1709) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tzlocal to v4.3 [\#1708](https://github.com/jrnl-org/jrnl/pull/1708) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency tox to v4.4.7 [\#1707](https://github.com/jrnl-org/jrnl/pull/1707) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13.3.2 [\#1706](https://github.com/jrnl-org/jrnl/pull/1706) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest-xdist to v3.2.1 [\#1705](https://github.com/jrnl-org/jrnl/pull/1705) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency pytest to v7.2.2 [\#1704](https://github.com/jrnl-org/jrnl/pull/1704) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency ipdb to v0.13.13 [\#1703](https://github.com/jrnl-org/jrnl/pull/1703) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency flake8-type-checking to v2.3.1 [\#1702](https://github.com/jrnl-org/jrnl/pull/1702) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency cryptography to v39.0.2 [\#1701](https://github.com/jrnl-org/jrnl/pull/1701) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
- Update dependency rich to v13 [\#1654](https://github.com/jrnl-org/jrnl/pull/1654) ([renovate[bot]](https://github.com/apps/renovate))
|
||||
|
||||
## [v3.3](https://pypi.org/project/jrnl/v3.3/) (2022-10-29)
|
||||
|
|
|
@ -19,7 +19,7 @@ jrnl
|
|||
====
|
||||
|
||||
_To get help, [submit an issue](https://github.com/jrnl-org/jrnl/issues/new/choose) on
|
||||
Github._
|
||||
GitHub._
|
||||
|
||||
`jrnl` is a simple journal application for the command line.
|
||||
|
||||
|
|
|
@ -74,39 +74,45 @@ jrnlimport () {
|
|||
}
|
||||
```
|
||||
|
||||
## Using templates
|
||||
## Using Templates
|
||||
|
||||
!!! note
|
||||
Templates require an [external editor](./advanced.md) be configured.
|
||||
|
||||
A template is a code snippet that makes it easier to use repeated text
|
||||
each time a new journal entry is started. There are two ways you can utilize
|
||||
templates in your entries.
|
||||
Templates are text files that are used for creating structured journals.
|
||||
There are three ways you can use templates:
|
||||
|
||||
### 1. Command line arguments
|
||||
### 1. Use the `--template` command line argument and the default $XDG_DATA_HOME/jrnl/templates directory
|
||||
|
||||
If you had a `template.txt` file with the following contents:
|
||||
`$XDG_DATA_HOME/jrnl/templates` is created by default to store your templates! Create a template (like `default.md`) in this directory and pass `--template FILE_IN_DIR`.
|
||||
|
||||
```sh
|
||||
jrnl --template default.md
|
||||
```
|
||||
|
||||
### 2. Use the `--template` command line argument with a local / absolute path
|
||||
|
||||
You can create a template file with any text. Here is an example:
|
||||
|
||||
```sh
|
||||
# /tmp/template.txt
|
||||
My Personal Journal
|
||||
Title:
|
||||
|
||||
Body:
|
||||
```
|
||||
|
||||
The `template.txt` file could be used to create a new entry with these
|
||||
command line arguments:
|
||||
Then, pass the absolute or relative path to the template file as an argument, and your external
|
||||
editor will open and have your template pre-populated.
|
||||
|
||||
```sh
|
||||
jrnl < template.txt # Imports template.txt as the most recent entry
|
||||
jrnl -1 --edit # Opens the most recent entry in the editor
|
||||
jrnl --template /tmp/template.md
|
||||
```
|
||||
|
||||
### 2. Include the template file in `jrnl.yaml`
|
||||
### 3. Set a default template file in `jrnl.yaml`
|
||||
|
||||
A more efficient way to work with a template file is to declare the file
|
||||
in your [config file](./reference-config-file.md) by changing the `template`
|
||||
setting from `false` to the template file's path in double quotes:
|
||||
If you want a template by default, change the value of `template` in the [config file](./reference-config-file.md)
|
||||
from `false` to the template file's path, wrapped in double quotes:
|
||||
|
||||
```sh
|
||||
...
|
||||
|
@ -114,9 +120,6 @@ template: "/path/to/template.txt"
|
|||
...
|
||||
```
|
||||
|
||||
Changes can be saved as you continue writing the journal entry and will be
|
||||
logged as a new entry in the journal you specified in the original argument.
|
||||
|
||||
!!! tip
|
||||
To read your journal entry or to verify the entry saved, you can use this
|
||||
command: `jrnl -n 1` (Check out [Formats](./formats.md) for more options).
|
||||
|
@ -219,4 +222,3 @@ To cause vi to jump to the end of the last line of the entry you edit, in your c
|
|||
```yaml
|
||||
editor: vi + -c "call cursor('.',strwidth(getline('.')))"
|
||||
```
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "v3.3"
|
||||
__version__ = "v4.0-beta2"
|
||||
|
|
|
@ -211,6 +211,11 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
"Writing", textwrap.dedent(compose_msg).strip()
|
||||
)
|
||||
composing.add_argument("text", metavar="", nargs="*")
|
||||
composing.add_argument(
|
||||
"--template",
|
||||
dest="template",
|
||||
help="Path to template file. Can be a local path, absolute path, or a path relative to $XDG_DATA_HOME/jrnl/templates/",
|
||||
)
|
||||
|
||||
read_msg = (
|
||||
"To find entries from your journal, use any combination of the below filters."
|
||||
|
@ -326,7 +331,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
nargs="?",
|
||||
metavar="DATE",
|
||||
const="now",
|
||||
help="Change timestamp for seleted entries (default: now)",
|
||||
help="Change timestamp for selected entries (default: now)",
|
||||
)
|
||||
exporting.add_argument(
|
||||
"--format",
|
||||
|
@ -355,7 +360,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
"--tags",
|
||||
dest="tags",
|
||||
action="store_true",
|
||||
help="Alias for '--format tags'. Returns a list of all tags and number of occurences",
|
||||
help="Alias for '--format tags'. Returns a list of all tags and number of occurrences",
|
||||
)
|
||||
exporting.add_argument(
|
||||
"--short",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import argparse
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
|
||||
|
@ -34,7 +35,6 @@ YAML_FILE_ENCODING = "utf-8"
|
|||
|
||||
|
||||
def make_yaml_valid_dict(input: list) -> dict:
|
||||
|
||||
"""
|
||||
|
||||
Convert a two-element list of configuration key-value pair into a flat dict.
|
||||
|
@ -73,9 +73,9 @@ def save_config(config: dict, alt_config_path: str | None = None) -> None:
|
|||
yaml.dump(config, f)
|
||||
|
||||
|
||||
def get_config_path() -> str:
|
||||
def get_config_directory() -> str:
|
||||
try:
|
||||
config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
|
||||
return xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
|
||||
except FileExistsError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
|
@ -89,7 +89,13 @@ def get_config_path() -> str:
|
|||
),
|
||||
)
|
||||
|
||||
return os.path.join(config_directory_path or home_dir(), DEFAULT_CONFIG_NAME)
|
||||
|
||||
def get_config_path() -> Path:
|
||||
try:
|
||||
config_directory_path = get_config_directory()
|
||||
except JrnlException:
|
||||
return Path(home_dir(), DEFAULT_CONFIG_NAME)
|
||||
return Path(config_directory_path, DEFAULT_CONFIG_NAME)
|
||||
|
||||
|
||||
def get_default_config() -> dict[str, Any]:
|
||||
|
@ -129,6 +135,15 @@ def get_default_journal_path() -> str:
|
|||
return os.path.join(journal_data_path, DEFAULT_JOURNAL_NAME)
|
||||
|
||||
|
||||
def get_templates_path() -> Path:
|
||||
# jrnl_xdg_resource_path is created by save_data_path if it does not exist
|
||||
jrnl_xdg_resource_path = Path(xdg.BaseDirectory.save_data_path(XDG_RESOURCE))
|
||||
jrnl_templates_path = jrnl_xdg_resource_path / "templates"
|
||||
# Create the directory if needed.
|
||||
jrnl_templates_path.mkdir(exist_ok=True)
|
||||
return jrnl_templates_path
|
||||
|
||||
|
||||
def scope_config(config: dict, journal_name: str) -> dict:
|
||||
if journal_name not in config["journals"]:
|
||||
return config
|
||||
|
|
|
@ -11,11 +11,11 @@ from jrnl import time
|
|||
from jrnl.config import DEFAULT_JOURNAL_KEY
|
||||
from jrnl.config import get_config_path
|
||||
from jrnl.config import get_journal_name
|
||||
from jrnl.config import get_templates_path
|
||||
from jrnl.config import scope_config
|
||||
from jrnl.editor import get_text_from_editor
|
||||
from jrnl.editor import get_text_from_stdin
|
||||
from jrnl.exception import JrnlException
|
||||
from jrnl.journals import Journal
|
||||
from jrnl.journals import open_journal
|
||||
from jrnl.messages import Message
|
||||
from jrnl.messages import MsgStyle
|
||||
|
@ -23,12 +23,13 @@ from jrnl.messages import MsgText
|
|||
from jrnl.output import print_msg
|
||||
from jrnl.output import print_msgs
|
||||
from jrnl.override import apply_overrides
|
||||
from jrnl.path import expand_path
|
||||
from jrnl.path import absolute_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Namespace
|
||||
|
||||
from jrnl.journals import Entry
|
||||
from jrnl.journals import Journal
|
||||
|
||||
|
||||
def run(args: "Namespace"):
|
||||
|
@ -38,8 +39,9 @@ def run(args: "Namespace"):
|
|||
2. Load config
|
||||
3. Run standalone command if it does require config (encrypt, decrypt, etc), then exit
|
||||
4. Load specified journal
|
||||
5. Start write mode, or search mode
|
||||
6. Profit
|
||||
5. Start append mode, or search mode
|
||||
6. Perform actions with results from search mode (if needed)
|
||||
7. Profit
|
||||
"""
|
||||
|
||||
# Run command if possible before config is available
|
||||
|
@ -71,91 +73,170 @@ def run(args: "Namespace"):
|
|||
"args": args,
|
||||
"config": config,
|
||||
"journal": journal,
|
||||
"old_entries": journal.entries,
|
||||
}
|
||||
|
||||
if _is_write_mode(**kwargs):
|
||||
write_mode(**kwargs)
|
||||
if _is_append_mode(**kwargs):
|
||||
append_mode(**kwargs)
|
||||
return
|
||||
|
||||
# If not append mode, then we're in search mode (only 2 modes exist)
|
||||
search_mode(**kwargs)
|
||||
entries_found_count = len(journal)
|
||||
_print_entries_found_count(entries_found_count, args)
|
||||
|
||||
# Actions
|
||||
_perform_actions_on_search_results(**kwargs)
|
||||
|
||||
if entries_found_count != 0 and _has_action_args(args):
|
||||
_print_changed_counts(journal)
|
||||
else:
|
||||
search_mode(**kwargs)
|
||||
# display only occurs if no other action occurs
|
||||
_display_search_results(**kwargs)
|
||||
|
||||
|
||||
def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool:
|
||||
"""Determines if we are in write mode (as opposed to search mode)"""
|
||||
def _perform_actions_on_search_results(**kwargs):
|
||||
args = kwargs["args"]
|
||||
|
||||
# Perform actions (if needed)
|
||||
if args.change_time:
|
||||
_change_time_search_results(**kwargs)
|
||||
|
||||
if args.delete:
|
||||
_delete_search_results(**kwargs)
|
||||
|
||||
# open results in editor (if `--edit` was used)
|
||||
if args.edit:
|
||||
_edit_search_results(**kwargs)
|
||||
|
||||
|
||||
def _is_append_mode(args: "Namespace", config: dict, **kwargs) -> bool:
|
||||
"""Determines if we are in append mode (as opposed to search mode)"""
|
||||
# Are any search filters present? If so, then search mode.
|
||||
write_mode = not any(
|
||||
(
|
||||
args.contains,
|
||||
args.delete,
|
||||
args.edit,
|
||||
args.change_time,
|
||||
args.excluded,
|
||||
args.exclude_starred,
|
||||
args.exclude_tagged,
|
||||
args.export,
|
||||
args.end_date,
|
||||
args.today_in_history,
|
||||
args.month,
|
||||
args.day,
|
||||
args.year,
|
||||
args.limit,
|
||||
args.on_date,
|
||||
args.short,
|
||||
args.starred,
|
||||
args.start_date,
|
||||
args.strict,
|
||||
args.tagged,
|
||||
args.tags,
|
||||
)
|
||||
append_mode = (
|
||||
not _has_search_args(args)
|
||||
and not _has_action_args(args)
|
||||
and not _has_display_args(args)
|
||||
)
|
||||
|
||||
# Might be writing and want to move to editor part of the way through
|
||||
if args.edit and args.text:
|
||||
write_mode = True
|
||||
append_mode = True
|
||||
|
||||
# If the text is entirely tags, then we are also searching (not writing)
|
||||
if (
|
||||
write_mode
|
||||
and args.text
|
||||
and all(word[0] in config["tagsymbols"] for word in " ".join(args.text).split())
|
||||
):
|
||||
write_mode = False
|
||||
if append_mode and args.text and _has_only_tags(config["tagsymbols"], args.text):
|
||||
append_mode = False
|
||||
|
||||
return write_mode
|
||||
return append_mode
|
||||
|
||||
|
||||
def write_mode(args: "Namespace", config: dict, journal: Journal, **kwargs) -> None:
|
||||
def _read_template_file(template_arg: str, template_path_from_config: str) -> str:
|
||||
"""
|
||||
This function is called when either a template file is passed with --template, or config.template is set.
|
||||
|
||||
The processing logic is:
|
||||
If --template was not used: Load the global template file.
|
||||
If --template was used:
|
||||
* Check $XDG_DATA_HOME/jrnl/templates/template_arg.
|
||||
* Check template_arg as an absolute / relative path.
|
||||
|
||||
If a file is found, its contents are returned as a string.
|
||||
If not, a JrnlException is raised.
|
||||
"""
|
||||
logging.debug(
|
||||
"Append mode: Either a template arg was passed, or the global config is set."
|
||||
)
|
||||
|
||||
# If filename is unset, we are in this flow due to a global template being configured
|
||||
if not template_arg:
|
||||
logging.debug("Append mode: Global template configuration detected.")
|
||||
global_template_path = absolute_path(template_path_from_config)
|
||||
try:
|
||||
with open(global_template_path, encoding="utf-8") as f:
|
||||
template_data = f.read()
|
||||
return template_data
|
||||
except FileNotFoundError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.CantReadTemplateGlobalConfig,
|
||||
MsgStyle.ERROR,
|
||||
{
|
||||
"global_template_path": global_template_path,
|
||||
},
|
||||
)
|
||||
)
|
||||
else: # A template CLI arg was passed.
|
||||
logging.debug("Trying to load template from $XDG_DATA_HOME/jrnl/templates/")
|
||||
jrnl_template_dir = get_templates_path()
|
||||
logging.debug(f"Append mode: jrnl templates directory: {jrnl_template_dir}")
|
||||
template_path = jrnl_template_dir / template_arg
|
||||
try:
|
||||
with open(template_path, encoding="utf-8") as f:
|
||||
template_data = f.read()
|
||||
return template_data
|
||||
except FileNotFoundError:
|
||||
logging.debug(
|
||||
f"Couldn't open {template_path}. Treating --template argument like a local / abs path."
|
||||
)
|
||||
pass
|
||||
|
||||
normalized_template_arg_filepath = absolute_path(template_arg)
|
||||
try:
|
||||
with open(normalized_template_arg_filepath, encoding="utf-8") as f:
|
||||
template_data = f.read()
|
||||
return template_data
|
||||
except FileNotFoundError:
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.CantReadTemplateCLIArg,
|
||||
MsgStyle.ERROR,
|
||||
{
|
||||
"normalized_template_arg_filepath": normalized_template_arg_filepath,
|
||||
"jrnl_template_dir": template_path,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def append_mode(args: "Namespace", config: dict, journal: "Journal", **kwargs) -> None:
|
||||
"""
|
||||
Gets input from the user to write to the journal
|
||||
0. Check for a template passed as an argument, or in the global config
|
||||
1. Check for input from cli
|
||||
2. Check input being piped in
|
||||
3. Open editor if configured (prepopulated with template if available)
|
||||
4. Use stdin.read as last resort
|
||||
6. Write any found text to journal, or exit
|
||||
"""
|
||||
logging.debug("Write mode: starting")
|
||||
logging.debug("Append mode: starting")
|
||||
|
||||
if args.text:
|
||||
logging.debug("Write mode: cli text detected: %s", args.text)
|
||||
if args.template or config["template"]:
|
||||
logging.debug(f"Append mode: template CLI arg detected: {args.template}")
|
||||
# Read template file and pass as raw text into the composer
|
||||
template_data = _read_template_file(args.template, config["template"])
|
||||
raw = _write_in_editor(config, template_data)
|
||||
if raw == template_data:
|
||||
logging.error("Append mode: raw text was the same as the template")
|
||||
raise JrnlException(Message(MsgText.NoChangesToTemplate, MsgStyle.NORMAL))
|
||||
elif args.text:
|
||||
logging.debug(f"Append mode: cli text detected: {args.text}")
|
||||
raw = " ".join(args.text).strip()
|
||||
if args.edit:
|
||||
raw = _write_in_editor(config, raw)
|
||||
|
||||
elif not sys.stdin.isatty():
|
||||
logging.debug("Write mode: receiving piped text")
|
||||
logging.debug("Append mode: receiving piped text")
|
||||
raw = sys.stdin.read()
|
||||
|
||||
else:
|
||||
raw = _write_in_editor(config)
|
||||
|
||||
if not raw or raw.isspace():
|
||||
logging.error("Write mode: couldn't get raw text or entry was empty")
|
||||
logging.error("Append mode: couldn't get raw text or entry was empty")
|
||||
raise JrnlException(Message(MsgText.NoTextReceived, MsgStyle.NORMAL))
|
||||
if config["template"] and raw == _get_editor_template(config):
|
||||
logging.error("Write mode: raw text was the same as the template")
|
||||
raise JrnlException(Message(MsgText.NoChangesToTemplate, MsgStyle.NORMAL))
|
||||
|
||||
logging.debug(
|
||||
'Write mode: appending raw text to journal "%s": %s', args.journal_name, raw
|
||||
f"Append mode: appending raw text to journal '{args.journal_name}': {raw}"
|
||||
)
|
||||
journal.new_entry(raw)
|
||||
if args.journal_name != DEFAULT_JOURNAL_KEY:
|
||||
|
@ -167,126 +248,36 @@ def write_mode(args: "Namespace", config: dict, journal: Journal, **kwargs) -> N
|
|||
)
|
||||
)
|
||||
journal.write()
|
||||
logging.debug("Write mode: completed journal.write()")
|
||||
logging.debug("Append mode: completed journal.write()")
|
||||
|
||||
|
||||
def search_mode(args: "Namespace", journal: Journal, **kwargs) -> None:
|
||||
def search_mode(args: "Namespace", journal: "Journal", **kwargs) -> None:
|
||||
"""
|
||||
Search for entries in a journal, then either:
|
||||
1. Send them to configured editor for user manipulation (and also
|
||||
change their timestamps if requested)
|
||||
2. Change their timestamps
|
||||
2. Delete them (with confirmation for each entry)
|
||||
3. Display them (with formatting options)
|
||||
Search for entries in a journal, and return the
|
||||
results. If no search args, then return all results
|
||||
"""
|
||||
kwargs = {
|
||||
**kwargs,
|
||||
"args": args,
|
||||
"journal": journal,
|
||||
"old_entries": journal.entries,
|
||||
}
|
||||
logging.debug("Search mode: starting")
|
||||
|
||||
if _has_search_args(args):
|
||||
_filter_journal_entries(**kwargs)
|
||||
_print_entries_found_count(len(journal), args)
|
||||
|
||||
# Where do the search results go?
|
||||
if args.edit:
|
||||
# If we want to both edit and change time in one action
|
||||
if args.change_time:
|
||||
# Generate a new list instead of assigning so it won't be
|
||||
# modified by _change_time_search_results
|
||||
selected_entries = [e for e in journal.entries]
|
||||
|
||||
no_change_time_prompt = len(journal.entries) == 1
|
||||
_change_time_search_results(no_prompt=no_change_time_prompt, **kwargs)
|
||||
|
||||
# Re-filter the journal enties (_change_time_search_results
|
||||
# puts the filtered entries back); use selected_entries
|
||||
# instead of running _search_journal again, because times
|
||||
# have changed since the original search
|
||||
kwargs["old_entries"] = journal.entries
|
||||
journal.entries = selected_entries
|
||||
|
||||
_edit_search_results(**kwargs)
|
||||
|
||||
elif not journal:
|
||||
# Bail out if there are no entries and we're not editing
|
||||
# If no search args, then return all results (don't filter anything)
|
||||
if not _has_search_args(args) and not _has_display_args(args) and not args.text:
|
||||
logging.debug("Search mode: has no search args")
|
||||
return
|
||||
|
||||
elif args.change_time:
|
||||
_change_time_search_results(**kwargs)
|
||||
|
||||
elif args.delete:
|
||||
_delete_search_results(**kwargs)
|
||||
|
||||
else:
|
||||
_display_search_results(**kwargs)
|
||||
logging.debug("Search mode: has search args")
|
||||
_filter_journal_entries(args, journal)
|
||||
|
||||
|
||||
def _write_in_editor(config: dict, template: str | None = None) -> str:
|
||||
def _write_in_editor(config: dict, prepopulated_text: str | None = None) -> str:
|
||||
if config["editor"]:
|
||||
logging.debug("Write mode: opening editor")
|
||||
if not template:
|
||||
template = _get_editor_template(config)
|
||||
raw = get_text_from_editor(config, template)
|
||||
|
||||
logging.debug("Append mode: opening editor")
|
||||
raw = get_text_from_editor(config, prepopulated_text)
|
||||
else:
|
||||
raw = get_text_from_stdin()
|
||||
|
||||
return raw
|
||||
|
||||
|
||||
def _get_editor_template(config: dict, **kwargs) -> str:
|
||||
logging.debug("Write mode: loading template for entry")
|
||||
|
||||
if not config["template"]:
|
||||
logging.debug("Write mode: no template configured")
|
||||
return ""
|
||||
|
||||
template_path = expand_path(config["template"])
|
||||
|
||||
try:
|
||||
with open(template_path) as f:
|
||||
template = f.read()
|
||||
logging.debug("Write mode: template loaded: %s", template)
|
||||
except OSError:
|
||||
logging.error("Write mode: template not loaded")
|
||||
raise JrnlException(
|
||||
Message(
|
||||
MsgText.CantReadTemplate,
|
||||
MsgStyle.ERROR,
|
||||
{"template": template_path},
|
||||
)
|
||||
)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
def _has_search_args(args: "Namespace") -> bool:
|
||||
return any(
|
||||
(
|
||||
args.on_date,
|
||||
args.today_in_history,
|
||||
args.text,
|
||||
args.month,
|
||||
args.day,
|
||||
args.year,
|
||||
args.start_date,
|
||||
args.end_date,
|
||||
args.strict,
|
||||
args.starred,
|
||||
args.tagged,
|
||||
args.excluded,
|
||||
args.exclude_starred,
|
||||
args.exclude_tagged,
|
||||
args.contains,
|
||||
args.limit,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _filter_journal_entries(args: "Namespace", journal: Journal, **kwargs) -> None:
|
||||
def _filter_journal_entries(args: "Namespace", journal: "Journal", **kwargs) -> None:
|
||||
"""Filter journal entries in-place based upon search args"""
|
||||
if args.on_date:
|
||||
args.start_date = args.end_date = args.on_date
|
||||
|
@ -315,6 +306,7 @@ def _filter_journal_entries(args: "Namespace", journal: Journal, **kwargs) -> No
|
|||
|
||||
|
||||
def _print_entries_found_count(count: int, args: "Namespace") -> None:
|
||||
logging.debug(f"count: {count}")
|
||||
if count == 0:
|
||||
if args.edit or args.change_time:
|
||||
print_msg(Message(MsgText.NothingToModify, MsgStyle.WARNING))
|
||||
|
@ -322,26 +314,26 @@ def _print_entries_found_count(count: int, args: "Namespace") -> None:
|
|||
print_msg(Message(MsgText.NothingToDelete, MsgStyle.WARNING))
|
||||
else:
|
||||
print_msg(Message(MsgText.NoEntriesFound, MsgStyle.NORMAL))
|
||||
elif args.limit:
|
||||
# Don't show count if the user expects a limited number of results
|
||||
return
|
||||
elif args.edit or not (args.change_time or args.delete):
|
||||
# Don't show count if we are ONLY changing the time or deleting entries
|
||||
my_msg = (
|
||||
MsgText.EntryFoundCountSingular
|
||||
if count == 1
|
||||
else MsgText.EntryFoundCountPlural
|
||||
)
|
||||
print_msg(Message(my_msg, MsgStyle.NORMAL, {"num": count}))
|
||||
elif args.limit and args.limit == count:
|
||||
# Don't show count if the user expects a limited number of results
|
||||
logging.debug("args.limit is true-ish")
|
||||
return
|
||||
|
||||
logging.debug("Printing general summary")
|
||||
my_msg = (
|
||||
MsgText.EntryFoundCountSingular if count == 1 else MsgText.EntryFoundCountPlural
|
||||
)
|
||||
print_msg(Message(my_msg, MsgStyle.NORMAL, {"num": count}))
|
||||
|
||||
|
||||
def _other_entries(journal: Journal, entries: list["Entry"]) -> list["Entry"]:
|
||||
def _other_entries(journal: "Journal", entries: list["Entry"]) -> list["Entry"]:
|
||||
"""Find entries that are not in journal"""
|
||||
return [e for e in entries if e not in journal.entries]
|
||||
|
||||
|
||||
def _edit_search_results(
|
||||
config: dict, journal: Journal, old_entries: list["Entry"], **kwargs
|
||||
config: dict, journal: "Journal", old_entries: list["Entry"], **kwargs
|
||||
) -> None:
|
||||
"""
|
||||
1. Send the given journal entries to the user-configured editor
|
||||
|
@ -360,15 +352,18 @@ def _edit_search_results(
|
|||
# separate entries we are not editing
|
||||
other_entries = _other_entries(journal, old_entries)
|
||||
|
||||
# Get stats now for summary later
|
||||
old_stats = _get_predit_stats(journal)
|
||||
|
||||
# Send user to the editor
|
||||
edited = get_text_from_editor(config, journal.editable_str())
|
||||
journal.parse_editable_str(edited)
|
||||
try:
|
||||
edited = get_text_from_editor(config, journal.editable_str())
|
||||
except JrnlException as e:
|
||||
if e.has_message_text(MsgText.NoTextReceived):
|
||||
raise JrnlException(
|
||||
Message(MsgText.NoEditsReceivedJournalNotDeleted, MsgStyle.WARNING)
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
|
||||
# Print summary if available
|
||||
_print_edited_summary(journal, old_stats)
|
||||
journal.parse_editable_str(edited)
|
||||
|
||||
# Put back entries we separated earlier, sort, and write the journal
|
||||
journal.entries += other_entries
|
||||
|
@ -376,15 +371,8 @@ def _edit_search_results(
|
|||
journal.write()
|
||||
|
||||
|
||||
def _print_edited_summary(
|
||||
journal: Journal, old_stats: dict[str, int], **kwargs
|
||||
) -> None:
|
||||
stats = {
|
||||
"added": len(journal) - old_stats["count"],
|
||||
"deleted": old_stats["count"] - len(journal),
|
||||
"modified": len([e for e in journal.entries if e.modified]),
|
||||
}
|
||||
stats["modified"] -= stats["added"]
|
||||
def _print_changed_counts(journal: "Journal", **kwargs) -> None:
|
||||
stats = journal.get_change_counts()
|
||||
msgs = []
|
||||
|
||||
if stats["added"] > 0:
|
||||
|
@ -417,17 +405,18 @@ def _print_edited_summary(
|
|||
print_msgs(msgs)
|
||||
|
||||
|
||||
def _get_predit_stats(journal: Journal) -> dict[str, int]:
|
||||
def _get_predit_stats(journal: "Journal") -> dict[str, int]:
|
||||
return {"count": len(journal)}
|
||||
|
||||
|
||||
def _delete_search_results(
|
||||
journal: Journal, old_entries: list["Entry"], **kwargs
|
||||
journal: "Journal", old_entries: list["Entry"], **kwargs
|
||||
) -> None:
|
||||
entries_to_delete = journal.prompt_action_entries(MsgText.DeleteEntryQuestion)
|
||||
|
||||
journal.entries = old_entries
|
||||
|
||||
if entries_to_delete:
|
||||
journal.entries = old_entries
|
||||
journal.delete_entries(entries_to_delete)
|
||||
|
||||
journal.write()
|
||||
|
@ -435,34 +424,27 @@ def _delete_search_results(
|
|||
|
||||
def _change_time_search_results(
|
||||
args: "Namespace",
|
||||
journal: Journal,
|
||||
journal: "Journal",
|
||||
old_entries: list["Entry"],
|
||||
no_prompt: bool = False,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
) -> None:
|
||||
# separate entries we are not editing
|
||||
other_entries = _other_entries(journal, old_entries)
|
||||
|
||||
if no_prompt:
|
||||
entries_to_change = journal.entries
|
||||
else:
|
||||
entries_to_change = journal.prompt_action_entries(
|
||||
MsgText.ChangeTimeEntryQuestion
|
||||
)
|
||||
# @todo if there's only 1, don't prompt
|
||||
entries_to_change = journal.prompt_action_entries(MsgText.ChangeTimeEntryQuestion)
|
||||
|
||||
if entries_to_change:
|
||||
other_entries += [e for e in journal.entries if e not in entries_to_change]
|
||||
journal.entries = entries_to_change
|
||||
|
||||
date = time.parse(args.change_time)
|
||||
journal.change_date_entries(date)
|
||||
journal.entries = old_entries
|
||||
journal.change_date_entries(date, entries_to_change)
|
||||
|
||||
journal.entries += other_entries
|
||||
journal.sort()
|
||||
journal.write()
|
||||
|
||||
|
||||
def _display_search_results(args: "Namespace", journal: Journal, **kwargs) -> None:
|
||||
def _display_search_results(args: "Namespace", journal: "Journal", **kwargs) -> None:
|
||||
if len(journal) == 0:
|
||||
return
|
||||
|
||||
# Get export format from config file if not provided at the command line
|
||||
args.export = args.export or kwargs["config"].get("display_format")
|
||||
|
||||
|
@ -480,3 +462,50 @@ def _display_search_results(args: "Namespace", journal: Journal, **kwargs) -> No
|
|||
print(exporter.export(journal, args.filename))
|
||||
else:
|
||||
print(journal.pprint())
|
||||
|
||||
|
||||
def _has_search_args(args: "Namespace") -> bool:
|
||||
"""Looking for arguments that filter a journal"""
|
||||
return any(
|
||||
(
|
||||
args.contains,
|
||||
args.tagged,
|
||||
args.excluded,
|
||||
args.exclude_starred,
|
||||
args.exclude_tagged,
|
||||
args.end_date,
|
||||
args.today_in_history,
|
||||
args.month,
|
||||
args.day,
|
||||
args.year,
|
||||
args.limit,
|
||||
args.on_date,
|
||||
args.starred,
|
||||
args.start_date,
|
||||
args.strict, # -and
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _has_action_args(args: "Namespace") -> bool:
|
||||
return any(
|
||||
(
|
||||
args.change_time,
|
||||
args.delete,
|
||||
args.edit,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _has_display_args(args: "Namespace") -> bool:
|
||||
return any(
|
||||
(
|
||||
args.tags,
|
||||
args.short,
|
||||
args.export, # --format
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _has_only_tags(tag_symbols: str, args_text: str) -> bool:
|
||||
return all(word[0] in tag_symbols for word in " ".join(args_text).split())
|
||||
|
|
|
@ -66,7 +66,7 @@ def get_text_from_stdin() -> str:
|
|||
try:
|
||||
raw = sys.stdin.read()
|
||||
except KeyboardInterrupt:
|
||||
logging.error("Write mode: keyboard interrupt")
|
||||
logging.error("Append mode: keyboard interrupt")
|
||||
raise JrnlException(
|
||||
Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR_ON_NEW_LINE),
|
||||
Message(MsgText.JournalNotSaved, MsgStyle.WARNING),
|
||||
|
|
|
@ -7,6 +7,7 @@ from jrnl.output import print_msg
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from jrnl.messages import Message
|
||||
from jrnl.messages import MsgText
|
||||
|
||||
|
||||
class JrnlException(Exception):
|
||||
|
@ -18,3 +19,6 @@ class JrnlException(Exception):
|
|||
def print(self) -> None:
|
||||
for msg in self.messages:
|
||||
print_msg(msg)
|
||||
|
||||
def has_message_text(self, message_text: "MsgText"):
|
||||
return any([m.text == message_text for m in self.messages])
|
||||
|
|
|
@ -90,17 +90,19 @@ class Folder(Journal):
|
|||
for entry in entries_to_delete:
|
||||
self.entries.remove(entry)
|
||||
self._diff_entry_dates.append(entry.date)
|
||||
self.deleted_entry_count += 1
|
||||
|
||||
def change_date_entries(self, date: str) -> None:
|
||||
def change_date_entries(self, date: str, entries_to_change: list["Entry"]) -> None:
|
||||
"""Changes entry dates to given date."""
|
||||
|
||||
date = time.parse(date)
|
||||
|
||||
self._diff_entry_dates.append(date)
|
||||
|
||||
for entry in self.entries:
|
||||
for entry in entries_to_change:
|
||||
self._diff_entry_dates.append(entry.date)
|
||||
entry.date = date
|
||||
entry.modified = True
|
||||
|
||||
def parse_editable_str(self, edited: str) -> None:
|
||||
"""Parses the output of self.editable_str and updates its entries."""
|
||||
|
@ -113,6 +115,8 @@ class Folder(Journal):
|
|||
# modified and how many got deleted later.
|
||||
for entry in mod_entries:
|
||||
entry.modified = not any(entry == old_entry for old_entry in self.entries)
|
||||
|
||||
self.increment_change_counts_by_edit(mod_entries)
|
||||
self.entries = mod_entries
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -51,6 +51,10 @@ class Journal:
|
|||
self.entries = []
|
||||
self.encryption_method = None
|
||||
|
||||
# Track changes to journal in session. Modified is tracked in Entry
|
||||
self.added_entry_count = 0
|
||||
self.deleted_entry_count = 0
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the number of entries"""
|
||||
return len(self.entries)
|
||||
|
@ -305,13 +309,17 @@ class Journal:
|
|||
"""Deletes specific entries from a journal."""
|
||||
for entry in entries_to_delete:
|
||||
self.entries.remove(entry)
|
||||
self.deleted_entry_count += 1
|
||||
|
||||
def change_date_entries(self, date: datetime.datetime | None) -> None:
|
||||
def change_date_entries(
|
||||
self, date: datetime.datetime, entries_to_change: list[Entry]
|
||||
) -> None:
|
||||
"""Changes entry dates to given date."""
|
||||
date = time.parse(date)
|
||||
|
||||
for entry in self.entries:
|
||||
for entry in entries_to_change:
|
||||
entry.date = date
|
||||
entry.modified = True
|
||||
|
||||
def prompt_action_entries(self, msg: MsgText) -> list[Entry]:
|
||||
"""Prompts for action for each entry in a journal, using given message.
|
||||
|
@ -335,7 +343,8 @@ class Journal:
|
|||
|
||||
def new_entry(self, raw: str, date=None, sort: bool = True) -> Entry:
|
||||
"""Constructs a new entry from some raw text input.
|
||||
If a date is given, it will parse and use this, otherwise scan for a date in the input first."""
|
||||
If a date is given, it will parse and use this, otherwise scan for a date in the input first.
|
||||
"""
|
||||
|
||||
raw = raw.replace("\\n ", "\n").replace("\\n", "\n")
|
||||
# Split raw text into title and body
|
||||
|
@ -382,8 +391,24 @@ class Journal:
|
|||
# modified and how many got deleted later.
|
||||
for entry in mod_entries:
|
||||
entry.modified = not any(entry == old_entry for old_entry in self.entries)
|
||||
|
||||
self.increment_change_counts_by_edit(mod_entries)
|
||||
|
||||
self.entries = mod_entries
|
||||
|
||||
def increment_change_counts_by_edit(self, mod_entries: Entry) -> None:
|
||||
if len(mod_entries) > len(self.entries):
|
||||
self.added_entry_count += len(mod_entries) - len(self.entries)
|
||||
else:
|
||||
self.deleted_entry_count += len(self.entries) - len(mod_entries)
|
||||
|
||||
def get_change_counts(self) -> dict:
|
||||
return {
|
||||
"added": self.added_entry_count,
|
||||
"deleted": self.deleted_entry_count,
|
||||
"modified": len([e for e in self.entries if e.modified]),
|
||||
}
|
||||
|
||||
|
||||
class LegacyJournal(Journal):
|
||||
"""Legacy class to support opening journals formatted with the jrnl 1.x
|
||||
|
@ -443,7 +468,7 @@ def open_journal(journal_name: str, config: dict, legacy: bool = False) -> Journ
|
|||
If legacy is True, it will open Journals with legacy classes build for
|
||||
backwards compatibility with jrnl 1.x
|
||||
"""
|
||||
logging.debug("open_journal start")
|
||||
logging.debug(f"open_journal '{journal_name}'")
|
||||
validate_journal_name(journal_name, config)
|
||||
config = config.copy()
|
||||
config["journal"] = expand_path(config["journal"])
|
||||
|
|
|
@ -105,10 +105,16 @@ class MsgText(Enum):
|
|||
|
||||
KeyboardInterruptMsg = "Aborted by user"
|
||||
|
||||
CantReadTemplate = """
|
||||
Unreadable template
|
||||
Could not read template file at:
|
||||
{template}
|
||||
CantReadTemplateGlobalConfig = """
|
||||
Could not read template file defined in config:
|
||||
{global_template_path}
|
||||
"""
|
||||
|
||||
CantReadTemplateCLIArg = """
|
||||
Unable to find a template file based on the passed arg, and no global template was detected.
|
||||
The following filepaths were checked:
|
||||
jrnl XDG Template Directory : {jrnl_template_dir}
|
||||
Local Filepath : {normalized_template_arg_filepath}
|
||||
"""
|
||||
|
||||
NoNamedJournal = "No '{journal_name}' journal configured\n{journals}"
|
||||
|
@ -159,6 +165,14 @@ class MsgText(Enum):
|
|||
https://jrnl.sh/en/stable/external-editors/
|
||||
"""
|
||||
|
||||
NoEditsReceivedJournalNotDeleted = """
|
||||
No text received from editor. Were you trying to delete all the entries?
|
||||
|
||||
This seems a bit drastic, so the operation was cancelled.
|
||||
|
||||
To delete all entries, use the --delete option.
|
||||
"""
|
||||
|
||||
NoEditsReceived = "No edits to save, because nothing was changed"
|
||||
|
||||
NoTextReceived = """
|
||||
|
|
|
@ -9,6 +9,7 @@ from jrnl.config import update_config
|
|||
if TYPE_CHECKING:
|
||||
from argparse import Namespace
|
||||
|
||||
|
||||
# import logging
|
||||
def apply_overrides(args: "Namespace", base_config: dict) -> dict:
|
||||
"""Unpack CLI provided overrides into the configuration tree.
|
||||
|
@ -26,7 +27,6 @@ def apply_overrides(args: "Namespace", base_config: dict) -> dict:
|
|||
|
||||
cfg_with_overrides = base_config.copy()
|
||||
for pairs in overrides:
|
||||
|
||||
pairs = make_yaml_valid_dict(pairs)
|
||||
key_as_dots, override_value = _get_key_and_value_from_pair(pairs)
|
||||
keys = _convert_dots_to_list(key_as_dots)
|
||||
|
|
|
@ -34,7 +34,7 @@ def parse(
|
|||
elif isinstance(date_str, datetime.datetime):
|
||||
return date_str
|
||||
|
||||
# Don't try to parse anything with 6 or less characters and was parsed from the existing journal.
|
||||
# Don't try to parse anything with 6 or fewer characters and was parsed from the existing journal.
|
||||
# It's probably a markdown footnote
|
||||
if len(date_str) <= 6 and bracketed:
|
||||
return None
|
||||
|
@ -83,7 +83,7 @@ def parse(
|
|||
date = datetime.datetime(*date[:6])
|
||||
|
||||
# Ugly heuristic: if the date is more than 4 weeks in the future, we got the year wrong.
|
||||
# Rather then this, we would like to see parsedatetime patched so we can tell it to prefer
|
||||
# Rather than this, we would like to see parsedatetime patched so we can tell it to prefer
|
||||
# past dates
|
||||
dt = datetime.datetime.now() - date
|
||||
if dt.days < -28 and not year_present:
|
||||
|
|
2687
poetry.lock
generated
2687
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "jrnl"
|
||||
version = "v3.3"
|
||||
version = "v4.0-beta2"
|
||||
description = "Collect your thoughts and notes without leaving the command line."
|
||||
authors = [
|
||||
"jrnl contributors <maintainers@jrnl.sh>",
|
||||
|
|
108
tests/bdd/features/actions.feature
Normal file
108
tests/bdd/features/actions.feature
Normal file
|
@ -0,0 +1,108 @@
|
|||
# Copyright © 2012-2023 jrnl contributors
|
||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
Feature: Test combinations of edit, change-time, and delete
|
||||
|
||||
Scenario Outline: --change-time with --edit modifies selected entries
|
||||
Given we use the config "<config_file>"
|
||||
And we write nothing to the editor if opened
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' --edit" and enter
|
||||
Y
|
||||
N
|
||||
Y
|
||||
Then the error output should contain "No text received from editor. Were you trying to delete all the entries?"
|
||||
And the editor should have been called
|
||||
When we run "jrnl -99 --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.
|
||||
2022-04-23 10:30 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
Scenario Outline: --delete with --edit deletes selected entries
|
||||
Given we use the config "<config_file>"
|
||||
And we append to the editor if opened
|
||||
[2023-02-21 10:32] Here is a new entry
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --delete --edit" and enter
|
||||
Y
|
||||
N
|
||||
Y
|
||||
Then the editor should have been called
|
||||
And the error output should contain "3 entries found"
|
||||
And the error output should contain "2 entries deleted"
|
||||
And the error output should contain "1 entry added"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the error output should contain "2 entries found"
|
||||
And the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
2023-02-21 10:32 Here is a new entry
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
Scenario Outline: --change-time with --delete affects appropriate entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
# --change-time is asked first, then --delete
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' --delete" and enter
|
||||
N
|
||||
N
|
||||
Y
|
||||
Y
|
||||
N
|
||||
N
|
||||
Then the error output should contain "3 entries found"
|
||||
And the error output should contain "1 entry deleted"
|
||||
And the error output should contain "1 entry modified"
|
||||
When we run "jrnl -99 --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 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
Scenario Outline: Combining --change-time and --delete and --edit affects appropriate entries
|
||||
Given we use the config "<config_file>"
|
||||
And we append to the editor if opened
|
||||
[2023-02-21 10:32] Here is a new entry
|
||||
And we use the password "test" if prompted
|
||||
# --change-time is asked first, then --delete, then --edit
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' --delete --edit" and enter
|
||||
N
|
||||
Y
|
||||
Y
|
||||
Y
|
||||
Y
|
||||
N
|
||||
Then the error output should contain "3 entries found"
|
||||
And the error output should contain "2 entries deleted"
|
||||
And the error output should contain "1 entry modified" # only 1, because the other was deleted
|
||||
And the error output should contain "1 entry added" # by edit
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2022-04-23 10:30 The third entry finally after weeks without writing.
|
||||
2023-02-21 10:32 Here is a new entry
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
|
@ -9,6 +9,8 @@ Feature: Change entry times in journal
|
|||
Then the output should contain "2020-09-24 09:14 The third entry finally"
|
||||
When we run "jrnl -1 --change-time '2022-04-23 10:30'" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry modified"
|
||||
And the error output should not contain "deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -34,6 +36,8 @@ Feature: Change entry times in journal
|
|||
Y
|
||||
N
|
||||
Y
|
||||
Then the error output should contain "3 entries found"
|
||||
And the error output should contain "2 entries modified"
|
||||
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.
|
||||
|
@ -48,10 +52,15 @@ Feature: Change entry times in journal
|
|||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with nonsense input changes nothing
|
||||
Scenario Outline: Answering "N" to change-time prompt deletes no entries
|
||||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --change-time now asdfasdf"
|
||||
Then the output should contain "No entries to modify"
|
||||
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 -1 --change-time '2023-02-21 10:30'" and enter
|
||||
N
|
||||
Then the error output should not contain "modified"
|
||||
And the error output should not contain "deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -59,16 +68,40 @@ Feature: Change entry times in journal
|
|||
2020-09-24 09:14 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
Scenario Outline: Change time flag with nonsense input changes nothing
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time now asdfasdf"
|
||||
Then the output should contain "No entries to modify"
|
||||
And the error output should not contain "entries modified"
|
||||
And the error output should not contain "entries deleted"
|
||||
When we run "jrnl -99 --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.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with tag only changes tagged entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' @ipsum" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry found"
|
||||
And the error output should contain "1 entry modified"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
|
@ -76,14 +109,16 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 Entry the first.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with multiple tags changes all entries matching any of the tags
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' @ipsum @tagthree" and enter
|
||||
Y
|
||||
Y
|
||||
|
@ -94,14 +129,16 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with -and changes boolean AND of tagged entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' -and @tagone @tagtwo" and enter
|
||||
Y
|
||||
When we run "jrnl -99 --short"
|
||||
|
@ -111,14 +148,16 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 Entry the first.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with -not does not change entries from given tag
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' @tagone -not @ipsum" and enter
|
||||
Y
|
||||
When we run "jrnl -99 --short"
|
||||
|
@ -128,14 +167,16 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with -from search operator only changes entries since that date
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' -from 2020-09-01" and enter
|
||||
Y
|
||||
When we run "jrnl -99 --short"
|
||||
|
@ -145,14 +186,16 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with -to only changes entries up to specified date
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' -to 2020-08-31" and enter
|
||||
Y
|
||||
Y
|
||||
|
@ -163,14 +206,16 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 A second entry in what I hope to be a long series.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with -starred only changes starred entries
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' -starred" and enter
|
||||
Y
|
||||
When we run "jrnl -99 --short"
|
||||
|
@ -180,16 +225,19 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 A second entry in what I hope to be a long series.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with -contains only changes entries containing expression
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' -contains dignissim" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry modified"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
|
@ -197,13 +245,14 @@ Feature: Change entry times in journal
|
|||
2022-04-23 10:30 Entry the first.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
|
||||
|
||||
Scenario Outline: Change time flag with no enties specified changes nothing
|
||||
Scenario Outline: Change time flag with no entries specified changes nothing
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time" and enter
|
||||
|
@ -217,30 +266,8 @@ Feature: Change entry times in journal
|
|||
2020-09-24 09:14 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
|
||||
Scenario Outline: --change-time with --edit modifies selected entries
|
||||
Given we use the config "<config_file>"
|
||||
And we write nothing to the editor if opened
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --change-time '2022-04-23 10:30' --edit" and enter
|
||||
Y
|
||||
N
|
||||
Y
|
||||
Then the error output should contain "No entry to save"
|
||||
And the editor should have been called
|
||||
When we run "jrnl -99 --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.
|
||||
2022-04-23 10:30 The third entry finally after weeks without writing.
|
||||
|
||||
Examples: Configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
# | basic_dayone.yaml | @todo
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
|
|
@ -11,6 +11,8 @@ Feature: Delete entries from journal
|
|||
N
|
||||
N
|
||||
Y
|
||||
Then the error output should contain "3 entries found"
|
||||
And the error output should contain "1 entry deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -28,6 +30,7 @@ Feature: Delete entries from journal
|
|||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete -n 1" and enter
|
||||
N
|
||||
Then the error output should not contain "deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -44,7 +47,7 @@ Feature: Delete entries from journal
|
|||
Scenario Outline: Delete flag with nonsense input deletes nothing (issue #932)
|
||||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete asdfasdf"
|
||||
Then the output should contain "No entries to delete"
|
||||
Then the error output should contain "No entries to delete"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -62,6 +65,8 @@ Feature: Delete entries from journal
|
|||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete @ipsum" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry found"
|
||||
Then the error output should contain "1 entry deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
|
@ -79,6 +84,8 @@ Feature: Delete entries from journal
|
|||
When we run "jrnl --delete @ipsum @tagthree" and enter
|
||||
Y
|
||||
Y
|
||||
Then the error output should contain "2 entries found"
|
||||
And the error output should contain "2 entries deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
|
@ -94,6 +101,8 @@ Feature: Delete entries from journal
|
|||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete -and @tagone @tagtwo" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry found"
|
||||
And the error output should contain "1 entry deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
|
@ -110,6 +119,8 @@ Feature: Delete entries from journal
|
|||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete @tagone -not @ipsum" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry found"
|
||||
And the error output should contain "1 entry deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -126,6 +137,8 @@ Feature: Delete entries from journal
|
|||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete -from 2020-09-01" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry found"
|
||||
And the error output should contain "1 entry deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -143,6 +156,8 @@ Feature: Delete entries from journal
|
|||
When we run "jrnl --delete -to 2020-08-31" and enter
|
||||
Y
|
||||
Y
|
||||
Then the error output should contain "2 entries found"
|
||||
And the error output should contain "2 entries deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-09-24 09:14 The third entry finally after weeks without writing.
|
||||
|
@ -158,6 +173,7 @@ Feature: Delete entries from journal
|
|||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete -starred" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-29 11:11 Entry the first.
|
||||
|
@ -174,6 +190,8 @@ Feature: Delete entries from journal
|
|||
Given we use the config "<config_file>"
|
||||
When we run "jrnl --delete -contains dignissim" and enter
|
||||
Y
|
||||
Then the error output should contain "1 entry found"
|
||||
And the error output should contain "1 entry deleted"
|
||||
When we run "jrnl -99 --short"
|
||||
Then the output should be
|
||||
2020-08-31 14:32 A second entry in what I hope to be a long series.
|
||||
|
|
|
@ -372,3 +372,29 @@ Feature: Searching in a journal
|
|||
2013-06-17 20:38 This entry has a location.
|
||||
|
||||
2013-07-17 11:38 This entry is starred!
|
||||
|
||||
Scenario Outline: Searching the most recent entry should not show found count
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -1"
|
||||
Then the error output should not contain "1 entry found"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: Searching for more entries than are in the journal should show found count
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl -4"
|
||||
Then the error output should contain "3 entries found"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
|
|
@ -32,3 +32,46 @@ Feature: Using templates
|
|||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: --template nonexistent_file should throw an error
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --template this_template_does_not_exist.template"
|
||||
Then we should get an error
|
||||
Then the error output should contain "Unable to find a template file based on the passed arg"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: --template local_filepath should be used in new entry
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --template features/templates/basic.template"
|
||||
Then the output should contain "No entry to save, because the template was not changed"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: --template file_in_XDG_templates_dir should be used in new entry
|
||||
Given we use the config "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
And we copy the template "basic.template" to the default templates folder
|
||||
When we run "jrnl --template basic.template"
|
||||
Then the output should contain "No entry to save, because the template was not changed"
|
||||
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
|
|
@ -76,21 +76,21 @@ Feature: Writing new entries.
|
|||
| basic_dayone.yaml |
|
||||
| basic_folder.yaml |
|
||||
|
||||
Scenario Outline: Writing an empty entry from the editor should yield "No entry to save" message
|
||||
Scenario Outline: Clearing the editor's contents should yield "No text received" message
|
||||
Given we use the config "<config_file>"
|
||||
And we write nothing to the editor if opened
|
||||
And we use the password "test" if prompted
|
||||
When we run "jrnl --edit"
|
||||
Then the error output should contain "No entry to save, because no text was received"
|
||||
Then the error output should contain "No text received from editor. Were you trying to delete all the entries?"
|
||||
And the editor should have been called
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
| editor.yaml |
|
||||
| editor_empty_folder.yaml |
|
||||
| dayone.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_onefile.yaml |
|
||||
| config_file |
|
||||
| editor.yaml |
|
||||
| basic_onefile.yaml |
|
||||
| basic_encrypted.yaml |
|
||||
| basic_dayone.yaml |
|
||||
| basic_folder.yaml |
|
||||
|
||||
Scenario Outline: Writing an empty entry from the command line should yield "No entry to save" message
|
||||
Given we use the config "<config_file>"
|
||||
|
@ -236,7 +236,9 @@ Feature: Writing new entries.
|
|||
And we append to the editor if opened
|
||||
[2021-11-13] worked on jrnl tests
|
||||
When we run "jrnl --edit"
|
||||
Then the output should contain "1 entry added"
|
||||
Then the error output should contain "3 entries found"
|
||||
And the error output should contain "1 entry added"
|
||||
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -254,7 +256,8 @@ Feature: Writing new entries.
|
|||
[2021-11-12] worked on jrnl tests again
|
||||
[2021-11-13] worked on jrnl tests a little bit more
|
||||
When we run "jrnl --edit"
|
||||
Then the error output should contain "3 entries added"
|
||||
Then the error output should contain "3 entries found"
|
||||
And the error output should contain "3 entries added"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -271,7 +274,7 @@ Feature: Writing new entries.
|
|||
[2021-11-13] I am replacing my whole journal with this entry
|
||||
When we run "jrnl --edit"
|
||||
Then the output should contain "2 entries deleted"
|
||||
Then the output should contain "3 entries modified"
|
||||
And the output should contain "1 entry modified"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -287,8 +290,7 @@ Feature: Writing new entries.
|
|||
And we write to the editor if opened
|
||||
[2021-11-13] I am replacing the last entry with this entry
|
||||
When we run "jrnl --edit -1"
|
||||
Then the output should contain
|
||||
1 entry modified
|
||||
Then the error output should contain "1 entry modified"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -304,8 +306,8 @@ Feature: Writing new entries.
|
|||
And we append to the editor if opened
|
||||
This is a small addendum to my latest entry.
|
||||
When we run "jrnl --edit"
|
||||
Then the output should contain
|
||||
1 entry modified
|
||||
Then the error output should contain "3 entries found"
|
||||
And the error output should contain "1 entry modified"
|
||||
|
||||
Examples: configs
|
||||
| config_file |
|
||||
|
@ -341,6 +343,7 @@ Feature: Writing new entries.
|
|||
And we append to the editor if opened
|
||||
@newtag
|
||||
When we run "jrnl --edit -1"
|
||||
Then the error output should contain "1 entry modified"
|
||||
When we run "jrnl --tags @newtag"
|
||||
Then the output should contain
|
||||
1 entry found
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
from pytest_bdd import scenarios
|
||||
|
||||
scenarios("features/actions.feature")
|
||||
scenarios("features/build.feature")
|
||||
scenarios("features/config_file.feature")
|
||||
scenarios("features/core.feature")
|
||||
|
|
|
@ -89,6 +89,7 @@ def cli_run(
|
|||
mock_user_input,
|
||||
mock_overrides,
|
||||
mock_default_journal_path,
|
||||
mock_default_templates_path,
|
||||
):
|
||||
# Check if we need more mocks
|
||||
mock_factories.update(mock_args)
|
||||
|
@ -98,6 +99,7 @@ def cli_run(
|
|||
mock_factories.update(mock_config_path)
|
||||
mock_factories.update(mock_user_input)
|
||||
mock_factories.update(mock_default_journal_path)
|
||||
mock_factories.update(mock_default_templates_path)
|
||||
|
||||
return {
|
||||
"status": 0,
|
||||
|
@ -179,6 +181,16 @@ def mock_default_journal_path(temp_dir):
|
|||
}
|
||||
|
||||
|
||||
@fixture
|
||||
def mock_default_templates_path(temp_dir):
|
||||
templates_path = Path(temp_dir.name, "templates")
|
||||
return {
|
||||
"get_templates_path": lambda: patch(
|
||||
"jrnl.controller.get_templates_path", return_value=templates_path
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@fixture
|
||||
def temp_dir():
|
||||
return tempfile.TemporaryDirectory()
|
||||
|
|
|
@ -125,6 +125,22 @@ def we_use_the_config(request, temp_dir, working_dir, config_file):
|
|||
return config_dest
|
||||
|
||||
|
||||
@given(
|
||||
parse('we copy the template "{template_file}" to the default templates folder'),
|
||||
target_fixture="default_templates_path",
|
||||
)
|
||||
def we_copy_the_template(request, temp_dir, working_dir, template_file):
|
||||
# Move into temp dir as cwd
|
||||
os.chdir(temp_dir.name) # @todo move this step to a more universal place
|
||||
|
||||
# Copy template over
|
||||
template_source = os.path.join(working_dir, "data", "templates", template_file)
|
||||
template_dest = os.path.join(temp_dir.name, "templates", template_file)
|
||||
os.makedirs(os.path.dirname(template_dest), exist_ok=True)
|
||||
shutil.copy2(template_source, template_dest)
|
||||
return template_dest
|
||||
|
||||
|
||||
@given(parse('the config "{config_file}" exists'), target_fixture="config_path")
|
||||
def config_exists(config_file, temp_dir, working_dir):
|
||||
config_source = os.path.join(working_dir, "data", "configs", config_file)
|
||||
|
|
|
@ -25,6 +25,11 @@ def should_get_no_error(cli_run):
|
|||
assert cli_run["status"] == 0, cli_run["status"]
|
||||
|
||||
|
||||
@then("we should get an error")
|
||||
def should_get_an_error(cli_run):
|
||||
assert cli_run["status"] != 0, cli_run["status"]
|
||||
|
||||
|
||||
@then(parse("the output should match\n{regex}"))
|
||||
@then(parse('the output should match "{regex}"'))
|
||||
def output_should_match(regex, cli_run):
|
||||
|
@ -192,12 +197,12 @@ def config_var_in_memory(config_in_memory, journal_name, it_should, some_yaml):
|
|||
|
||||
@then("we should be prompted for a password")
|
||||
def password_was_called(cli_run):
|
||||
assert cli_run["mocks"]["user_input"].called
|
||||
assert cli_run["mocks"]["user_input"].return_value.input.called
|
||||
|
||||
|
||||
@then("we should not be prompted for a password")
|
||||
def password_was_not_called(cli_run):
|
||||
assert not cli_run["mocks"]["user_input"].called
|
||||
assert not cli_run["mocks"]["user_input"].return_value.input.called
|
||||
|
||||
|
||||
@then(parse("the cache directory should contain the files\n{file_list}"))
|
||||
|
|
|
@ -18,15 +18,17 @@ def random_string():
|
|||
|
||||
|
||||
@pytest.mark.parametrize("export_format", ["pretty", "short"])
|
||||
@mock.patch("builtins.print")
|
||||
@mock.patch("jrnl.controller.Journal.pprint")
|
||||
def test_display_search_results_pretty_short(mock_pprint, mock_print, export_format):
|
||||
def test_display_search_results_pretty_short(export_format):
|
||||
mock_args = parse_args(["--format", export_format])
|
||||
test_journal = mock.Mock(wraps=jrnl.journals.Journal)
|
||||
|
||||
test_journal = jrnl.journals.Journal()
|
||||
test_journal.new_entry("asdf")
|
||||
|
||||
test_journal.pprint = mock.Mock()
|
||||
|
||||
_display_search_results(mock_args, test_journal)
|
||||
|
||||
mock_print.assert_called_once_with(mock_pprint.return_value)
|
||||
test_journal.pprint.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -40,7 +42,9 @@ def test_display_search_results_builtin_plugins(
|
|||
test_filename = random_string
|
||||
mock_args = parse_args(["--format", export_format, "--file", test_filename])
|
||||
|
||||
test_journal = mock.Mock(wraps=jrnl.journals.Journal)
|
||||
test_journal = jrnl.journals.Journal()
|
||||
test_journal.new_entry("asdf")
|
||||
|
||||
mock_export = mock.Mock()
|
||||
mock_exporter.return_value.export = mock_export
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ def build_card_header(datestr):
|
|||
|
||||
class TestFancy:
|
||||
def test_too_small_linewrap(self, datestr):
|
||||
|
||||
journal = "test_journal"
|
||||
content = build_card_header(datestr)
|
||||
|
||||
|
|
|
@ -101,7 +101,6 @@ def test_get_kv_from_pair():
|
|||
|
||||
class TestDotNotationToList:
|
||||
def test_unpack_dots_to_list(self):
|
||||
|
||||
keys = "a.b.c.d.e.f"
|
||||
keys_list = _convert_dots_to_list(keys)
|
||||
assert len(keys_list) == 6
|
||||
|
|
|
@ -42,6 +42,7 @@ def expected_args(**kwargs):
|
|||
"strict": False,
|
||||
"tagged": False,
|
||||
"tags": False,
|
||||
"template": None,
|
||||
"text": [],
|
||||
"config_override": [],
|
||||
"config_file_path": "",
|
||||
|
@ -229,7 +230,6 @@ def test_version_alone():
|
|||
|
||||
|
||||
def test_editor_override():
|
||||
|
||||
parsed_args = cli_as_dict('--config-override editor "nano"')
|
||||
assert parsed_args == expected_args(config_override=[["editor", "nano"]])
|
||||
|
||||
|
@ -293,7 +293,6 @@ class TestDeserialization:
|
|||
],
|
||||
)
|
||||
def test_deserialize_multiword_strings(self, input_str):
|
||||
|
||||
runtime_config = make_yaml_valid_dict(input_str)
|
||||
assert runtime_config.__class__ == dict
|
||||
assert input_str[0] in runtime_config
|
||||
|
|
Loading…
Add table
Reference in a new issue