mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-06-29 05:56:14 +02:00
Add ability to use template with --template
(#1667)
* Add ability to pass template path with --template Update jrnl/args.py * Fix tests
This commit is contained in:
parent
0725ea6b87
commit
a2b217fdfc
12 changed files with 217 additions and 68 deletions
|
@ -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."
|
||||
|
@ -419,7 +424,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
|
|||
default="",
|
||||
help="""
|
||||
Overrides default (created when first installed) config file for this command only.
|
||||
|
||||
|
||||
Examples: \n
|
||||
\t - Use a work config file for this jrnl entry, call: \n
|
||||
\t jrnl --config-file /home/user1/work_config.yaml
|
||||
|
|
|
@ -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,6 +11,7 @@ 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
|
||||
|
@ -23,7 +24,7 @@ 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
|
||||
|
@ -123,9 +124,78 @@ def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool:
|
|||
return write_mode
|
||||
|
||||
|
||||
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(
|
||||
"Write 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("Write 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"Write 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 write_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)
|
||||
|
@ -134,8 +204,16 @@ def write_mode(args: "Namespace", config: dict, journal: Journal, **kwargs) -> N
|
|||
"""
|
||||
logging.debug("Write mode: starting")
|
||||
|
||||
if args.text:
|
||||
logging.debug("Write mode: cli text detected: %s", args.text)
|
||||
if args.template or config["template"]:
|
||||
logging.debug(f"Write 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("Write mode: raw text was the same as the template")
|
||||
raise JrnlException(Message(MsgText.NoChangesToTemplate, MsgStyle.NORMAL))
|
||||
elif args.text:
|
||||
logging.debug(f"Write mode: cli text detected: {args.text}")
|
||||
raw = " ".join(args.text).strip()
|
||||
if args.edit:
|
||||
raw = _write_in_editor(config, raw)
|
||||
|
@ -150,9 +228,6 @@ def write_mode(args: "Namespace", config: dict, journal: Journal, **kwargs) -> N
|
|||
if not raw or raw.isspace():
|
||||
logging.error("Write 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
|
||||
|
@ -224,45 +299,16 @@ def search_mode(args: "Namespace", journal: Journal, **kwargs) -> None:
|
|||
_display_search_results(**kwargs)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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(
|
||||
(
|
||||
|
@ -438,7 +484,7 @@ def _change_time_search_results(
|
|||
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)
|
||||
|
|
|
@ -335,7 +335,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
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue