2020-10-25 14:48:44 -07:00
""" This script automates the generation of the QMK API data.
"""
from pathlib import Path
2022-02-27 03:28:51 -08:00
import shutil
2020-10-25 14:48:44 -07:00
import json
from milc import cli
from qmk . datetime import current_datetime
from qmk . info import info_json
2021-03-24 09:26:38 -07:00
from qmk . json_schema import json_load
2023-01-10 17:38:35 -08:00
from qmk . keymap import list_keymaps
2021-05-24 19:38:27 -07:00
from qmk . keyboard import find_readme , list_keyboards
2022-12-08 16:54:52 -08:00
from qmk . keycodes import load_spec , list_versions , list_languages
2020-10-25 14:48:44 -07:00
2022-08-05 23:14:29 -07:00
DATA_PATH = Path ( ' data ' )
TEMPLATE_PATH = DATA_PATH / ' templates/api/ '
2022-02-27 03:28:51 -08:00
BUILD_API_PATH = Path ( ' .build/api_data/ ' )
2020-10-25 14:48:44 -07:00
2022-11-23 10:01:07 -08:00
def _list_constants ( output_folder ) :
""" Produce a map of available constants
"""
ret = { }
for file in ( output_folder / ' constants ' ) . glob ( ' **/*_[0-9].[0-9].[0-9].json ' ) :
name , version = file . stem . rsplit ( ' _ ' , 1 )
if name not in ret :
ret [ name ] = [ ]
ret [ name ] . append ( version )
# Ensure content is sorted
for name in ret :
ret [ name ] = sorted ( ret [ name ] )
return ret
2022-11-05 03:30:09 -07:00
def _resolve_keycode_specs ( output_folder ) :
""" To make it easier for consumers, publish pre-merged spec files
"""
for version in list_versions ( ) :
overall = load_spec ( version )
output_file = output_folder / f ' constants/keycodes_ { version } .json '
2023-03-29 05:01:16 -07:00
output_file . write_text ( json . dumps ( overall , separators = ( ' , ' , ' : ' ) ) , encoding = ' utf-8 ' )
2022-11-05 03:30:09 -07:00
2022-12-08 16:54:52 -08:00
for lang in list_languages ( ) :
for version in list_versions ( lang ) :
overall = load_spec ( version , lang )
output_file = output_folder / f ' constants/keycodes_ { lang } _ { version } .json '
2023-03-29 05:01:16 -07:00
output_file . write_text ( json . dumps ( overall , separators = ( ' , ' , ' : ' ) ) , encoding = ' utf-8 ' )
2022-12-08 16:54:52 -08:00
2022-11-07 19:03:02 -08:00
# Purge files consumed by 'load_spec'
shutil . rmtree ( output_folder / ' constants/keycodes/ ' )
def _filtered_copy ( src , dst ) :
src = Path ( src )
dst = Path ( dst )
if dst . suffix == ' .hjson ' :
data = json_load ( src )
dst = dst . with_suffix ( ' .json ' )
2023-03-29 05:01:16 -07:00
dst . write_text ( json . dumps ( data , separators = ( ' , ' , ' : ' ) ) , encoding = ' utf-8 ' )
2022-11-07 19:03:02 -08:00
return dst
2023-03-27 12:01:07 -07:00
if dst . suffix == ' .jsonschema ' :
data = json_load ( src )
dst . write_text ( json . dumps ( data ) , encoding = ' utf-8 ' )
return dst
2022-11-07 19:03:02 -08:00
return shutil . copy2 ( src , dst )
2022-11-05 03:30:09 -07:00
2022-08-05 23:14:29 -07:00
def _filtered_keyboard_list ( ) :
""" Perform basic filtering of list_keyboards
"""
keyboard_list = list_keyboards ( )
if cli . args . filter :
kb_list = [ ]
for keyboard_name in keyboard_list :
if any ( i in keyboard_name for i in cli . args . filter ) :
kb_list . append ( keyboard_name )
keyboard_list = kb_list
return keyboard_list
2021-05-10 11:18:44 -07:00
@cli.argument ( ' -n ' , ' --dry-run ' , arg_only = True , action = ' store_true ' , help = " Don ' t write the data to disk. " )
2022-02-27 03:28:51 -08:00
@cli.argument ( ' -f ' , ' --filter ' , arg_only = True , action = ' append ' , default = [ ] , help = " Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times. " )
2022-08-05 23:14:29 -07:00
@cli.subcommand ( ' Generate QMK API data ' , hidden = False if cli . config . user . developer else True )
2020-10-25 14:48:44 -07:00
def generate_api ( cli ) :
""" Generates the QMK API data.
"""
2022-02-27 03:28:51 -08:00
v1_dir = BUILD_API_PATH / ' v1 '
2021-03-24 20:33:25 -07:00
keyboard_all_file = v1_dir / ' keyboards.json ' # A massive JSON containing everything
keyboard_list_file = v1_dir / ' keyboard_list.json ' # A simple list of keyboard targets
keyboard_aliases_file = v1_dir / ' keyboard_aliases.json ' # A list of historical keyboard names and their new name
2021-03-24 09:26:38 -07:00
keyboard_metadata_file = v1_dir / ' keyboard_metadata.json ' # All the data configurator/via needs for initialization
2022-11-23 10:01:07 -08:00
constants_metadata_file = v1_dir / ' constants_metadata.json ' # Metadata for available constants
2021-03-24 20:33:25 -07:00
usb_file = v1_dir / ' usb.json ' # A mapping of USB VID/PID -> keyboard target
2020-10-25 14:48:44 -07:00
2022-08-05 23:14:29 -07:00
if BUILD_API_PATH . exists ( ) :
shutil . rmtree ( BUILD_API_PATH )
shutil . copytree ( TEMPLATE_PATH , BUILD_API_PATH )
2022-11-07 19:03:02 -08:00
shutil . copytree ( DATA_PATH , v1_dir , copy_function = _filtered_copy )
2022-08-05 23:14:29 -07:00
2022-02-27 03:28:51 -08:00
# Filter down when required
2022-08-05 23:14:29 -07:00
keyboard_list = _filtered_keyboard_list ( )
2020-10-25 14:48:44 -07:00
2021-03-24 09:26:38 -07:00
kb_all = { }
usb_list = { }
2020-10-25 14:48:44 -07:00
# Generate and write keyboard specific JSON files
2022-02-27 03:28:51 -08:00
for keyboard_name in keyboard_list :
2023-01-19 19:38:19 -08:00
kb_json = info_json ( keyboard_name )
kb_all [ keyboard_name ] = kb_json
2023-01-10 17:38:35 -08:00
2020-10-25 14:48:44 -07:00
keyboard_dir = v1_dir / ' keyboards ' / keyboard_name
keyboard_info = keyboard_dir / ' info.json '
keyboard_readme = keyboard_dir / ' readme.md '
2021-05-24 19:38:27 -07:00
keyboard_readme_src = find_readme ( keyboard_name )
2020-10-25 14:48:44 -07:00
2023-01-19 19:38:19 -08:00
# Populate the list of JSON keymaps
for keymap in list_keymaps ( keyboard_name , c = False , fullpath = True ) :
kb_json [ ' keymaps ' ] [ keymap . name ] = {
# TODO: deprecate 'url' as consumer needs to know its potentially hjson
' url ' : f ' https://raw.githubusercontent.com/qmk/qmk_firmware/master/ { keymap } /keymap.json ' ,
# Instead consumer should grab from API and not repo directly
' path ' : ( keymap / ' keymap.json ' ) . as_posix ( ) ,
}
2020-10-25 14:48:44 -07:00
keyboard_dir . mkdir ( parents = True , exist_ok = True )
2023-03-29 05:01:16 -07:00
keyboard_json = json . dumps ( { ' last_updated ' : current_datetime ( ) , ' keyboards ' : { keyboard_name : kb_json } } , separators = ( ' , ' , ' : ' ) )
2021-05-10 11:18:44 -07:00
if not cli . args . dry_run :
2023-01-19 19:38:19 -08:00
keyboard_info . write_text ( keyboard_json , encoding = ' utf-8 ' )
2021-05-10 11:18:44 -07:00
cli . log . debug ( ' Wrote file %s ' , keyboard_info )
2020-10-25 14:48:44 -07:00
2021-05-24 19:38:27 -07:00
if keyboard_readme_src :
2022-02-27 03:28:51 -08:00
shutil . copyfile ( keyboard_readme_src , keyboard_readme )
2021-05-10 11:18:44 -07:00
cli . log . debug ( ' Copied %s -> %s ' , keyboard_readme_src , keyboard_readme )
2020-10-25 14:48:44 -07:00
2023-01-19 19:38:19 -08:00
# resolve keymaps as json
for keymap in kb_json [ ' keymaps ' ] :
keymap_hjson = kb_json [ ' keymaps ' ] [ keymap ] [ ' path ' ]
keymap_json = v1_dir / keymap_hjson
keymap_json . parent . mkdir ( parents = True , exist_ok = True )
2023-03-29 05:01:16 -07:00
keymap_json . write_text ( json . dumps ( json_load ( Path ( keymap_hjson ) ) , separators = ( ' , ' , ' : ' ) ) , encoding = ' utf-8 ' )
2023-01-19 19:38:19 -08:00
cli . log . debug ( ' Wrote keymap %s ' , keymap_json )
if ' usb ' in kb_json :
usb = kb_json [ ' usb ' ]
2020-10-25 14:48:44 -07:00
2021-03-24 09:26:38 -07:00
if ' vid ' in usb and usb [ ' vid ' ] not in usb_list :
usb_list [ usb [ ' vid ' ] ] = { }
2020-10-25 14:48:44 -07:00
2021-03-24 09:26:38 -07:00
if ' pid ' in usb and usb [ ' pid ' ] not in usb_list [ usb [ ' vid ' ] ] :
usb_list [ usb [ ' vid ' ] ] [ usb [ ' pid ' ] ] = { }
2020-10-25 14:48:44 -07:00
2020-12-30 10:27:37 -08:00
if ' vid ' in usb and ' pid ' in usb :
2021-03-24 09:26:38 -07:00
usb_list [ usb [ ' vid ' ] ] [ usb [ ' pid ' ] ] [ keyboard_name ] = usb
2020-10-25 14:48:44 -07:00
2021-05-10 11:18:44 -07:00
# Generate data for the global files
2021-03-24 09:26:38 -07:00
keyboard_list = sorted ( kb_all )
2022-11-07 17:05:08 -08:00
keyboard_aliases = json_load ( Path ( ' data/mappings/keyboard_aliases.hjson ' ) )
2021-03-24 09:26:38 -07:00
keyboard_metadata = {
' last_updated ' : current_datetime ( ) ,
' keyboards ' : keyboard_list ,
' keyboard_aliases ' : keyboard_aliases ,
2021-03-24 20:33:25 -07:00
' usb ' : usb_list ,
2021-03-24 09:26:38 -07:00
}
2021-05-10 11:18:44 -07:00
2022-11-05 03:30:09 -07:00
# Feature specific handling
_resolve_keycode_specs ( v1_dir )
2021-05-10 11:18:44 -07:00
# Write the global JSON files
2023-03-29 05:01:16 -07:00
keyboard_all_json = json . dumps ( { ' last_updated ' : current_datetime ( ) , ' keyboards ' : kb_all } , separators = ( ' , ' , ' : ' ) )
usb_json = json . dumps ( { ' last_updated ' : current_datetime ( ) , ' usb ' : usb_list } , separators = ( ' , ' , ' : ' ) )
keyboard_list_json = json . dumps ( { ' last_updated ' : current_datetime ( ) , ' keyboards ' : keyboard_list } , separators = ( ' , ' , ' : ' ) )
keyboard_aliases_json = json . dumps ( { ' last_updated ' : current_datetime ( ) , ' keyboard_aliases ' : keyboard_aliases } , separators = ( ' , ' , ' : ' ) )
keyboard_metadata_json = json . dumps ( keyboard_metadata , separators = ( ' , ' , ' : ' ) )
constants_metadata_json = json . dumps ( { ' last_updated ' : current_datetime ( ) , ' constants ' : _list_constants ( v1_dir ) } , separators = ( ' , ' , ' : ' ) )
2021-05-10 11:18:44 -07:00
if not cli . args . dry_run :
2023-01-19 19:38:19 -08:00
keyboard_all_file . write_text ( keyboard_all_json , encoding = ' utf-8 ' )
usb_file . write_text ( usb_json , encoding = ' utf-8 ' )
keyboard_list_file . write_text ( keyboard_list_json , encoding = ' utf-8 ' )
keyboard_aliases_file . write_text ( keyboard_aliases_json , encoding = ' utf-8 ' )
keyboard_metadata_file . write_text ( keyboard_metadata_json , encoding = ' utf-8 ' )
constants_metadata_file . write_text ( constants_metadata_json , encoding = ' utf-8 ' )