From c01af59c85b5c6646102a3d91cbeb0f04571c6bf Mon Sep 17 00:00:00 2001 From: Paul Egan Date: Wed, 7 May 2025 11:51:12 +0100 Subject: [PATCH 1/2] Fix for #4. Special characters like ^ need to be escaped. --- gencompose.py | 4 +++- tests/test_gencompose.py | 14 ++++++++++++++ tests/test_special_chars.yaml | 5 +++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/test_gencompose.py create mode 100644 tests/test_special_chars.yaml diff --git a/gencompose.py b/gencompose.py index 075f708..34ea240 100644 --- a/gencompose.py +++ b/gencompose.py @@ -127,7 +127,9 @@ def data_to_mac_dict(data): }; }; """ - updated = object_hook(data, lambda value: f'INSERT:{value}', str) + escape_special_chars = lambda c: f'\\{c}' if c in '^@~#$' else c + escape_keys = lambda d: {escape_special_chars(k): (escape_keys(v) if type(v) is dict else v) for k, v in d.items()} + updated = object_hook(escape_keys(data), lambda value: f'INSERT:{value}', str) text = json.dumps(updated, indent=2, ensure_ascii=False) repl = lambda value: f'("insertText:", "{value.groups()[0]}");' text = re.sub('"INSERT:(.+)",*', repl, text) diff --git a/tests/test_gencompose.py b/tests/test_gencompose.py new file mode 100644 index 0000000..d48d8f3 --- /dev/null +++ b/tests/test_gencompose.py @@ -0,0 +1,14 @@ +from pathlib import Path + +from click.testing import CliRunner +from gencompose import main + + +def test_special_characters(): + runner = CliRunner() + file = Path(__file__).parent / 'test_special_chars.yaml' + result = runner.invoke(main, args=[str(file)]) + print(result.output) + assert result.exit_code == 0 + assert '"\\\\^"' in result.output + assert '"2"' in result.output diff --git a/tests/test_special_chars.yaml b/tests/test_special_chars.yaml new file mode 100644 index 0000000..845cc31 --- /dev/null +++ b/tests/test_special_chars.yaml @@ -0,0 +1,5 @@ +^2: ² +$$: ﹩ +~~: ~ +"@@": @ +"#x": ⌧ From 097ee90aed1550148c2aa9877fe5cebd9e1d8df5 Mon Sep 17 00:00:00 2001 From: Paul Egan Date: Mon, 12 May 2025 21:23:01 +0100 Subject: [PATCH 2/2] Add --escape-modifier-keys to make this functionality change an explicit choice --- README.md | 1 + gencompose.py | 15 +++++++++------ tests/test_gencompose.py | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0c4b114..a10f816 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Usage: gen-compose [OPTIONS] [MAPPINGS]... Options: --key TEXT key to use as compose key [default: §] -r, --raw just keymap without prefix + -e, --escape-modifier-keys escape keys such as ^, so they're treated as the character and not a modifier --help Show this message and exit. ``` diff --git a/gencompose.py b/gencompose.py index 34ea240..63699e6 100644 --- a/gencompose.py +++ b/gencompose.py @@ -66,14 +66,15 @@ def object_hook(dict_, func: Callable, types: Union[Tuple[Type], Type] = dict): @click.argument('mappings', type=click.File(), nargs=-1) @click.option('--key', default='§', show_default=True, help='key to use as compose key') @click.option('-r', '--raw', is_flag=True, help='just keymap without prefix') -def main(mappings, raw, key): +@click.option('-e', '--escape-modifier-keys', is_flag=True, help='escape modifier keys such as "^"') +def main(mappings, raw, key, escape_modifier_keys): """Generate macos rebind file from compose json mapping""" all_maps = {} for mapping in mappings: yamldata = yaml.load(mapping.read(), Loader=yaml.Loader) all_maps.update(**{str(k): str(v) for k, v in yamldata.items()}) all_maps = read_paths(all_maps) - text = data_to_mac_dict(all_maps) + text = data_to_mac_dict(all_maps, escape_modifier_keys) if raw: echo(text) else: @@ -114,7 +115,7 @@ def read_paths(data): return parsed -def data_to_mac_dict(data): +def data_to_mac_dict(data, escape_modifer_keys=False): """ converts dictionary data to macos keymap.dict format @@ -127,9 +128,11 @@ def data_to_mac_dict(data): }; }; """ - escape_special_chars = lambda c: f'\\{c}' if c in '^@~#$' else c - escape_keys = lambda d: {escape_special_chars(k): (escape_keys(v) if type(v) is dict else v) for k, v in d.items()} - updated = object_hook(escape_keys(data), lambda value: f'INSERT:{value}', str) + if escape_modifer_keys: + escape_special_chars = lambda c: f'\\{c}' if c in '^@~#$' else c + escape_keys = lambda d: {escape_special_chars(k): (escape_keys(v) if type(v) is dict else v) for k, v in d.items()} + data = escape_keys(data) + updated = object_hook(data, lambda value: f'INSERT:{value}', str) text = json.dumps(updated, indent=2, ensure_ascii=False) repl = lambda value: f'("insertText:", "{value.groups()[0]}");' text = re.sub('"INSERT:(.+)",*', repl, text) diff --git a/tests/test_gencompose.py b/tests/test_gencompose.py index d48d8f3..c8a0e17 100644 --- a/tests/test_gencompose.py +++ b/tests/test_gencompose.py @@ -7,7 +7,7 @@ def test_special_characters(): runner = CliRunner() file = Path(__file__).parent / 'test_special_chars.yaml' - result = runner.invoke(main, args=[str(file)]) + result = runner.invoke(main, args=['--escape-modifier-keys', str(file)]) print(result.output) assert result.exit_code == 0 assert '"\\\\^"' in result.output