mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-19 20:48:31 +02:00
Merge pull request #2 from sriniv27/double-delimited-text-input-for-config-override
Double delimited text input for config override
This commit is contained in:
commit
18458b7299
8 changed files with 178 additions and 87 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -58,4 +58,5 @@ site/
|
|||
.vscode/settings.json
|
||||
coverage.xml
|
||||
.vscode/launch.json
|
||||
.coverage
|
||||
.coverage
|
||||
.vscode/tasks.json
|
|
@ -1,7 +1,8 @@
|
|||
journals:
|
||||
default: /tmp/journal.jrnl
|
||||
default: features/journals/simple.journal
|
||||
colors:
|
||||
body: none
|
||||
title: green
|
||||
editor: "vim"
|
||||
encrypt: false
|
||||
#
|
|
@ -1,45 +1,52 @@
|
|||
Feature: Implementing Runtime Overrides for Select Configuration Keys
|
||||
|
||||
Scenario: Override configured editor with built-in input === editor:''
|
||||
Given we use the config "editor-args.yaml"
|
||||
When we run "jrnl --config-override '{"editor": ""}'"
|
||||
Then the editor "" should have been called
|
||||
Scenario: Override configured editor with built-in input === editor:''
|
||||
Given we use the config "tiny.yaml"
|
||||
When we run jrnl with --config-override editor:''
|
||||
Then the stdin prompt must be launched
|
||||
|
||||
Scenario: Override configured editor with 'nano'
|
||||
Given we use the config "editor.yaml"
|
||||
When we run "jrnl --config-override '{"editor": "nano"}'"
|
||||
Then the editor "nano" should have been called
|
||||
@skip_win
|
||||
Scenario: Override configured linewrap with a value of 23
|
||||
Given we use the config "tiny.yaml"
|
||||
When we run "jrnl -2 --config-override linewrap:23 --format fancy"
|
||||
Then the output should be
|
||||
|
||||
@skip_win
|
||||
Scenario: Override configured linewrap with a value of 23
|
||||
Given we use the config "editor.yaml"
|
||||
When we run "jrnl -2 --config-override '{"linewrap": 23}' --format fancy"
|
||||
Then the output should be
|
||||
"""
|
||||
┎─────╮2013-06-09 15:39
|
||||
┃ My ╘═══════════════╕
|
||||
┃ fir st ent ry. │
|
||||
┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
┃ Everything is │
|
||||
┃ alright │
|
||||
┖─────────────────────┘
|
||||
┎─────╮2013-06-10 15:40
|
||||
┃ Lif ╘═══════════════╕
|
||||
┃ e is goo d. │
|
||||
┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
┃ But I'm better. │
|
||||
┖─────────────────────┘
|
||||
"""
|
||||
"""
|
||||
┎─────╮2013-06-09 15:39
|
||||
┃ My ╘═══════════════╕
|
||||
┃ fir st ent ry. │
|
||||
┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
┃ Everything is │
|
||||
┃ alright │
|
||||
┖─────────────────────┘
|
||||
┎─────╮2013-06-10 15:40
|
||||
┃ Lif ╘═══════════════╕
|
||||
┃ e is goo d. │
|
||||
┠╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
┃ But I'm better. │
|
||||
┖─────────────────────┘
|
||||
"""
|
||||
|
||||
@skip_win
|
||||
Scenario: Override color selections with runtime overrides
|
||||
Given we use the config "tiny.yaml"
|
||||
When we run jrnl with -1 --config-override '{"colors.body": "blue"}'
|
||||
Then the runtime config should have colors.body set to blue
|
||||
@skip_win
|
||||
Scenario: Override color selections with runtime overrides
|
||||
Given we use the config "tiny.yaml"
|
||||
When we run jrnl with -1 --config-override colors.body:blue
|
||||
Then the runtime config should have colors.body set to blue
|
||||
|
||||
@skip_win
|
||||
Scenario: Apply multiple config overrides
|
||||
Given we use the config "tiny.yaml"
|
||||
When we run jrnl with -1 --config-override '{"colors.body": "green", "editor": "nano"}'
|
||||
Then the runtime config should have colors.body set to green
|
||||
And the runtime config should have editor set to nano
|
||||
@skip_win
|
||||
Scenario: Apply multiple config overrides
|
||||
Given we use the config "tiny.yaml"
|
||||
When we run jrnl with -1 --config-override colors.body:green,editor:"nano"
|
||||
Then the runtime config should have colors.body set to green
|
||||
And the runtime config should have editor set to nano
|
||||
|
||||
|
||||
Scenario Outline: Override configured editor
|
||||
Given we use the config "tiny.yaml"
|
||||
When we run jrnl with --config-override editor:"<editor>"
|
||||
Then the editor <editor> should have been called
|
||||
Examples: Editor Commands
|
||||
| editor |
|
||||
| nano |
|
||||
| vi -c startinsert |
|
||||
| code -w - |
|
||||
|
|
|
@ -10,7 +10,18 @@ import yaml
|
|||
from yaml.loader import FullLoader
|
||||
|
||||
import jrnl
|
||||
from jrnl.cli import cli
|
||||
|
||||
|
||||
def _mock_time_parse(context):
|
||||
original_parse = jrnl.time.parse
|
||||
if "now" not in context:
|
||||
return original_parse
|
||||
|
||||
def wrapper(input, *args, **kwargs):
|
||||
input = context.now if input == "now" else input
|
||||
return original_parse(input, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@given("we use the config {config_file}")
|
||||
|
@ -31,30 +42,27 @@ def run_command(context, args):
|
|||
@then("the runtime config should have {key_as_dots} set to {override_value}")
|
||||
def config_override(context, key_as_dots: str, override_value: str):
|
||||
key_as_vec = key_as_dots.split(".")
|
||||
expected_call_args_list = [
|
||||
(context.cfg, key_as_vec, override_value),
|
||||
(context.cfg[key_as_vec[0]], key_as_vec[1], override_value),
|
||||
]
|
||||
with open(context.config_path) as f:
|
||||
loaded_cfg = yaml.load(f, Loader=yaml.FullLoader)
|
||||
loaded_cfg["journal"] = "features/journals/simple.journal"
|
||||
|
||||
|
||||
def _mock_callback(**args):
|
||||
print("callback executed")
|
||||
|
||||
# fmt: off
|
||||
try:
|
||||
with \
|
||||
mock.patch("jrnl.jrnl.search_mode"), \
|
||||
mock.patch.object(jrnl.override,"_recursively_apply",wraps=jrnl.override._recursively_apply) as mock_recurse, \
|
||||
mock.patch('jrnl.install.load_or_install_jrnl', return_value=context.cfg), \
|
||||
mock.patch('jrnl.time.parse', side_effect=_mock_time_parse(context)), \
|
||||
mock.patch("jrnl.config.get_config_path", side_effect=lambda: context.config_path), \
|
||||
mock.patch("jrnl.install.get_config_path", side_effect=lambda: context.config_path) \
|
||||
:
|
||||
run(context.parser)
|
||||
runtime_cfg = mock_recurse.call_args_list[0][0][0]
|
||||
|
||||
assert mock_recurse.call_count >= 2
|
||||
mock_recurse.call_args_list = expected_call_args_list
|
||||
|
||||
for k in key_as_vec:
|
||||
runtime_cfg = runtime_cfg['%s'%k]
|
||||
|
||||
assert runtime_cfg == override_value
|
||||
except SystemExit as e :
|
||||
context.exit_status = e.code
|
||||
# fmt: on
|
||||
|
@ -62,27 +70,54 @@ def config_override(context, key_as_dots: str, override_value: str):
|
|||
|
||||
@then("the editor {editor} should have been called")
|
||||
def editor_override(context, editor):
|
||||
def _mock_editor(command_and_journal_file):
|
||||
editor = command_and_journal_file[0]
|
||||
tmpfile = command_and_journal_file[-1]
|
||||
context.tmpfile = tmpfile
|
||||
def _mock_write_in_editor(config):
|
||||
editor = config["editor"]
|
||||
journal = "features/journals/journal.jrnl"
|
||||
context.tmpfile = journal
|
||||
print("%s has been launched" % editor)
|
||||
return tmpfile
|
||||
return journal
|
||||
|
||||
# fmt: off
|
||||
# see: https://github.com/psf/black/issues/664
|
||||
with \
|
||||
mock.patch("subprocess.call", side_effect=_mock_editor) as mock_editor, \
|
||||
mock.patch("jrnl.jrnl._write_in_editor", side_effect=_mock_write_in_editor(context.cfg)) as mock_write_in_editor, \
|
||||
mock.patch("sys.stdin.isatty", return_value=True), \
|
||||
mock.patch("jrnl.time.parse"), \
|
||||
mock.patch("jrnl.time.parse", side_effect = _mock_time_parse(context)), \
|
||||
mock.patch("jrnl.config.get_config_path", side_effect=lambda: context.config_path), \
|
||||
mock.patch("jrnl.install.get_config_path", side_effect=lambda: context.config_path) \
|
||||
:
|
||||
try :
|
||||
cli(['--config-override','{"editor": "%s"}'%editor])
|
||||
run(context.parser)
|
||||
context.exit_status = 0
|
||||
context.editor = mock_editor
|
||||
assert mock_editor.assert_called_once_with(editor, context.tmpfile)
|
||||
context.editor = mock_write_in_editor
|
||||
expected_config = context.cfg
|
||||
expected_config['editor'] = '%s'%editor
|
||||
expected_config['journal'] ='features/journals/journal.jrnl'
|
||||
|
||||
assert mock_write_in_editor.call_count == 1
|
||||
assert mock_write_in_editor.call_args[0][0]['editor']==editor
|
||||
except SystemExit as e:
|
||||
context.exit_status = e.code
|
||||
# fmt: on
|
||||
|
||||
|
||||
@then("the stdin prompt must be launched")
|
||||
def override_editor_to_use_stdin(context):
|
||||
|
||||
try:
|
||||
with mock.patch(
|
||||
"sys.stdin.read",
|
||||
return_value="Zwei peanuts walk into a bar und one of zem was a-salted",
|
||||
) as mock_stdin_read, mock.patch(
|
||||
"jrnl.install.load_or_install_jrnl", return_value=context.cfg
|
||||
), mock.patch(
|
||||
"jrnl.Journal.open_journal",
|
||||
spec=False,
|
||||
return_value="features/journals/journal.jrnl",
|
||||
):
|
||||
run(context.parser)
|
||||
context.exit_status = 0
|
||||
mock_stdin_read.assert_called_once()
|
||||
|
||||
except SystemExit as e:
|
||||
context.exit_status = e.code
|
||||
|
|
28
jrnl/args.py
28
jrnl/args.py
|
@ -4,7 +4,6 @@
|
|||
import argparse
|
||||
import re
|
||||
import textwrap
|
||||
import json
|
||||
|
||||
from .commands import postconfig_decrypt
|
||||
from .commands import postconfig_encrypt
|
||||
|
@ -18,6 +17,22 @@ from .plugins import IMPORT_FORMATS
|
|||
from .plugins import util
|
||||
|
||||
|
||||
def deserialize_config_args(input: str) -> dict:
|
||||
_kvpairs = input.strip(" ").split(",")
|
||||
runtime_modifications = {}
|
||||
for _p in _kvpairs:
|
||||
l, r = _p.strip().split(":")
|
||||
r = r.strip()
|
||||
if r.isdigit():
|
||||
r = int(r)
|
||||
elif r.lower() == "true":
|
||||
r = True
|
||||
elif r.lower() == "false":
|
||||
r = False
|
||||
runtime_modifications[l] = r
|
||||
return runtime_modifications
|
||||
|
||||
|
||||
class WrappingFormatter(argparse.RawTextHelpFormatter):
|
||||
"""Used in help screen"""
|
||||
|
||||
|
@ -323,18 +338,18 @@ def parse_args(args=[]):
|
|||
"--config-override",
|
||||
dest="config_override",
|
||||
action="store",
|
||||
type=json.loads,
|
||||
type=deserialize_config_args,
|
||||
nargs="?",
|
||||
default={},
|
||||
default=None,
|
||||
metavar="CONFIG_KV_PAIR",
|
||||
help="""
|
||||
Override configured key-value pairs with CONFIG_KV_PAIR for this command invocation only.
|
||||
|
||||
Examples: \n
|
||||
\t - Use a different editor for this jrnl entry, call: \n
|
||||
\t jrnl --config-override '{"editor": "nano"}' \n
|
||||
\t jrnl --config-override editor: "nano" \n
|
||||
\t - Override color selections\n
|
||||
\t jrnl --config-override '{"colors.body":"blue", "colors.title": "green"}
|
||||
\t jrnl --config-override colors.body: blue, colors.title: green
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -342,4 +357,5 @@ def parse_args(args=[]):
|
|||
num = re.compile(r"^-(\d+)$")
|
||||
args = [num.sub(r"-n \1", arg) for arg in args]
|
||||
|
||||
return parser.parse_intermixed_args(args)
|
||||
parsed_args = parser.parse_intermixed_args(args)
|
||||
return parsed_args
|
||||
|
|
|
@ -16,6 +16,7 @@ from .editor import get_text_from_editor
|
|||
from .editor import get_text_from_stdin
|
||||
from .exception import UserAbort
|
||||
from . import time
|
||||
from .override import apply_overrides
|
||||
|
||||
|
||||
def run(args):
|
||||
|
@ -51,9 +52,8 @@ def run(args):
|
|||
|
||||
# Apply config overrides
|
||||
overrides = args.config_override
|
||||
from .override import apply_overrides
|
||||
|
||||
config = apply_overrides(overrides, config)
|
||||
if overrides:
|
||||
config = apply_overrides(overrides, config)
|
||||
|
||||
# --- All the standalone commands are now done --- #
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import shlex
|
||||
import pytest
|
||||
import mock
|
||||
|
||||
import yaml
|
||||
|
||||
from jrnl.args import parse_args
|
||||
from jrnl.jrnl import run
|
||||
|
@ -43,7 +43,7 @@ def test_override_configured_editor(
|
|||
mock_load_or_install.return_value = minimal_config
|
||||
mock_isatty.return_value = True
|
||||
|
||||
cli_args = ["--config-override", '{"editor": "nano"}']
|
||||
cli_args = shlex.split('--config-override editor:"nano"')
|
||||
parser = parse_args(cli_args)
|
||||
assert parser.config_override.__len__() == 1
|
||||
assert "editor" in parser.config_override.keys()
|
||||
|
@ -84,7 +84,7 @@ def test_override_configured_colors(
|
|||
):
|
||||
mock_load_or_install.return_value = minimal_config
|
||||
|
||||
cli_args = ["--config-override", '{"colors.body": "blue"}']
|
||||
cli_args = shlex.split("--config-override colors.body:blue")
|
||||
parser = parse_args(cli_args)
|
||||
assert "colors.body" in parser.config_override.keys()
|
||||
with mock.patch.object(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from features.steps.override import config_override
|
||||
import shlex
|
||||
|
||||
import pytest
|
||||
|
||||
from jrnl.args import parse_args
|
||||
from jrnl.args import deserialize_config_args
|
||||
|
||||
|
||||
def cli_as_dict(str):
|
||||
|
@ -36,7 +36,7 @@ def expected_args(**kwargs):
|
|||
"strict": False,
|
||||
"tags": False,
|
||||
"text": [],
|
||||
"config_override": {},
|
||||
"config_override": None,
|
||||
}
|
||||
return {**default_args, **kwargs}
|
||||
|
||||
|
@ -207,27 +207,58 @@ def test_version_alone():
|
|||
assert cli_as_dict("--version") == expected_args(preconfig_cmd=preconfig_version)
|
||||
|
||||
|
||||
class TestDeserialization:
|
||||
@pytest.mark.parametrize(
|
||||
"input_str",
|
||||
[
|
||||
'editor:"nano", colors.title:blue, default:"/tmp/egg.txt"',
|
||||
'editor:"vi -c startinsert", colors.title:blue, default:"/tmp/egg.txt"',
|
||||
'editor:"nano", colors.title:blue, default:"/tmp/eg\ g.txt"',
|
||||
],
|
||||
)
|
||||
def test_deserialize_multiword_strings(self, input_str):
|
||||
|
||||
runtime_config = deserialize_config_args(input_str)
|
||||
assert runtime_config.__class__ == dict
|
||||
assert "editor" in runtime_config.keys()
|
||||
assert "colors.title" in runtime_config.keys()
|
||||
assert "default" in runtime_config.keys()
|
||||
|
||||
def test_deserialize_int(self):
|
||||
input = "linewrap: 23, default_hour: 19"
|
||||
runtime_config = deserialize_config_args(input)
|
||||
assert runtime_config["linewrap"] == 23
|
||||
assert runtime_config["default_hour"] == 19
|
||||
|
||||
def test_deserialize_multiple_datatypes(self):
|
||||
input = 'linewrap: 23, encrypt: false, editor:"vi -c startinsert"'
|
||||
cfg = deserialize_config_args(input)
|
||||
assert cfg["encrypt"] == False
|
||||
assert cfg["linewrap"] == 23
|
||||
assert cfg["editor"] == '"vi -c startinsert"'
|
||||
|
||||
|
||||
def test_editor_override():
|
||||
|
||||
assert cli_as_dict('--config-override \'{"editor": "nano"}\'') == expected_args(
|
||||
config_override={"editor": "nano"}
|
||||
)
|
||||
parsed_args = cli_as_dict('--config-override editor:"nano"')
|
||||
assert parsed_args == expected_args(config_override={"editor": "nano"})
|
||||
|
||||
|
||||
def test_color_override():
|
||||
assert cli_as_dict(
|
||||
'--config-override \'{"colors.body": "blue"}\''
|
||||
) == expected_args(config_override={"colors.body": "blue"})
|
||||
assert cli_as_dict("--config-override colors.body:blue") == expected_args(
|
||||
config_override={"colors.body": "blue"}
|
||||
)
|
||||
|
||||
|
||||
def test_multiple_overrides():
|
||||
assert cli_as_dict(
|
||||
'--config-override \'{"colors.title": "green", "editor":"", "journal.scratchpad": "/tmp/scratchpad"}\''
|
||||
) == expected_args(
|
||||
parsed_args = cli_as_dict(
|
||||
'--config-override colors.title:green,editor:"nano",journal.scratchpad:"/tmp/scratchpad"'
|
||||
)
|
||||
assert parsed_args == expected_args(
|
||||
config_override={
|
||||
"colors.title": "green",
|
||||
"journal.scratchpad": "/tmp/scratchpad",
|
||||
"editor": "",
|
||||
"editor": "nano",
|
||||
}
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue