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:
Suhas 2021-01-28 08:51:30 -05:00 committed by GitHub
commit 18458b7299
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 178 additions and 87 deletions

3
.gitignore vendored
View file

@ -58,4 +58,5 @@ site/
.vscode/settings.json
coverage.xml
.vscode/launch.json
.coverage
.coverage
.vscode/tasks.json

View file

@ -1,7 +1,8 @@
journals:
default: /tmp/journal.jrnl
default: features/journals/simple.journal
colors:
body: none
title: green
editor: "vim"
encrypt: false
#

View file

@ -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 - |

View file

@ -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

View file

@ -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

View file

@ -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 --- #

View file

@ -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(

View file

@ -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",
}
)