mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
Merge branch 'develop' into rich-instead-of-ansiwrap-1191
This commit is contained in:
commit
8cd8a5ba17
36 changed files with 1030 additions and 484 deletions
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -12,7 +12,7 @@ Here are some key points to include in your description:
|
|||
|
||||
### Checklist
|
||||
|
||||
- [ ] I have read the [contributing doc](https://github.com/jrnl-org/jrnl/blob/develop/CONTRIBUTING.md).
|
||||
- [ ] I have read the [contributing doc](https://github.com/jrnl-org/jrnl/blob/develop/docs/contributing.md).
|
||||
- [ ] I have included a link to the relevant issue number.
|
||||
- [ ] I have checked to ensure there aren't other open [pull requests](../pulls)
|
||||
for the same issue.
|
||||
|
|
51
CHANGELOG.md
51
CHANGELOG.md
|
@ -1,8 +1,40 @@
|
|||
# Changelog
|
||||
|
||||
## [Unreleased](https://github.com/jrnl-org/jrnl/)
|
||||
## [v4.0-beta3](https://pypi.org/project/jrnl/v4.0-beta3/) (2023-04-29)
|
||||
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v3.3...HEAD)
|
||||
[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v4.0-beta2...v4.0-beta3)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- jrnl reads extraneous text files when reading folder journal [\#1692](https://github.com/jrnl-org/jrnl/issues/1692)
|
||||
- jrnl crashes when adding tag argument after `--change-time` [\#1644](https://github.com/jrnl-org/jrnl/issues/1644)
|
||||
- Only read text files that look like entries when opening folder journal [\#1697](https://github.com/jrnl-org/jrnl/pull/1697) ([micahellison](https://github.com/micahellison))
|
||||
|
||||
**Documentation:**
|
||||
|
||||
- Update contributing.md links in documentation [\#1726](https://github.com/jrnl-org/jrnl/pull/1726) ([ahosking](https://github.com/ahosking))
|
||||
|
||||
## [v4.0-beta2](https://pypi.org/project/jrnl/v4.0-beta2/) (2023-04-22)
|
||||
|
||||
[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 +42,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 +51,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 +94,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.
|
||||
|
||||
|
@ -70,7 +70,7 @@ src="https://opencollective.com/jrnl/contributors.svg?width=890&button=false"
|
|||
/></a>
|
||||
|
||||
If you'd also like to help make `jrnl` better, please see our [contributing
|
||||
documentation](CONTRIBUTING.md).
|
||||
documentation](docs/contributing.md).
|
||||
|
||||
### Financial Backers
|
||||
|
||||
|
|
|
@ -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-beta3"
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
import codecs
|
||||
import fnmatch
|
||||
import os
|
||||
import pathlib
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from jrnl import time
|
||||
|
@ -13,14 +13,11 @@ from .Journal import Journal
|
|||
if TYPE_CHECKING:
|
||||
from jrnl.journals import Entry
|
||||
|
||||
|
||||
def get_files(journal_config: str) -> list[str]:
|
||||
"""Searches through sub directories starting with journal_config and find all text files"""
|
||||
filenames = []
|
||||
for root, dirnames, f in os.walk(journal_config):
|
||||
for filename in fnmatch.filter(f, "*.txt"):
|
||||
filenames.append(os.path.join(root, filename))
|
||||
return filenames
|
||||
# glob search patterns for folder/file structure
|
||||
DIGIT_PATTERN = "[0123456789]"
|
||||
YEAR_PATTERN = DIGIT_PATTERN * 4
|
||||
MONTH_PATTERN = "[01]" + DIGIT_PATTERN
|
||||
DAY_PATTERN = "[0123]" + DIGIT_PATTERN + ".txt"
|
||||
|
||||
|
||||
class Folder(Journal):
|
||||
|
@ -35,12 +32,15 @@ class Folder(Journal):
|
|||
def open(self) -> "Folder":
|
||||
filenames = []
|
||||
self.entries = []
|
||||
filenames = get_files(self.config["journal"])
|
||||
for filename in filenames:
|
||||
with codecs.open(filename, "r", "utf-8") as f:
|
||||
journal = f.read()
|
||||
self.entries.extend(self._parse(journal))
|
||||
self.sort()
|
||||
|
||||
if os.path.exists(self.config["journal"]):
|
||||
filenames = Folder._get_files(self.config["journal"])
|
||||
for filename in filenames:
|
||||
with codecs.open(filename, "r", "utf-8") as f:
|
||||
journal = f.read()
|
||||
self.entries.extend(self._parse(journal))
|
||||
self.sort()
|
||||
|
||||
return self
|
||||
|
||||
def write(self) -> None:
|
||||
|
@ -81,7 +81,7 @@ class Folder(Journal):
|
|||
journal_file.write(journal)
|
||||
# look for and delete empty files
|
||||
filenames = []
|
||||
filenames = get_files(self.config["journal"])
|
||||
filenames = Folder._get_files(self.config["journal"])
|
||||
for filename in filenames:
|
||||
if os.stat(filename).st_size <= 0:
|
||||
os.remove(filename)
|
||||
|
@ -91,17 +91,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."""
|
||||
|
@ -114,4 +116,42 @@ 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
|
||||
def _get_files(journal_path: str) -> list[str]:
|
||||
"""Searches through sub directories starting with journal_path and find all text files that look like entries"""
|
||||
for year_folder in Folder._get_year_folders(pathlib.Path(journal_path)):
|
||||
for month_folder in Folder._get_month_folders(year_folder):
|
||||
yield from Folder._get_day_files(month_folder)
|
||||
|
||||
@staticmethod
|
||||
def _get_year_folders(path: pathlib.Path) -> list[pathlib.Path]:
|
||||
for child in path.glob(YEAR_PATTERN):
|
||||
if child.is_dir():
|
||||
yield child
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def _get_month_folders(path: pathlib.Path) -> list[pathlib.Path]:
|
||||
for child in path.glob(MONTH_PATTERN):
|
||||
if int(child.name) > 0 and int(child.name) <= 12 and path.is_dir():
|
||||
yield child
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def _get_day_files(path: pathlib.Path) -> list[str]:
|
||||
for child in path.glob(DAY_PATTERN):
|
||||
if (
|
||||
int(child.stem) > 0
|
||||
and int(child.stem) <= 31
|
||||
and time.is_valid_date(
|
||||
year=int(path.parent.name),
|
||||
month=int(path.name),
|
||||
day=int(child.stem),
|
||||
)
|
||||
and child.is_file()
|
||||
):
|
||||
yield str(child)
|
||||
|
|
|
@ -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)
|
||||
|
|
12
jrnl/time.py
12
jrnl/time.py
|
@ -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,9 +83,17 @@ 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:
|
||||
date = date.replace(date.year - 1)
|
||||
return date
|
||||
|
||||
|
||||
def is_valid_date(year: int, month: int, day: int) -> bool:
|
||||
try:
|
||||
datetime.datetime(year, month, day)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
|
266
poetry.lock
generated
266
poetry.lock
generated
|
@ -1,4 +1,19 @@
|
|||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "ansiwrap"
|
||||
version = "0.8.4"
|
||||
description = "textwrap, but savvy to ANSI colors and styles"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ansiwrap-0.8.4-py2.py3-none-any.whl", hash = "sha256:7b053567c88e1ad9eed030d3ac41b722125e4c1271c8a99ade797faff1f49fb1"},
|
||||
{file = "ansiwrap-0.8.4.zip", hash = "sha256:ca0c740734cde59bf919f8ff2c386f74f9a369818cdc60efe94893d01ea8d9b7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
textwrap3 = ">=0.9.2"
|
||||
|
||||
[[package]]
|
||||
name = "appnope"
|
||||
|
@ -42,25 +57,6 @@ six = "*"
|
|||
[package.extras]
|
||||
test = ["astroid", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "22.2.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
|
||||
{file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
|
||||
dev = ["attrs[docs,tests]"]
|
||||
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
|
||||
tests = ["attrs[tests-no-zope]", "zope.interface"]
|
||||
tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
|
||||
|
||||
[[package]]
|
||||
name = "backcall"
|
||||
version = "0.2.0"
|
||||
|
@ -75,37 +71,37 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "23.1.0"
|
||||
version = "23.3.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "black-23.1.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221"},
|
||||
{file = "black-23.1.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26"},
|
||||
{file = "black-23.1.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b"},
|
||||
{file = "black-23.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"},
|
||||
{file = "black-23.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074"},
|
||||
{file = "black-23.1.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27"},
|
||||
{file = "black-23.1.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648"},
|
||||
{file = "black-23.1.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958"},
|
||||
{file = "black-23.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a"},
|
||||
{file = "black-23.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481"},
|
||||
{file = "black-23.1.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad"},
|
||||
{file = "black-23.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8"},
|
||||
{file = "black-23.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24"},
|
||||
{file = "black-23.1.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6"},
|
||||
{file = "black-23.1.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd"},
|
||||
{file = "black-23.1.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580"},
|
||||
{file = "black-23.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468"},
|
||||
{file = "black-23.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753"},
|
||||
{file = "black-23.1.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651"},
|
||||
{file = "black-23.1.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06"},
|
||||
{file = "black-23.1.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739"},
|
||||
{file = "black-23.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9"},
|
||||
{file = "black-23.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555"},
|
||||
{file = "black-23.1.0-py3-none-any.whl", hash = "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32"},
|
||||
{file = "black-23.1.0.tar.gz", hash = "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac"},
|
||||
{file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"},
|
||||
{file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"},
|
||||
{file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"},
|
||||
{file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"},
|
||||
{file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"},
|
||||
{file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"},
|
||||
{file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"},
|
||||
{file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"},
|
||||
{file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"},
|
||||
{file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"},
|
||||
{file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"},
|
||||
{file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"},
|
||||
{file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"},
|
||||
{file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"},
|
||||
{file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"},
|
||||
{file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"},
|
||||
{file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"},
|
||||
{file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"},
|
||||
{file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"},
|
||||
{file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"},
|
||||
{file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"},
|
||||
{file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"},
|
||||
{file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
|
||||
{file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"},
|
||||
{file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -374,35 +370,31 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "39.0.1"
|
||||
version = "40.0.2"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-win32.whl", hash = "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a"},
|
||||
{file = "cryptography-39.0.1.tar.gz", hash = "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-win32.whl", hash = "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9"},
|
||||
{file = "cryptography-40.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b"},
|
||||
{file = "cryptography-40.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b"},
|
||||
{file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e"},
|
||||
{file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a"},
|
||||
{file = "cryptography-40.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958"},
|
||||
{file = "cryptography-40.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b"},
|
||||
{file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636"},
|
||||
{file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e"},
|
||||
{file = "cryptography-40.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404"},
|
||||
{file = "cryptography-40.0.2.tar.gz", hash = "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -411,10 +403,10 @@ cffi = ">=1.12"
|
|||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
|
||||
pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"]
|
||||
pep8test = ["black", "check-manifest", "mypy", "ruff"]
|
||||
sdist = ["setuptools-rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"]
|
||||
test = ["iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
tox = ["tox"]
|
||||
|
||||
|
@ -501,19 +493,19 @@ tests = ["asttokens", "littleutils", "pytest", "rich"]
|
|||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.9.0"
|
||||
version = "3.11.0"
|
||||
description = "A platform independent file lock."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"},
|
||||
{file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"},
|
||||
{file = "filelock-3.11.0-py3-none-any.whl", hash = "sha256:f08a52314748335c6460fc8fe40cd5638b85001225db78c2aa01c8c0db83b318"},
|
||||
{file = "filelock-3.11.0.tar.gz", hash = "sha256:3618c0da67adcc0506b015fd11ef7faf1b493f0b40d87728e19986b536890c37"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
|
||||
testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
|
||||
docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
|
@ -589,14 +581,14 @@ flake8 = ">=3.7"
|
|||
|
||||
[[package]]
|
||||
name = "flake8-type-checking"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
description = "A flake8 plugin for managing type-checking imports & forward references"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "flake8_type_checking-2.3.0-py3-none-any.whl", hash = "sha256:7117b8a22d64db02f9d8c724df5d2517e59c6290b034cfa54496c7ae73c07f51"},
|
||||
{file = "flake8_type_checking-2.3.0.tar.gz", hash = "sha256:f802c9933b2a98b96fc4a0b3b90ef0f8379625f867cb73633c09fc2bf746333b"},
|
||||
{file = "flake8_type_checking-2.4.0-py3-none-any.whl", hash = "sha256:2dee127f300bb95b7f17b7c3fff4f6336f5e4ba92082c15928c6e19b666cfba4"},
|
||||
{file = "flake8_type_checking-2.4.0.tar.gz", hash = "sha256:9ea96d01e6557a47835acf04020c48fabb9c3d4664c15f2920915e09e65c1d55"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -605,14 +597,14 @@ flake8 = "*"
|
|||
|
||||
[[package]]
|
||||
name = "flakeheaven"
|
||||
version = "3.2.1"
|
||||
version = "3.3.0"
|
||||
description = "FlakeHeaven is a [Flake8](https://gitlab.com/pycqa/flake8) wrapper to make it cool."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7,<4.0"
|
||||
files = [
|
||||
{file = "flakeheaven-3.2.1-py3-none-any.whl", hash = "sha256:fdae542414a8cd327dbbc969bb18d5972379570f6562af21b4a83f67bdd6b87c"},
|
||||
{file = "flakeheaven-3.2.1.tar.gz", hash = "sha256:f2d54aedd98b817e94c8c0fcc0da1230b43dbf911ce38aa412d00eb5db6fb71d"},
|
||||
{file = "flakeheaven-3.3.0-py3-none-any.whl", hash = "sha256:ae246197a178845b30b63fc03023f7ba925cc84cc96314ec19807dafcd6b39a3"},
|
||||
{file = "flakeheaven-3.3.0.tar.gz", hash = "sha256:eb07860e028ff8dd56cce742c4766624a37a4ce397fd34300254ab623d13047b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -690,14 +682,14 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "ipdb"
|
||||
version = "0.13.11"
|
||||
version = "0.13.13"
|
||||
description = "IPython-enabled pdb"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "ipdb-0.13.11-py3-none-any.whl", hash = "sha256:f74c2f741c18b909eaf89f19fde973f745ac721744aa1465888ce45813b63a9c"},
|
||||
{file = "ipdb-0.13.11.tar.gz", hash = "sha256:c23b6736f01fd4586cc2ecbebdf79a5eb454796853e1cd8f2ed3b7b91d4a3e93"},
|
||||
{file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"},
|
||||
{file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1206,19 +1198,19 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.0.0"
|
||||
version = "3.2.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.0.0-py3-none-any.whl", hash = "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"},
|
||||
{file = "platformdirs-3.0.0.tar.gz", hash = "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9"},
|
||||
{file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"},
|
||||
{file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
|
@ -1238,14 +1230,14 @@ testing = ["pytest", "pytest-benchmark"]
|
|||
|
||||
[[package]]
|
||||
name = "poethepoet"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
description = "A task runner that works well with poetry."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "poethepoet-0.18.1-py3-none-any.whl", hash = "sha256:e85727bf6f4a10bf6c1a43026bdeb40df689bea3c4682d03cbe531cabc8f2ba6"},
|
||||
{file = "poethepoet-0.18.1.tar.gz", hash = "sha256:5f3566b14c2f5dccdfbc3bb26f0096006b38dc0b9c74bd4f8dd1eba7b0e29f6a"},
|
||||
{file = "poethepoet-0.19.0-py3-none-any.whl", hash = "sha256:87038be589077e4b407050a9da644d9cd9e4076ccfc8abc7f855cf6870d5c6c2"},
|
||||
{file = "poethepoet-0.19.0.tar.gz", hash = "sha256:897eb85ec15876d79befc7d19d4c80ce7c8b214d1bb0dcfec640abd81616bfed"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1362,38 +1354,37 @@ plugins = ["importlib-metadata"]
|
|||
|
||||
[[package]]
|
||||
name = "pyproject-api"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
description = "API to interact with the python pyproject.toml based projects"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pyproject_api-1.5.0-py3-none-any.whl", hash = "sha256:4c111277dfb96bcd562c6245428f27250b794bfe3e210b8714c4f893952f2c17"},
|
||||
{file = "pyproject_api-1.5.0.tar.gz", hash = "sha256:0962df21f3e633b8ddb9567c011e6c1b3dcdfc31b7860c0ede7e24c5a1200fbe"},
|
||||
{file = "pyproject_api-1.5.1-py3-none-any.whl", hash = "sha256:4698a3777c2e0f6b624f8a4599131e2a25376d90fe8d146d7ac74c67c6f97c43"},
|
||||
{file = "pyproject_api-1.5.1.tar.gz", hash = "sha256:435f46547a9ff22cf4208ee274fca3e2869aeb062a4834adfc99a4dd64af3cf9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
packaging = ">=21.3"
|
||||
packaging = ">=23"
|
||||
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
|
||||
testing = ["covdefaults (>=2.2.2)", "importlib-metadata (>=5.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "virtualenv (>=20.17)", "wheel (>=0.38.4)"]
|
||||
docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
||||
testing = ["covdefaults (>=2.2.2)", "importlib-metadata (>=6)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "virtualenv (>=20.17.1)", "wheel (>=0.38.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.2.1"
|
||||
version = "7.3.1"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"},
|
||||
{file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"},
|
||||
{file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"},
|
||||
{file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
|
@ -1402,7 +1393,7 @@ pluggy = ">=0.12,<2.0"
|
|||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-bdd"
|
||||
|
@ -1441,14 +1432,14 @@ rich = ">=8.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-xdist-3.2.0.tar.gz", hash = "sha256:fa10f95a2564cd91652f2d132725183c3b590d9fdcdec09d3677386ecf4c1ce9"},
|
||||
{file = "pytest_xdist-3.2.0-py3-none-any.whl", hash = "sha256:336098e3bbd8193276867cc87db8b22903c3927665dff9d1ac8684c02f597b68"},
|
||||
{file = "pytest-xdist-3.2.1.tar.gz", hash = "sha256:1849bd98d8b242b948e472db7478e090bf3361912a8fed87992ed94085f54727"},
|
||||
{file = "pytest_xdist-3.2.1-py3-none-any.whl", hash = "sha256:37290d161638a20b672401deef1cba812d110ac27e35d213f091d15b8beb40c9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1603,19 +1594,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
|||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.3.1"
|
||||
version = "13.3.4"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "rich-13.3.1-py3-none-any.whl", hash = "sha256:8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9"},
|
||||
{file = "rich-13.3.1.tar.gz", hash = "sha256:125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f"},
|
||||
{file = "rich-13.3.4-py3-none-any.whl", hash = "sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a"},
|
||||
{file = "rich-13.3.4.tar.gz", hash = "sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown-it-py = ">=2.1.0,<3.0.0"
|
||||
pygments = ">=2.14.0,<3.0.0"
|
||||
markdown-it-py = ">=2.2.0,<3.0.0"
|
||||
pygments = ">=2.13.0,<3.0.0"
|
||||
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
@ -1656,6 +1647,8 @@ files = [
|
|||
{file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"},
|
||||
{file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"},
|
||||
{file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"},
|
||||
{file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"},
|
||||
{file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"},
|
||||
{file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072"},
|
||||
{file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_12_0_arm64.whl", hash = "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8"},
|
||||
{file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3"},
|
||||
|
@ -1731,6 +1724,18 @@ pure-eval = "*"
|
|||
[package.extras]
|
||||
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap3"
|
||||
version = "0.9.2"
|
||||
description = "textwrap from Python 3.6 backport (plus a few tweaks)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "textwrap3-0.9.2-py2.py3-none-any.whl", hash = "sha256:bf5f4c40faf2a9ff00a9e0791fed5da7415481054cef45bb4a3cfb1f69044ae0"},
|
||||
{file = "textwrap3-0.9.2.zip", hash = "sha256:5008eeebdb236f6303dcd68f18b856d355f6197511d952ba74bc75e40e0c3414"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
|
@ -1757,31 +1762,31 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "tox"
|
||||
version = "4.4.6"
|
||||
version = "4.4.12"
|
||||
description = "tox is a generic virtualenv management and test command line tool"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tox-4.4.6-py3-none-any.whl", hash = "sha256:e3d4a65852f029e5ba441a01824d2d839d30bb8fb071635ef9cb53952698e6bf"},
|
||||
{file = "tox-4.4.6.tar.gz", hash = "sha256:9786671d23b673ace7499c602c5746e2a225d1ecd9d9f624d0461303f40bd93b"},
|
||||
{file = "tox-4.4.12-py3-none-any.whl", hash = "sha256:d4be558809d86fad13f4553976b0500352630a8fbfa39ea4b1ce3bd945ba680b"},
|
||||
{file = "tox-4.4.12.tar.gz", hash = "sha256:740f5209d0dec19451b951ee5b1cce4a207acdc7357af84dbc8ec35bcf2c454e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cachetools = ">=5.3"
|
||||
chardet = ">=5.1"
|
||||
colorama = ">=0.4.6"
|
||||
filelock = ">=3.9"
|
||||
filelock = ">=3.11"
|
||||
packaging = ">=23"
|
||||
platformdirs = ">=2.6.2"
|
||||
platformdirs = ">=3.2"
|
||||
pluggy = ">=1"
|
||||
pyproject-api = ">=1.5"
|
||||
pyproject-api = ">=1.5.1"
|
||||
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
|
||||
virtualenv = ">=20.17.1"
|
||||
virtualenv = ">=20.21"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-argparse-cli (>=1.11)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)", "sphinx-copybutton (>=0.5.1)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"]
|
||||
testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.2.2)", "devpi-process (>=0.3)", "diff-cover (>=7.4)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.12.2)", "psutil (>=5.9.4)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-xdist (>=3.1)", "re-assert (>=1.1)", "time-machine (>=2.9)", "wheel (>=0.38.4)"]
|
||||
docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-argparse-cli (>=1.11)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)", "sphinx-copybutton (>=0.5.1)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"]
|
||||
testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "devpi-process (>=0.3)", "diff-cover (>=7.5)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.14)", "psutil (>=5.9.4)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-xdist (>=3.2.1)", "re-assert (>=1.1)", "time-machine (>=2.9)", "wheel (>=0.40)"]
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
|
@ -1825,14 +1830,14 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "tzlocal"
|
||||
version = "4.2"
|
||||
version = "4.3"
|
||||
description = "tzinfo object for the local timezone"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"},
|
||||
{file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"},
|
||||
{file = "tzlocal-4.3-py3-none-any.whl", hash = "sha256:b44c4388f3d34f25862cfbb387578a4d70fec417649da694a132f628a23367e2"},
|
||||
{file = "tzlocal-4.3.tar.gz", hash = "sha256:3f21d09e1b2aa9f2dacca12da240ca37de3ba5237a93addfd6d593afe9073355"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1840,8 +1845,7 @@ pytz-deprecation-shim = "*"
|
|||
tzdata = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[package.extras]
|
||||
devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"]
|
||||
test = ["pytest (>=4.3)", "pytest-mock (>=3.3)"]
|
||||
devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
|
@ -1862,14 +1866,14 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.19.0"
|
||||
version = "20.21.0"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "virtualenv-20.19.0-py3-none-any.whl", hash = "sha256:54eb59e7352b573aa04d53f80fc9736ed0ad5143af445a1e539aada6eb947dd1"},
|
||||
{file = "virtualenv-20.19.0.tar.gz", hash = "sha256:37a640ba82ed40b226599c522d411e4be5edb339a0c0de030c0dc7b646d61590"},
|
||||
{file = "virtualenv-20.21.0-py3-none-any.whl", hash = "sha256:31712f8f2a17bd06234fa97fdf19609e789dd4e3e4bf108c3da71d710651adbc"},
|
||||
{file = "virtualenv-20.21.0.tar.gz", hash = "sha256:f50e3e60f990a0757c9b68333c9fdaa72d7188caa417f96af9e52407831a3b68"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1965,4 +1969,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10.0, <3.13"
|
||||
content-hash = "39227dc2d020370fccdb4c3a0c140dbe1cda73e0ea703abb5d9c6857d2c145b9"
|
||||
content-hash = "dfc32ee61025dae6033987a8ff8290d4c2a34197502b8030cef02db58b86baf1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "jrnl"
|
||||
version = "v3.3"
|
||||
version = "v4.0-beta3"
|
||||
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")
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[2022-03-02 9:25:00 AM] This file should be ignored (month)
|
||||
This text file is in a folder journal's month directory ("2020/09"), but it's not in the file name format used by jrnl for folder journal entries, so it should be ignored.
|
||||
|
||||
This file should not ever appear in a test.
|
|
@ -0,0 +1,4 @@
|
|||
[2022-03-02 9:25:00 AM] This file should be ignored (year)
|
||||
This text file is in a folder journal's year directory ("2020"), but it's not in the file name format used by jrnl for folder journal entries, so it should be ignored.
|
||||
|
||||
This file should not ever appear in a test.
|
4
tests/data/journals/basic_folder/should-be-ignored.txt
Normal file
4
tests/data/journals/basic_folder/should-be-ignored.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
[2022-03-02 9:25:00 AM] This file should be ignored (root)
|
||||
This text file is in a folder journal's root directory, but it's not in the file name format used by jrnl for folder journal entries, so it should be ignored.
|
||||
|
||||
This file should not ever appear in a test.
|
|
@ -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)
|
||||
|
||||
|
|
59
tests/unit/test_journals_folder_journal.py
Normal file
59
tests/unit/test_journals_folder_journal.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright © 2012-2023 jrnl contributors
|
||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
import pathlib
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from jrnl.journals.FolderJournal import Folder
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"inputs_and_outputs",
|
||||
[
|
||||
[
|
||||
"/2020/01",
|
||||
["02.txt", "03.txt", "31.txt"],
|
||||
["/2020/01/02.txt", "/2020/01/03.txt", "/2020/01/31.txt"],
|
||||
],
|
||||
[
|
||||
"/2020/02", # leap year
|
||||
["02.txt", "03.txt", "28.txt", "29.txt", "31.txt", "39.txt"],
|
||||
[
|
||||
"/2020/02/02.txt",
|
||||
"/2020/02/03.txt",
|
||||
"/2020/02/28.txt",
|
||||
"/2020/02/29.txt",
|
||||
],
|
||||
],
|
||||
[
|
||||
"/2100/02", # not a leap year
|
||||
["01.txt", "28.txt", "29.txt", "39.txt"],
|
||||
["/2100/02/01.txt", "/2100/02/28.txt"],
|
||||
],
|
||||
[
|
||||
"/2023/04",
|
||||
["29.txt", "30.txt", "31.txt", "39.txt"],
|
||||
["/2023/04/29.txt", "/2023/04/30.txt"],
|
||||
],
|
||||
],
|
||||
)
|
||||
def test_get_day_files_expected_filtering(inputs_and_outputs):
|
||||
year_month_path, glob_filenames, expected_output = inputs_and_outputs
|
||||
|
||||
year_month_path = pathlib.Path(year_month_path)
|
||||
|
||||
glob_files = map(lambda x: year_month_path / x, glob_filenames)
|
||||
expected_output = list(map(lambda x: str(pathlib.PurePath(x)), expected_output))
|
||||
|
||||
with (
|
||||
mock.patch("pathlib.Path.glob", return_value=glob_files),
|
||||
mock.patch.object(pathlib.Path, "is_file", return_value=True),
|
||||
):
|
||||
actual_output = list(Folder._get_day_files(year_month_path))
|
||||
actual_output.sort()
|
||||
|
||||
expected_output.sort()
|
||||
|
||||
assert actual_output == expected_output
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from jrnl import time
|
||||
|
||||
|
||||
|
@ -20,3 +22,23 @@ def test_default_minute_is_added():
|
|||
default_minute=30,
|
||||
bracketed=False,
|
||||
) == datetime.datetime(2020, 6, 20, 0, 30)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"inputs",
|
||||
[
|
||||
[2000, 2, 29, True],
|
||||
[2023, 1, 0, False],
|
||||
[2023, 1, 1, True],
|
||||
[2023, 4, 31, False],
|
||||
[2023, 12, 31, True],
|
||||
[2023, 12, 32, False],
|
||||
[2023, 13, 1, False],
|
||||
[2100, 2, 27, True],
|
||||
[2100, 2, 28, True],
|
||||
[2100, 2, 29, False],
|
||||
],
|
||||
)
|
||||
def test_is_valid_date(inputs):
|
||||
year, month, day, expected_result = inputs
|
||||
assert time.is_valid_date(year, month, day) == expected_result
|
||||
|
|
Loading…
Add table
Reference in a new issue