From 3e59bbd7316aaa61a4f90bfa1b50f95cc967d0dc Mon Sep 17 00:00:00 2001
From: Joel Challis <git@zvecr.com>
Date: Wed, 30 Nov 2022 20:08:54 +0000
Subject: [PATCH] Automate "Data Driven" migrations (#17820)

---
 lib/python/qmk/cli/__init__.py |  1 +
 lib/python/qmk/cli/migrate.py  | 81 ++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+)
 create mode 100644 lib/python/qmk/cli/migrate.py

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}}.")