mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-25 17:52:02 +02:00
Allow dynamically generating configuration by running an arbitrary program using the new geninclude directive
This commit is contained in:
@@ -89,6 +89,8 @@ Detailed list of changes
|
|||||||
|
|
||||||
- diff kitten: Automatically use dark/light color scheme based on the color scheme of the parent terminal. Can be controlled via the new :opt:`kitten-diff.color_scheme` option. Note that this is a **default behavior change** (:iss:`8170`)
|
- diff kitten: Automatically use dark/light color scheme based on the color scheme of the parent terminal. Can be controlled via the new :opt:`kitten-diff.color_scheme` option. Note that this is a **default behavior change** (:iss:`8170`)
|
||||||
|
|
||||||
|
- Allow dynamically generating configuration by running an arbitrary program using the new :code:`geninclude` directive in :file:`kitty.conf`
|
||||||
|
|
||||||
- When a program running in kitty reports progress of a task display it as a percentage in the tab title. Controlled by the :opt:`tab_title_template` option
|
- When a program running in kitty reports progress of a task display it as a percentage in the tab title. Controlled by the :opt:`tab_title_template` option
|
||||||
|
|
||||||
- When mapping a custom kitten allow using shell escaping for the kitten path (:iss:`8178`)
|
- When mapping a custom kitten allow using shell escaping for the kitten path (:iss:`8178`)
|
||||||
|
|||||||
@@ -45,13 +45,21 @@ expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
|||||||
to detect the operating system. It is ``linux``, ``macos`` or ``bsd``.
|
to detect the operating system. It is ``linux``, ``macos`` or ``bsd``.
|
||||||
Also, you can use :code:`globinclude` to include files
|
Also, you can use :code:`globinclude` to include files
|
||||||
matching a shell glob pattern and :code:`envinclude` to include configuration
|
matching a shell glob pattern and :code:`envinclude` to include configuration
|
||||||
from environment variables. For example::
|
from environment variables. Finally, you can dynamically generate configuration
|
||||||
|
by running a program using :code:`geninclude`. For example::
|
||||||
|
|
||||||
|
# Include other.conf
|
||||||
include other.conf
|
include other.conf
|
||||||
# Include *.conf files from all subdirs of kitty.d inside the kitty config dir
|
# Include *.conf files from all subdirs of kitty.d inside the kitty config dir
|
||||||
globinclude kitty.d/**/*.conf
|
globinclude kitty.d/**/*.conf
|
||||||
# Include the *contents* of all env vars starting with KITTY_CONF_
|
# Include the *contents* of all env vars starting with KITTY_CONF_
|
||||||
envinclude KITTY_CONF_*
|
envinclude KITTY_CONF_*
|
||||||
|
# Run the script dynamic.py placed in the same directory as this config file
|
||||||
|
# and include its :file:`STDOUT`. Note that Python scripts are fastest
|
||||||
|
# as they use the embedded Python interpreter, but any executable script
|
||||||
|
# or program is supported, in any language. Remember to mark the script
|
||||||
|
# file executable.
|
||||||
|
geninclude dynamic.py
|
||||||
|
|
||||||
|
|
||||||
.. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via
|
.. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via
|
||||||
|
|||||||
@@ -198,12 +198,67 @@ class NamedLineIterator:
|
|||||||
return self.lines
|
return self.lines
|
||||||
|
|
||||||
|
|
||||||
|
def pygeninclude(path: str) -> list[str]:
|
||||||
|
import io
|
||||||
|
import runpy
|
||||||
|
before = sys.stdout
|
||||||
|
buf = sys.stdout = io.StringIO()
|
||||||
|
try:
|
||||||
|
runpy.run_path(path, run_name='__main__')
|
||||||
|
finally:
|
||||||
|
sys.stdout = before
|
||||||
|
return buf.getvalue().splitlines()
|
||||||
|
|
||||||
|
|
||||||
|
def geninclude(path: str) -> list[str]:
|
||||||
|
old = os.environ.get('KITTY_OS')
|
||||||
|
os.environ['KITTY_OS'] = os_name()
|
||||||
|
try:
|
||||||
|
if path.endswith('.py'):
|
||||||
|
return pygeninclude(path)
|
||||||
|
import subprocess
|
||||||
|
cp = subprocess.run([path], stdout=subprocess.PIPE, text=True)
|
||||||
|
return cp.stdout.splitlines()
|
||||||
|
finally:
|
||||||
|
if old is None:
|
||||||
|
os.environ.pop('KITTY_OS', None)
|
||||||
|
else:
|
||||||
|
os.environ['KITTY_OS'] = old
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include_keys = 'include', 'globinclude', 'envinclude', 'geninclude'
|
||||||
|
|
||||||
|
|
||||||
|
class RecursiveInclude(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Memory:
|
||||||
|
|
||||||
|
def __init__(self, accumulate_bad_lines: Optional[List[BadLine]]) -> None:
|
||||||
|
self.s: set[str] = set()
|
||||||
|
if accumulate_bad_lines is None:
|
||||||
|
accumulate_bad_lines = []
|
||||||
|
self.accumulate_bad_lines = accumulate_bad_lines
|
||||||
|
|
||||||
|
def seen(self, path: str) -> bool:
|
||||||
|
key = os.path.normpath(path)
|
||||||
|
if key in self.s:
|
||||||
|
self.accumulate_bad_lines.append(BadLine(currently_parsing.number, currently_parsing.line.rstrip(), RecursiveInclude(
|
||||||
|
f'The file {path} has already been included, ignoring'), currently_parsing.file))
|
||||||
|
return True
|
||||||
|
self.s.add(key)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def parse_line(
|
def parse_line(
|
||||||
line: str,
|
line: str,
|
||||||
parse_conf_item: ItemParser,
|
parse_conf_item: ItemParser,
|
||||||
ans: Dict[str, Any],
|
ans: Dict[str, Any],
|
||||||
base_path_for_includes: str,
|
base_path_for_includes: str,
|
||||||
effective_config_lines: Callable[[str, str], None],
|
effective_config_lines: Callable[[str, str], None],
|
||||||
|
memory: Memory,
|
||||||
accumulate_bad_lines: Optional[List[BadLine]] = None,
|
accumulate_bad_lines: Optional[List[BadLine]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@@ -214,7 +269,7 @@ def parse_line(
|
|||||||
log_error(f'Ignoring invalid config line: {line!r}')
|
log_error(f'Ignoring invalid config line: {line!r}')
|
||||||
return
|
return
|
||||||
key, val = m.groups()
|
key, val = m.groups()
|
||||||
if key in ('include', 'globinclude', 'envinclude'):
|
if key.endswith('include') and key in include_keys:
|
||||||
val = expandvars(os.path.expanduser(val.strip()), {'KITTY_OS': os_name()})
|
val = expandvars(os.path.expanduser(val.strip()), {'KITTY_OS': os_name()})
|
||||||
if key == 'globinclude':
|
if key == 'globinclude':
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -226,7 +281,22 @@ def parse_line(
|
|||||||
with currently_parsing.set_file(f'<env var: {x}>'):
|
with currently_parsing.set_file(f'<env var: {x}>'):
|
||||||
_parse(
|
_parse(
|
||||||
NamedLineIterator(os.path.join(base_path_for_includes, ''), iter(os.environ[x].splitlines())),
|
NamedLineIterator(os.path.join(base_path_for_includes, ''), iter(os.environ[x].splitlines())),
|
||||||
parse_conf_item, ans, accumulate_bad_lines, effective_config_lines,
|
parse_conf_item, ans, memory, accumulate_bad_lines, effective_config_lines
|
||||||
|
)
|
||||||
|
return
|
||||||
|
elif key == 'geninclude':
|
||||||
|
if not os.path.isabs(val):
|
||||||
|
val = os.path.join(base_path_for_includes, val)
|
||||||
|
if not memory.seen(val):
|
||||||
|
try:
|
||||||
|
lines = geninclude(val)
|
||||||
|
except Exception:
|
||||||
|
log_error(f'Could not process geninclude {val}, ignoring')
|
||||||
|
else:
|
||||||
|
with currently_parsing.set_file(f'<get: {val}>'):
|
||||||
|
_parse(
|
||||||
|
NamedLineIterator(os.path.join(base_path_for_includes, ''), iter(lines)),
|
||||||
|
parse_conf_item, ans, memory, accumulate_bad_lines, effective_config_lines
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@@ -234,15 +304,14 @@ def parse_line(
|
|||||||
val = os.path.join(base_path_for_includes, val)
|
val = os.path.join(base_path_for_includes, val)
|
||||||
vals = (val,)
|
vals = (val,)
|
||||||
for val in vals:
|
for val in vals:
|
||||||
|
if memory.seen(val):
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
with open(val, encoding='utf-8', errors='replace') as include:
|
with open(val, encoding='utf-8', errors='replace') as include:
|
||||||
with currently_parsing.set_file(val):
|
with currently_parsing.set_file(val):
|
||||||
_parse(include, parse_conf_item, ans, accumulate_bad_lines, effective_config_lines)
|
_parse(include, parse_conf_item, ans, memory, accumulate_bad_lines, effective_config_lines)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
log_error(
|
log_error(f'Could not find included config file: {val}, ignoring')
|
||||||
'Could not find included config file: {}, ignoring'.
|
|
||||||
format(val)
|
|
||||||
)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
log_error(
|
log_error(
|
||||||
'Could not read from included config file: {}, ignoring'.
|
'Could not read from included config file: {}, ignoring'.
|
||||||
@@ -260,6 +329,7 @@ def _parse(
|
|||||||
lines: Iterable[str],
|
lines: Iterable[str],
|
||||||
parse_conf_item: ItemParser,
|
parse_conf_item: ItemParser,
|
||||||
ans: Dict[str, Any],
|
ans: Dict[str, Any],
|
||||||
|
memory: Memory,
|
||||||
accumulate_bad_lines: Optional[List[BadLine]] = None,
|
accumulate_bad_lines: Optional[List[BadLine]] = None,
|
||||||
effective_config_lines: Optional[Callable[[str, str], None]] = None,
|
effective_config_lines: Optional[Callable[[str, str], None]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -301,7 +371,7 @@ def _parse(
|
|||||||
next_line = ''
|
next_line = ''
|
||||||
try:
|
try:
|
||||||
with currently_parsing.set_line(line, line_num):
|
with currently_parsing.set_line(line, line_num):
|
||||||
parse_line(line, parse_conf_item, ans, base_path_for_includes, effective_config_lines, accumulate_bad_lines)
|
parse_line(line, parse_conf_item, ans, base_path_for_includes, effective_config_lines, memory, accumulate_bad_lines)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if accumulate_bad_lines is None:
|
if accumulate_bad_lines is None:
|
||||||
raise
|
raise
|
||||||
@@ -317,7 +387,7 @@ def parse_config_base(
|
|||||||
accumulate_bad_lines: Optional[List[BadLine]] = None,
|
accumulate_bad_lines: Optional[List[BadLine]] = None,
|
||||||
effective_config_lines: Optional[Callable[[str, str], None]] = None,
|
effective_config_lines: Optional[Callable[[str, str], None]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
_parse(lines, parse_conf_item, ans, accumulate_bad_lines, effective_config_lines)
|
_parse(lines, parse_conf_item, ans, Memory(accumulate_bad_lines), accumulate_bad_lines, effective_config_lines)
|
||||||
|
|
||||||
|
|
||||||
def merge_dicts(defaults: Dict[str, Any], newvals: Dict[str, Any]) -> Dict[str, Any]:
|
def merge_dicts(defaults: Dict[str, Any], newvals: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from kitty.fast_data_types import Color, test_cursor_blink_easing_function
|
from kitty.fast_data_types import Color, test_cursor_blink_easing_function
|
||||||
from kitty.options.utils import DELETE_ENV_VAR, EasingFunction
|
from kitty.options.utils import DELETE_ENV_VAR, EasingFunction, to_color
|
||||||
from kitty.utils import log_error
|
from kitty.utils import log_error
|
||||||
|
|
||||||
from . import BaseTest
|
from . import BaseTest
|
||||||
@@ -15,170 +18,219 @@ class TestConfParsing(BaseTest):
|
|||||||
super().setUp()
|
super().setUp()
|
||||||
self.error_messages = []
|
self.error_messages = []
|
||||||
log_error.redirect = self.error_messages.append
|
log_error.redirect = self.error_messages.append
|
||||||
|
self.tdir = tempfile.mkdtemp()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
del log_error.redirect
|
del log_error.redirect
|
||||||
|
shutil.rmtree(self.tdir)
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
def test_conf_parsing(self):
|
def test_conf_parsing(self):
|
||||||
from kitty.config import defaults, load_config
|
conf_parsing(self)
|
||||||
from kitty.constants import is_macos
|
|
||||||
from kitty.fonts import FontModification, ModificationType, ModificationUnit, ModificationValue
|
|
||||||
from kitty.options.utils import to_modifiers
|
|
||||||
bad_lines = []
|
|
||||||
|
|
||||||
def p(*lines, bad_line_num=0):
|
|
||||||
del bad_lines[:]
|
|
||||||
del self.error_messages[:]
|
|
||||||
ans = load_config(overrides=lines, accumulate_bad_lines=bad_lines)
|
|
||||||
if bad_line_num:
|
|
||||||
self.ae(len(bad_lines), bad_line_num)
|
|
||||||
else:
|
|
||||||
self.assertFalse(bad_lines)
|
|
||||||
return ans
|
|
||||||
|
|
||||||
def keys_for_func(opts, name):
|
def conf_parsing(self):
|
||||||
for key, defns in opts.keyboard_modes[''].keymap.items():
|
from kitty.config import defaults, load_config
|
||||||
for action in opts.alias_map.resolve_aliases(defns[0].definition):
|
from kitty.constants import is_macos
|
||||||
if action.func == name:
|
from kitty.fonts import FontModification, ModificationType, ModificationUnit, ModificationValue
|
||||||
yield key
|
from kitty.options.utils import to_modifiers
|
||||||
|
bad_lines = []
|
||||||
|
|
||||||
opts = p('font_size 11.37', 'clear_all_shortcuts y', 'color23 red')
|
def p(*lines, bad_line_num=0, num_err=None):
|
||||||
self.ae(opts.font_size, 11.37)
|
del bad_lines[:]
|
||||||
self.ae(opts.mouse_hide_wait, 0 if is_macos else 3)
|
del self.error_messages[:]
|
||||||
self.ae(opts.color23, Color(255, 0, 0))
|
ans = load_config(overrides=lines, accumulate_bad_lines=bad_lines)
|
||||||
self.assertFalse(opts.keyboard_modes[''].keymap)
|
if bad_line_num:
|
||||||
opts = p('clear_all_shortcuts y', 'map f1 next_window')
|
self.ae(len(bad_lines), bad_line_num)
|
||||||
self.ae(len(opts.keyboard_modes[''].keymap), 1)
|
else:
|
||||||
opts = p('clear_all_mouse_actions y', 'mouse_map left click ungrabbed mouse_click_url_or_select')
|
self.assertFalse(bad_lines)
|
||||||
self.ae(len(opts.mousemap), 1)
|
if num_err is not None:
|
||||||
opts = p('strip_trailing_spaces always')
|
self.ae(len(self.error_messages), num_err, '\n'.join(self.error_messages))
|
||||||
self.ae(opts.strip_trailing_spaces, 'always')
|
return ans
|
||||||
self.assertFalse(bad_lines)
|
|
||||||
opts = p('pointer_shape_when_grabbed XXX', bad_line_num=1)
|
|
||||||
self.ae(opts.pointer_shape_when_grabbed, defaults.pointer_shape_when_grabbed)
|
|
||||||
opts = p('modify_font underline_position -2', 'modify_font underline_thickness 150%', 'modify_font size Test -1px')
|
|
||||||
self.ae(opts.modify_font, {
|
|
||||||
'underline_position': FontModification(ModificationType.underline_position, ModificationValue(-2., ModificationUnit.pt)),
|
|
||||||
'underline_thickness': FontModification(ModificationType.underline_thickness, ModificationValue(150, ModificationUnit.percent)),
|
|
||||||
'size:Test': FontModification(ModificationType.size, ModificationValue(-1., ModificationUnit.pixel), 'Test'),
|
|
||||||
})
|
|
||||||
|
|
||||||
# test the aliasing options
|
def keys_for_func(opts, name):
|
||||||
opts = p('env A=1', 'env B=x$A', 'env C=', 'env D', 'clear_all_shortcuts y', 'kitten_alias a b --moo', 'map f1 kitten a arg')
|
for key, defns in opts.keyboard_modes[''].keymap.items():
|
||||||
self.ae(opts.env, {'A': '1', 'B': 'x1', 'C': '', 'D': DELETE_ENV_VAR})
|
for action in opts.alias_map.resolve_aliases(defns[0].definition):
|
||||||
|
if action.func == name:
|
||||||
|
yield key
|
||||||
|
|
||||||
def ac(which=0):
|
opts = p('font_size 11.37', 'clear_all_shortcuts y', 'color23 red')
|
||||||
ka = tuple(opts.keyboard_modes[''].keymap.values())[0][0]
|
self.ae(opts.font_size, 11.37)
|
||||||
acs = opts.alias_map.resolve_aliases(ka.definition)
|
self.ae(opts.mouse_hide_wait, 0 if is_macos else 3)
|
||||||
return acs[which]
|
self.ae(opts.color23, Color(255, 0, 0))
|
||||||
|
self.assertFalse(opts.keyboard_modes[''].keymap)
|
||||||
|
opts = p('clear_all_shortcuts y', 'map f1 next_window')
|
||||||
|
self.ae(len(opts.keyboard_modes[''].keymap), 1)
|
||||||
|
opts = p('clear_all_mouse_actions y', 'mouse_map left click ungrabbed mouse_click_url_or_select')
|
||||||
|
self.ae(len(opts.mousemap), 1)
|
||||||
|
opts = p('strip_trailing_spaces always')
|
||||||
|
self.ae(opts.strip_trailing_spaces, 'always')
|
||||||
|
self.assertFalse(bad_lines)
|
||||||
|
opts = p('pointer_shape_when_grabbed XXX', bad_line_num=1)
|
||||||
|
self.ae(opts.pointer_shape_when_grabbed, defaults.pointer_shape_when_grabbed)
|
||||||
|
opts = p('modify_font underline_position -2', 'modify_font underline_thickness 150%', 'modify_font size Test -1px')
|
||||||
|
self.ae(opts.modify_font, {
|
||||||
|
'underline_position': FontModification(ModificationType.underline_position, ModificationValue(-2., ModificationUnit.pt)),
|
||||||
|
'underline_thickness': FontModification(ModificationType.underline_thickness, ModificationValue(150, ModificationUnit.percent)),
|
||||||
|
'size:Test': FontModification(ModificationType.size, ModificationValue(-1., ModificationUnit.pixel), 'Test'),
|
||||||
|
})
|
||||||
|
|
||||||
ka = ac()
|
# test the aliasing options
|
||||||
self.ae(ka.func, 'kitten')
|
opts = p('env A=1', 'env B=x$A', 'env C=', 'env D', 'clear_all_shortcuts y', 'kitten_alias a b --moo', 'map f1 kitten a arg')
|
||||||
self.ae(ka.args, ('b', '--moo', 'arg'))
|
self.ae(opts.env, {'A': '1', 'B': 'x1', 'C': '', 'D': DELETE_ENV_VAR})
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'kitten_alias hints hints --hi', 'map f1 kitten hints XXX')
|
def ac(which=0):
|
||||||
ka = ac()
|
ka = tuple(opts.keyboard_modes[''].keymap.values())[0][0]
|
||||||
self.ae(ka.func, 'kitten')
|
acs = opts.alias_map.resolve_aliases(ka.definition)
|
||||||
self.ae(ka.args, ('hints', '--hi', 'XXX'))
|
return acs[which]
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias la launch --moo', 'map f1 la XXX')
|
ka = ac()
|
||||||
ka = ac()
|
self.ae(ka.func, 'kitten')
|
||||||
self.ae(ka.func, 'launch')
|
self.ae(ka.args, ('b', '--moo', 'arg'))
|
||||||
self.ae(ka.args, ('--moo', 'XXX'))
|
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias one launch --moo', 'action_alias two one recursive', 'map f1 two XXX')
|
opts = p('clear_all_shortcuts y', 'kitten_alias hints hints --hi', 'map f1 kitten hints XXX')
|
||||||
ka = ac()
|
ka = ac()
|
||||||
self.ae(ka.func, 'launch')
|
self.ae(ka.func, 'kitten')
|
||||||
self.ae(ka.args, ('--moo', 'recursive', 'XXX'))
|
self.ae(ka.args, ('hints', '--hi', 'XXX'))
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias launch two 1', 'action_alias two launch 2', 'map f1 launch 3')
|
opts = p('clear_all_shortcuts y', 'action_alias la launch --moo', 'map f1 la XXX')
|
||||||
ka = ac()
|
ka = ac()
|
||||||
self.ae(ka.func, 'launch')
|
self.ae(ka.func, 'launch')
|
||||||
self.ae(ka.args, ('2', '1', '3'))
|
self.ae(ka.args, ('--moo', 'XXX'))
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias launch launch --moo', 'map f1 launch XXX')
|
opts = p('clear_all_shortcuts y', 'action_alias one launch --moo', 'action_alias two one recursive', 'map f1 two XXX')
|
||||||
ka = ac()
|
ka = ac()
|
||||||
self.ae(ka.func, 'launch')
|
self.ae(ka.func, 'launch')
|
||||||
self.ae(ka.args, ('--moo', 'XXX'))
|
self.ae(ka.args, ('--moo', 'recursive', 'XXX'))
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias cfs change_font_size current', 'map f1 cfs +2')
|
opts = p('clear_all_shortcuts y', 'action_alias launch two 1', 'action_alias two launch 2', 'map f1 launch 3')
|
||||||
ka = ac()
|
ka = ac()
|
||||||
self.ae(ka.func, 'change_font_size')
|
self.ae(ka.func, 'launch')
|
||||||
self.ae(ka.args, (False, '+', 2.0))
|
self.ae(ka.args, ('2', '1', '3'))
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias la launch --moo', 'map f1 combine : new_window : la ')
|
opts = p('clear_all_shortcuts y', 'action_alias launch launch --moo', 'map f1 launch XXX')
|
||||||
self.ae((ac().func, ac(1).func), ('new_window', 'launch'))
|
ka = ac()
|
||||||
|
self.ae(ka.func, 'launch')
|
||||||
|
self.ae(ka.args, ('--moo', 'XXX'))
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias cc combine : new_window : launch --moo', 'map f1 cc XXX')
|
opts = p('clear_all_shortcuts y', 'action_alias cfs change_font_size current', 'map f1 cfs +2')
|
||||||
self.ae((ac().func, ac(1).func), ('new_window', 'launch'))
|
ka = ac()
|
||||||
self.ae(ac(1).args, ('--moo', 'XXX'))
|
self.ae(ka.func, 'change_font_size')
|
||||||
|
self.ae(ka.args, (False, '+', 2.0))
|
||||||
|
|
||||||
opts = p('clear_all_shortcuts y', 'action_alias ss kitten "space 1"', 'map f1 ss "space 2"')
|
opts = p('clear_all_shortcuts y', 'action_alias la launch --moo', 'map f1 combine : new_window : la ')
|
||||||
self.ae(ac().args, ('space 1', 'space 2'))
|
self.ae((ac().func, ac(1).func), ('new_window', 'launch'))
|
||||||
|
|
||||||
opts = p('kitty_mod alt')
|
opts = p('clear_all_shortcuts y', 'action_alias cc combine : new_window : launch --moo', 'map f1 cc XXX')
|
||||||
self.ae(opts.kitty_mod, to_modifiers('alt'))
|
self.ae((ac().func, ac(1).func), ('new_window', 'launch'))
|
||||||
self.ae(next(keys_for_func(opts, 'next_layout')).mods, opts.kitty_mod)
|
self.ae(ac(1).args, ('--moo', 'XXX'))
|
||||||
|
|
||||||
# deprecation handling
|
opts = p('clear_all_shortcuts y', 'action_alias ss kitten "space 1"', 'map f1 ss "space 2"')
|
||||||
opts = p('clear_all_shortcuts y', 'send_text all f1 hello')
|
self.ae(ac().args, ('space 1', 'space 2'))
|
||||||
self.ae(len(opts.keyboard_modes[''].keymap), 1)
|
|
||||||
opts = p('macos_hide_titlebar y' if is_macos else 'x11_hide_window_decorations y')
|
|
||||||
self.assertTrue(opts.hide_window_decorations)
|
|
||||||
self.ae(len(self.error_messages), 1)
|
|
||||||
|
|
||||||
# line breaks
|
opts = p('kitty_mod alt')
|
||||||
opts = p(" font",
|
self.ae(opts.kitty_mod, to_modifiers('alt'))
|
||||||
" \t \t \\_size",
|
self.ae(next(keys_for_func(opts, 'next_layout')).mods, opts.kitty_mod)
|
||||||
" \\ 12",
|
|
||||||
"\\.35",
|
|
||||||
"col",
|
|
||||||
"\\o",
|
|
||||||
"\t \t\\r",
|
|
||||||
"\\25",
|
|
||||||
" \\ blue")
|
|
||||||
self.ae(opts.font_size, 12.35)
|
|
||||||
self.ae(opts.color25, Color(0, 0, 255))
|
|
||||||
|
|
||||||
# cursor_blink_interval
|
# deprecation handling
|
||||||
def cb(src, interval=-1, first=EasingFunction(), second=EasingFunction()):
|
opts = p('clear_all_shortcuts y', 'send_text all f1 hello')
|
||||||
opts = p('cursor_blink_interval ' + src)
|
self.ae(len(opts.keyboard_modes[''].keymap), 1)
|
||||||
self.ae((float(interval), first, second), (float(opts.cursor_blink_interval[0]), opts.cursor_blink_interval[1], opts.cursor_blink_interval[2]))
|
opts = p('macos_hide_titlebar y' if is_macos else 'x11_hide_window_decorations y')
|
||||||
cb('3', 3)
|
self.assertTrue(opts.hide_window_decorations)
|
||||||
cb('-2.3', -2.3)
|
self.ae(len(self.error_messages), 1)
|
||||||
cb('linear', first=EasingFunction('cubic-bezier', cubic_bezier_points=(0, 0.0, 1.0, 1.0)))
|
|
||||||
cb('linear 19', 19, EasingFunction('cubic-bezier', cubic_bezier_points=(0, 0.0, 1.0, 1.0)))
|
|
||||||
cb('ease-in-out cubic-bezier(0.1, 0.2, 0.3, 0.4) 11', 11,
|
|
||||||
EasingFunction('cubic-bezier', cubic_bezier_points=(0.42, 0, 0.58, 1)),
|
|
||||||
EasingFunction('cubic-bezier', cubic_bezier_points=(0.1, 0.2, 0.3, 0.4))
|
|
||||||
)
|
|
||||||
cb('step-start', first=EasingFunction('steps', num_steps=1, jump_type='start'))
|
|
||||||
cb('steps(7, jump-none)', first=EasingFunction('steps', num_steps=7, jump_type='none'))
|
|
||||||
cb('linear(0, 0.25, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.5, 1.0), linear_y=(0, 0.25, 1.0)))
|
|
||||||
cb('linear(0, 0.25 75%, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.75, 1.0), linear_y=(0, 0.25, 1.0)))
|
|
||||||
cb('linear(0, 0.25 25% 75%, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.25, 0.75, 1.0), linear_y=(0, 0.25, 0.25, 1.0)))
|
|
||||||
|
|
||||||
# test that easing functions give expected values
|
# line breaks
|
||||||
def ef(spec, tests, only_single=True, duration=0.5, accuracy=0):
|
opts = p(" font",
|
||||||
cfv = p('cursor_blink_interval ' + spec).cursor_blink_interval
|
" \t \t \\_size",
|
||||||
self.set_options({'cursor_blink_interval': cfv})
|
" \\ 12",
|
||||||
for t, expected in tests.items():
|
"\\.35",
|
||||||
actual = test_cursor_blink_easing_function(t, only_single, duration)
|
"col",
|
||||||
if abs(actual - expected) > accuracy:
|
"\\o",
|
||||||
self.ae(expected, actual, f'Failed for {spec=} with {t=}: {expected} != {actual}')
|
"\t \t\\r",
|
||||||
|
"\\25",
|
||||||
|
" \\ blue")
|
||||||
|
self.ae(opts.font_size, 12.35)
|
||||||
|
self.ae(opts.color25, Color(0, 0, 255))
|
||||||
|
|
||||||
ef('linear', {0:1, 0.25: 0.5, 0.5: 0, 0.75: 0.5, 1: 1}, only_single=False)
|
# cursor_blink_interval
|
||||||
|
def cb(src, interval=-1, first=EasingFunction(), second=EasingFunction()):
|
||||||
|
opts = p('cursor_blink_interval ' + src)
|
||||||
|
self.ae((float(interval), first, second), (float(opts.cursor_blink_interval[0]), opts.cursor_blink_interval[1], opts.cursor_blink_interval[2]))
|
||||||
|
cb('3', 3)
|
||||||
|
cb('-2.3', -2.3)
|
||||||
|
cb('linear', first=EasingFunction('cubic-bezier', cubic_bezier_points=(0, 0.0, 1.0, 1.0)))
|
||||||
|
cb('linear 19', 19, EasingFunction('cubic-bezier', cubic_bezier_points=(0, 0.0, 1.0, 1.0)))
|
||||||
|
cb('ease-in-out cubic-bezier(0.1, 0.2, 0.3, 0.4) 11', 11,
|
||||||
|
EasingFunction('cubic-bezier', cubic_bezier_points=(0.42, 0, 0.58, 1)),
|
||||||
|
EasingFunction('cubic-bezier', cubic_bezier_points=(0.1, 0.2, 0.3, 0.4))
|
||||||
|
)
|
||||||
|
cb('step-start', first=EasingFunction('steps', num_steps=1, jump_type='start'))
|
||||||
|
cb('steps(7, jump-none)', first=EasingFunction('steps', num_steps=7, jump_type='none'))
|
||||||
|
cb('linear(0, 0.25, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.5, 1.0), linear_y=(0, 0.25, 1.0)))
|
||||||
|
cb('linear(0, 0.25 75%, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.75, 1.0), linear_y=(0, 0.25, 1.0)))
|
||||||
|
cb('linear(0, 0.25 25% 75%, 1)', first=EasingFunction('linear', linear_x=(0.0, 0.25, 0.75, 1.0), linear_y=(0, 0.25, 0.25, 1.0)))
|
||||||
|
|
||||||
ef('linear(0, 0.25 25% 75%, 1)', {0: 0, 0.25: 0.25, 0.3: 0.25, 0.75: 0.25, 1:1})
|
# test that easing functions give expected values
|
||||||
linear_vals = {0: 0, 1: 1, 0.1234: 0.1234, 0.6453: 0.6453}
|
def ef(spec, tests, only_single=True, duration=0.5, accuracy=0):
|
||||||
for spec in ('linear', 'linear(0, 1)', 'cubic-bezier(0, 0, 1, 1)', 'cubic-bezier(0.2, 0.2, 0.7, 0.7)'):
|
cfv = p('cursor_blink_interval ' + spec).cursor_blink_interval
|
||||||
ef(spec, linear_vals)
|
self.set_options({'cursor_blink_interval': cfv})
|
||||||
# test an almost linear function to test cubic bezier implementation
|
for t, expected in tests.items():
|
||||||
ef('cubic-bezier(0.2, 0.2, 0.7, 0.71)', linear_vals, accuracy=0.01)
|
actual = test_cursor_blink_easing_function(t, only_single, duration)
|
||||||
ef('cubic-bezier(0.23, 0.2, 0.7, 0.71)', linear_vals, accuracy=0.01)
|
if abs(actual - expected) > accuracy:
|
||||||
|
self.ae(expected, actual, f'Failed for {spec=} with {t=}: {expected} != {actual}')
|
||||||
|
|
||||||
ef('steps(5)', {0: 0, 0.1: 0, 0.3: 0.2, 0.9:0.8})
|
ef('linear', {0:1, 0.25: 0.5, 0.5: 0, 0.75: 0.5, 1: 1}, only_single=False)
|
||||||
ef('steps(5, start)', {0: 0.2, 0.1: 0.2, 0.3: 0.4, 0.9:1})
|
|
||||||
ef('steps(4, jump-both)', {0: 0.2, 0.1: 0.2, 0.3: 0.4, 0.9:1})
|
ef('linear(0, 0.25 25% 75%, 1)', {0: 0, 0.25: 0.25, 0.3: 0.25, 0.75: 0.25, 1:1})
|
||||||
ef('steps(6, jump-none)', {0: 0, 0.1: 0.0, 0.3: 0.2, 0.9:1})
|
linear_vals = {0: 0, 1: 1, 0.1234: 0.1234, 0.6453: 0.6453}
|
||||||
|
for spec in ('linear', 'linear(0, 1)', 'cubic-bezier(0, 0, 1, 1)', 'cubic-bezier(0.2, 0.2, 0.7, 0.7)'):
|
||||||
|
ef(spec, linear_vals)
|
||||||
|
# test an almost linear function to test cubic bezier implementation
|
||||||
|
ef('cubic-bezier(0.2, 0.2, 0.7, 0.71)', linear_vals, accuracy=0.01)
|
||||||
|
ef('cubic-bezier(0.23, 0.2, 0.7, 0.71)', linear_vals, accuracy=0.01)
|
||||||
|
|
||||||
|
ef('steps(5)', {0: 0, 0.1: 0, 0.3: 0.2, 0.9:0.8})
|
||||||
|
ef('steps(5, start)', {0: 0.2, 0.1: 0.2, 0.3: 0.4, 0.9:1})
|
||||||
|
ef('steps(4, jump-both)', {0: 0.2, 0.1: 0.2, 0.3: 0.4, 0.9:1})
|
||||||
|
ef('steps(6, jump-none)', {0: 0, 0.1: 0.0, 0.3: 0.2, 0.9:1})
|
||||||
|
|
||||||
|
# test various include modes
|
||||||
|
base = os.path.join(self.tdir, 'glob')
|
||||||
|
os.mkdir(base)
|
||||||
|
with open(os.path.join(base, 'fg'), 'w') as f:
|
||||||
|
print('foreground red', file=f)
|
||||||
|
opts = p(f'include {f.name}', num_err=0)
|
||||||
|
self.ae(opts.foreground, to_color('red'))
|
||||||
|
with open(os.path.join(self.tdir, 'bg'), 'w') as f:
|
||||||
|
print('background white', file=f)
|
||||||
|
print('globinclude glob/*', file=f)
|
||||||
|
print('envinclude ENVINCLUDE', file=f)
|
||||||
|
print('geninclude g.py', file=f)
|
||||||
|
print('geninclude g', file=f)
|
||||||
|
with open(os.path.join(self.tdir, 'g.py'), 'w') as g:
|
||||||
|
print('print("background_opacity .77")', file=g)
|
||||||
|
print('print("background_blur 77")', file=g)
|
||||||
|
with open(os.path.join(self.tdir, 'g'), 'w') as g:
|
||||||
|
print('#!/bin/sh', file=g)
|
||||||
|
print('echo background_image_linear y', file=g)
|
||||||
|
print('echo background_image_layout clamped', file=g)
|
||||||
|
os.chmod(g.fileno(), 0o700)
|
||||||
|
os.environ['ENVINCLUDE'] = 'cursor yellow'
|
||||||
|
opts = p(f'include {f.name}', num_err=0)
|
||||||
|
os.environ.pop('ENVINCLUDE')
|
||||||
|
self.ae(opts.foreground, to_color('red'))
|
||||||
|
self.ae(opts.background, to_color('white'))
|
||||||
|
self.ae(opts.cursor, to_color('yellow'))
|
||||||
|
self.ae(opts.background_opacity, .77)
|
||||||
|
self.ae(opts.background_blur, 77)
|
||||||
|
self.ae(opts.background_image_linear, True)
|
||||||
|
self.ae(opts.background_image_layout, 'clamped')
|
||||||
|
with open(os.path.join(self.tdir, 'a'), 'w') as a:
|
||||||
|
print('background red', file=a)
|
||||||
|
print('include b', file=a)
|
||||||
|
with open(os.path.join(self.tdir, 'b'), 'w') as a:
|
||||||
|
print('foreground red', file=a)
|
||||||
|
print('include a', file=a)
|
||||||
|
opts = p(f'include {a.name}', num_err=0, bad_line_num=1)
|
||||||
|
self.ae(opts.foreground, to_color('red'))
|
||||||
|
self.ae(opts.background, to_color('red'))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -61,6 +62,55 @@ var key_pat = sync.OnceValue(func() *regexp.Regexp {
|
|||||||
return regexp.MustCompile(`([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$`)
|
return regexp.MustCompile(`([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var kitty_os = sync.OnceValue(func() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
return "linux"
|
||||||
|
case "freebsd", "netbsd", "openbsd":
|
||||||
|
return "bsd"
|
||||||
|
case "darwin":
|
||||||
|
return "macos"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
})
|
||||||
|
|
||||||
|
func geninclude(path string) (string, error) {
|
||||||
|
cmd := exec.Command(path)
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
cmd.Env = append(cmd.Env, "KITTY_OS="+kitty_os())
|
||||||
|
if strings.HasSuffix(path, ".py") && unix.Access(path, unix.X_OK) != nil {
|
||||||
|
if utils.KittyExe() == "" || strings.HasPrefix(path, ":") {
|
||||||
|
cmd = exec.Command("python", path)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command(utils.KittyExe(), "+launch", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(stdout)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err = cmd.Wait(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return utils.UnsafeBytesToString(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpandVars(x string) string {
|
||||||
|
return os.Expand(x, func(k string) string {
|
||||||
|
if k == "KITTY_OS" {
|
||||||
|
return kitty_os()
|
||||||
|
}
|
||||||
|
return os.Getenv(k)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes string, depth int) error {
|
func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes string, depth int) error {
|
||||||
if self.seen_includes[name] { // avoid include loops
|
if self.seen_includes[name] { // avoid include loops
|
||||||
return nil
|
return nil
|
||||||
@@ -90,6 +140,10 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st
|
|||||||
next_line := ""
|
next_line := ""
|
||||||
var line string
|
var line string
|
||||||
|
|
||||||
|
add_bad_line := func(err error) {
|
||||||
|
self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err})
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if next_line != "" {
|
if next_line != "" {
|
||||||
line = next_line
|
line = next_line
|
||||||
@@ -124,16 +178,15 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st
|
|||||||
|
|
||||||
if line[0] == '#' {
|
if line[0] == '#' {
|
||||||
if self.CommentsHandler != nil {
|
if self.CommentsHandler != nil {
|
||||||
err := self.CommentsHandler(line)
|
if err := self.CommentsHandler(line); err != nil {
|
||||||
if err != nil {
|
add_bad_line(err)
|
||||||
self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m := key_pat().FindStringSubmatch(line)
|
m := key_pat().FindStringSubmatch(line)
|
||||||
if len(m) < 3 {
|
if len(m) < 3 {
|
||||||
self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: fmt.Errorf("Invalid config line: %#v", line)})
|
add_bad_line(fmt.Errorf("Invalid config line: %#v", line))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key, val := m[1], m[2]
|
key, val := m[1], m[2]
|
||||||
@@ -146,17 +199,18 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st
|
|||||||
}
|
}
|
||||||
switch key {
|
switch key {
|
||||||
default:
|
default:
|
||||||
err := self.LineHandler(key, val)
|
if err := self.LineHandler(key, val); err != nil {
|
||||||
if err != nil {
|
add_bad_line(err)
|
||||||
self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err})
|
|
||||||
}
|
}
|
||||||
case "include", "globinclude", "envinclude":
|
case "include", "globinclude", "envinclude", "geninclude":
|
||||||
var includes []string
|
var includes []string
|
||||||
|
val = ExpandVars(val)
|
||||||
switch key {
|
switch key {
|
||||||
case "include":
|
case "include":
|
||||||
aval, err := make_absolute(val)
|
if aval, err := make_absolute(val); err == nil {
|
||||||
if err == nil {
|
|
||||||
includes = []string{aval}
|
includes = []string{aval}
|
||||||
|
} else {
|
||||||
|
add_bad_line(err)
|
||||||
}
|
}
|
||||||
case "globinclude":
|
case "globinclude":
|
||||||
aval, err := make_absolute(val)
|
aval, err := make_absolute(val)
|
||||||
@@ -164,13 +218,26 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st
|
|||||||
matches, err := filepath.Glob(aval)
|
matches, err := filepath.Glob(aval)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
includes = matches
|
includes = matches
|
||||||
|
} else {
|
||||||
|
add_bad_line(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
add_bad_line(err)
|
||||||
|
}
|
||||||
|
case "geninclude":
|
||||||
|
if aval, err := make_absolute(val); err == nil {
|
||||||
|
if g, err := geninclude(aval); err == nil {
|
||||||
|
if err := recurse(strings.NewReader(g), "<gen: "+val+">", base_path_for_includes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_bad_line(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_bad_line(err)
|
||||||
}
|
}
|
||||||
case "envinclude":
|
case "envinclude":
|
||||||
env := self.override_env
|
env := utils.IfElse(self.override_env == nil, os.Environ(), self.override_env)
|
||||||
if env == nil {
|
|
||||||
env = os.Environ()
|
|
||||||
}
|
|
||||||
for _, x := range env {
|
for _, x := range env {
|
||||||
key, eval, _ := strings.Cut(x, "=")
|
key, eval, _ := strings.Cut(x, "=")
|
||||||
is_match, err := filepath.Match(val, key)
|
is_match, err := filepath.Match(val, key)
|
||||||
@@ -184,14 +251,12 @@ func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes st
|
|||||||
}
|
}
|
||||||
if len(includes) > 0 {
|
if len(includes) > 0 {
|
||||||
for _, incpath := range includes {
|
for _, incpath := range includes {
|
||||||
raw, err := os.ReadFile(incpath)
|
if raw, err := os.ReadFile(incpath); err == nil {
|
||||||
if err == nil {
|
if err := recurse(bytes.NewReader(raw), incpath, filepath.Dir(incpath)); err != nil {
|
||||||
err := recurse(bytes.NewReader(raw), incpath, filepath.Dir(incpath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return fmt.Errorf("Failed to process include %#v with error: %w", incpath, err)
|
add_bad_line(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ func TestConfigParsing(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
w(filepath.Join(tdir, "g.py"), []byte(`
|
||||||
|
print('gpy 1')
|
||||||
|
print('gpy 2')
|
||||||
|
`))
|
||||||
w(conf_file, []byte(
|
w(conf_file, []byte(
|
||||||
`error main
|
`error main
|
||||||
# igno
|
# igno
|
||||||
@@ -39,6 +43,7 @@ globin
|
|||||||
\clude sub/c?.c
|
\clude sub/c?.c
|
||||||
\onf
|
\onf
|
||||||
badline
|
badline
|
||||||
|
geninclude g.py
|
||||||
`))
|
`))
|
||||||
w(filepath.Join(tdir, "sub/b.conf"), []byte("incb cool\ninclude a.conf"))
|
w(filepath.Join(tdir, "sub/b.conf"), []byte("incb cool\ninclude a.conf"))
|
||||||
w(filepath.Join(tdir, "sub/c1.conf"), []byte("inc1 cool"))
|
w(filepath.Join(tdir, "sub/c1.conf"), []byte("inc1 cool"))
|
||||||
@@ -62,7 +67,7 @@ badline
|
|||||||
if err = p.ParseOverrides("over one", "over two"); err != nil {
|
if err = p.ParseOverrides("over one", "over two"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
diff := cmp.Diff([]string{"a one", "incb cool", "b x", "inc1 cool", "inc2 cool", "env cool", "inc notcool", "over one", "over two"}, parsed_lines)
|
diff := cmp.Diff([]string{"a one", "incb cool", "b x", "inc1 cool", "inc2 cool", "env cool", "inc notcool", "gpy 1", "gpy 2", "over one", "over two"}, parsed_lines)
|
||||||
if diff != "" {
|
if diff != "" {
|
||||||
t.Fatalf("Unexpected parsed config values:\n%s", diff)
|
t.Fatalf("Unexpected parsed config values:\n%s", diff)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user