diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 9190af4e50..4e3ce63da3 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -71,6 +71,7 @@ subcommands = [ 'qmk.cli.list.keymaps', 'qmk.cli.list.layouts', 'qmk.cli.kle2json', + 'qmk.cli.migrate', 'qmk.cli.multibuild', 'qmk.cli.new.keyboard', 'qmk.cli.new.keymap', diff --git a/lib/python/qmk/cli/migrate.py b/lib/python/qmk/cli/migrate.py new file mode 100644 index 0000000000..4164f9c8ad --- /dev/null +++ b/lib/python/qmk/cli/migrate.py @@ -0,0 +1,81 @@ +"""Migrate keyboard configuration to "Data Driven" +""" +import json +from pathlib import Path +from dotty_dict import dotty + +from milc import cli + +from qmk.keyboard import keyboard_completer, keyboard_folder, resolve_keyboard +from qmk.info import info_json, find_info_json +from qmk.json_encoders import InfoJSONEncoder +from qmk.json_schema import json_load + + +def _candidate_files(keyboard): + kb_dir = Path(resolve_keyboard(keyboard)) + + cur_dir = Path('keyboards') + files = [] + for dir in kb_dir.parts: + cur_dir = cur_dir / dir + files.append(cur_dir / 'config.h') + files.append(cur_dir / 'rules.mk') + + return [file for file in files if file.exists()] + + +@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the performed migrations based on the supplied value. Supported format is 'KEY' located from 'data/mappings'. May be passed multiple times.") +@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='The keyboard\'s name') +@cli.subcommand('Migrate keyboard config to "Data Driven".', hidden=True) +def migrate(cli): + """Migrate keyboard configuration to "Data Driven" + """ + # Merge mappings as we do not care to where "KEY" is found just that its removed + info_config_map = json_load(Path('data/mappings/info_config.hjson')) + info_rules_map = json_load(Path('data/mappings/info_rules.hjson')) + info_map = {**info_config_map, **info_rules_map} + + # Parse target info.json which will receive updates + target_info = Path(find_info_json(cli.args.keyboard)[0]) + info_data = dotty(json_load(target_info)) + + # Already parsed used for updates + kb_info_json = dotty(info_json(cli.args.keyboard)) + + # List of candidate files + files = _candidate_files(cli.args.keyboard) + + # Filter down keys if requested + keys = info_map.keys() + if cli.args.filter: + keys = list(set(keys) & set(cli.args.filter)) + + cli.log.info(f'{{fg_green}}Migrating keyboard {{fg_cyan}}{cli.args.keyboard}{{fg_green}}.{{fg_reset}}') + + # Start migration + for file in files: + cli.log.info(f' Migrating file {file}') + file_contents = file.read_text(encoding='utf-8').split('\n') + for key in keys: + for num, line in enumerate(file_contents): + if line.startswith(f'{key} =') or line.startswith(f'#define {key} '): + cli.log.info(f' Migrating {key}...') + + while line.rstrip().endswith('\\'): + file_contents.pop(num) + line = file_contents[num] + file_contents.pop(num) + + update_key = info_map[key]["info_key"] + if update_key in kb_info_json: + info_data[update_key] = kb_info_json[update_key] + + file.write_text('\n'.join(file_contents), encoding='utf-8') + + # Finally write out updated info.json + cli.log.info(f' Updating {target_info}') + target_info.write_text(json.dumps(info_data.to_dict(), cls=InfoJSONEncoder)) + + cli.log.info(f'{{fg_green}}Migration of keyboard {{fg_cyan}}{cli.args.keyboard}{{fg_green}} complete!{{fg_reset}}') + cli.log.info(f"Verify build with {{fg_yellow}}qmk compile -kb {cli.args.keyboard} -km default{{fg_reset}}.")