def parse_configurator_json(configurator_file): """Open and parse a configurator json export """ user_keymap = json_load(configurator_file) # Validate against the jsonschema try: validate(user_keymap, 'qmk.keymap.v1') except jsonschema.ValidationError as e: cli.log.error( f'Invalid JSON keymap: {configurator_file} : {e.message}') exit(1) orig_keyboard = user_keymap['keyboard'] aliases = json_load(Path('data/mappings/keyboard_aliases.json')) if orig_keyboard in aliases: if 'target' in aliases[orig_keyboard]: user_keymap['keyboard'] = aliases[orig_keyboard]['target'] if 'layouts' in aliases[orig_keyboard] and user_keymap[ 'layout'] in aliases[orig_keyboard]['layouts']: user_keymap['layout'] = aliases[orig_keyboard]['layouts'][ user_keymap['layout']] return user_keymap
def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.keyboard)) info_rules_map = json_load(Path('data/mappings/info_rules.json')) rules_mk_lines = [GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE] # Iterate through the info_rules map to generate basic rules for rules_key, info_dict in info_rules_map.items(): new_entry = process_mapping_rule(kb_info_json, rules_key, info_dict) if new_entry: rules_mk_lines.append(new_entry) # Iterate through features to enable/disable them if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): feature = feature.upper() enabled = 'yes' if enabled else 'no' rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') # Set SPLIT_TRANSPORT, if needed if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': rules_mk_lines.append('SPLIT_TRANSPORT ?= custom') # Set CUSTOM_MATRIX, if needed if kb_info_json.get('matrix_pins', {}).get('custom'): if kb_info_json.get('matrix_pins', {}).get('custom_lite'): rules_mk_lines.append('CUSTOM_MATRIX ?= lite') else: rules_mk_lines.append('CUSTOM_MATRIX ?= yes') # Show the results dump_lines(cli.args.output, rules_mk_lines) if cli.args.output: if cli.args.quiet: if cli.args.escape: print(cli.args.output.as_posix().replace(' ', '\\ ')) else: print(cli.args.output) else: cli.log.info('Wrote rules.mk to %s.', cli.args.output)
def generate_config_h(cli): """Generates the info_config.h file. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.keyboard)) # Build the info_config.h file. config_h_lines = [ GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once' ] generate_config_items(kb_info_json, config_h_lines) generate_matrix_size(kb_info_json, config_h_lines) if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) if 'split' in kb_info_json: generate_split_config(kb_info_json, config_h_lines) # Show the results dump_lines(cli.args.output, config_h_lines, cli.args.quiet)
def _extract_config_h(info_data): """Pull some keyboard information from existing config.h files """ config_c = config_h(info_data['keyboard_folder']) # Pull in data from the json map dotty_info = dotty(info_data) info_config_map = json_load(Path('data/mappings/info_config.json')) for config_key, info_dict in info_config_map.items(): info_key = info_dict['info_key'] key_type = info_dict.get('value_type', 'str') try: if config_key in config_c and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): _log_warning(info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key)) if key_type.startswith('array'): if '.' in key_type: key_type, array_type = key_type.split('.', 1) else: array_type = None config_value = config_c[config_key].replace('{', '').replace('}', '').strip() if array_type == 'int': dotty_info[info_key] = list(map(int, config_value.split(','))) else: dotty_info[info_key] = config_value.split(',') elif key_type == 'bool': dotty_info[info_key] = config_c[config_key] in true_values elif key_type == 'hex': dotty_info[info_key] = '0x' + config_c[config_key][2:].upper() elif key_type == 'list': dotty_info[info_key] = config_c[config_key].split() elif key_type == 'int': dotty_info[info_key] = int(config_c[config_key]) else: dotty_info[info_key] = config_c[config_key] except Exception as e: _log_warning(info_data, f'{config_key}->{info_key}: {e}') info_data.update(dotty_info) # Pull data that easily can't be mapped in json _extract_matrix_info(info_data, config_c) _extract_audio(info_data, config_c) _extract_split_main(info_data, config_c) _extract_split_transport(info_data, config_c) _extract_split_right_pins(info_data, config_c) return info_data
def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ if not cli.config.generate_rules_mk.keyboard: cli.log.error('Missing parameter: --keyboard') cli.subcommands['info'].print_help() return False if not is_keyboard(cli.config.generate_rules_mk.keyboard): cli.log.error('Invalid keyboard: "%s"', cli.config.generate_rules_mk.keyboard) return False kb_info_json = dotty(info_json(cli.config.generate_rules_mk.keyboard)) info_rules_map = json_load(Path('data/mappings/info_rules.json')) rules_mk_lines = [ '# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', '' ] # Iterate through the info_rules map to generate basic rules for rules_key, info_dict in info_rules_map.items(): new_entry = process_mapping_rule(kb_info_json, rules_key, info_dict) if new_entry: rules_mk_lines.append(new_entry) # Iterate through features to enable/disable them if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): if feature == 'bootmagic_lite' and enabled: rules_mk_lines.append('BOOTMAGIC_ENABLE ?= lite') else: feature = feature.upper() enabled = 'yes' if enabled else 'no' rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') # Show the results rules_mk = '\n'.join(rules_mk_lines) + '\n' if cli.args.output: cli.args.output.parent.mkdir(parents=True, exist_ok=True) if cli.args.output.exists(): cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak')) cli.args.output.write_text(rules_mk) if cli.args.quiet: if cli.args.escape: print(cli.args.output.as_posix().replace(' ', '\\ ')) else: print(cli.args.output) else: cli.log.info('Wrote rules.mk to %s.', cli.args.output) else: print(rules_mk)
def _extract_rules_mk(info_data, rules): """Pull some keyboard information from existing rules.mk files """ info_data['processor'] = rules.get( 'MCU', info_data.get('processor', 'atmega32u4')) if info_data['processor'] in CHIBIOS_PROCESSORS: arm_processor_rules(info_data, rules) elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS: avr_processor_rules(info_data, rules) else: cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor'])) unknown_processor_rules(info_data, rules) # Pull in data from the json map dotty_info = dotty(info_data) info_rules_map = json_load(Path('data/mappings/info_rules.json')) for rules_key, info_dict in info_rules_map.items(): info_key = info_dict['info_key'] key_type = info_dict.get('value_type', 'raw') try: if rules_key in rules and info_dict.get('invalid', False): _log_error( info_data, '%s in rules.mk is no longer a valid option' % rules_key) elif rules_key in rules and info_dict.get('deprecated', False): _log_warning( info_data, '%s in rules.mk is deprecated and will be removed at a later date' % rules_key) if rules_key in rules and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get( 'warn_duplicate', True): _log_warning( info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key)) dotty_info[info_key] = _config_to_json(key_type, rules[rules_key]) except Exception as e: _log_warning(info_data, f'{rules_key}->{info_key}: {e}') info_data.update(dotty_info) # Merge in config values that can't be easily mapped _extract_features(info_data, rules) return info_data
def _process_defaults(info_data): """Process any additional defaults based on currently discovered information """ defaults_map = json_load(Path('data/mappings/defaults.json')) for default_type in defaults_map.keys(): thing_map = defaults_map[default_type] if default_type in info_data: for key, value in thing_map.get(info_data[default_type], {}).items(): info_data[key] = value return info_data
def merge_info_jsons(keyboard, info_data): """Return a merged copy of all the info.json files for a keyboard. """ for info_file in find_info_json(keyboard): # Load and validate the JSON data new_info_data = json_load(info_file) if not isinstance(new_info_data, dict): _log_error( info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file), )) continue try: keyboard_validate(new_info_data) except jsonschema.ValidationError as e: json_path = '.'.join([str(p) for p in e.absolute_path]) cli.log.error('Not including data from file: %s', info_file) cli.log.error('\t%s: %s', json_path, e.message) continue # Merge layout data in if 'layout_aliases' in new_info_data: info_data['layout_aliases'] = { **info_data.get('layout_aliases', {}), **new_info_data['layout_aliases'] } del new_info_data['layout_aliases'] for layout_name, layout in new_info_data.get('layouts', {}).items(): if layout_name in info_data.get('layout_aliases', {}): _log_warning( info_data, f"info.json uses alias name {layout_name} instead of {info_data['layout_aliases'][layout_name]}" ) layout_name = info_data['layout_aliases'][layout_name] if layout_name in info_data['layouts']: for new_key, existing_key in zip( layout['layout'], info_data['layouts'][layout_name]['layout']): existing_key.update(new_key) else: layout['c_macro'] = False info_data['layouts'][layout_name] = layout # Update info_data with the new data if 'layouts' in new_info_data: del new_info_data['layouts'] deep_update(info_data, new_info_data) return info_data
def merge_info_jsons(keyboard, info_data): """Return a merged copy of all the info.json files for a keyboard. """ for info_file in find_info_json(keyboard): # Load and validate the JSON data new_info_data = json_load(info_file) if not isinstance(new_info_data, dict): _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) continue try: validate(new_info_data, 'qmk.keyboard.v1') except jsonschema.ValidationError as e: json_path = '.'.join([str(p) for p in e.absolute_path]) cli.log.error('Not including data from file: %s', info_file) cli.log.error('\t%s: %s', json_path, e.message) continue # Merge layout data in if 'layout_aliases' in new_info_data: info_data['layout_aliases'] = {**info_data.get('layout_aliases', {}), **new_info_data['layout_aliases']} del new_info_data['layout_aliases'] for layout_name, layout in new_info_data.get('layouts', {}).items(): if layout_name in info_data.get('layout_aliases', {}): _log_warning(info_data, f"info.json uses alias name {layout_name} instead of {info_data['layout_aliases'][layout_name]}") layout_name = info_data['layout_aliases'][layout_name] if layout_name in info_data['layouts']: if len(info_data['layouts'][layout_name]['layout']) != len(layout['layout']): msg = 'Number of keys for %s does not match! info.json specifies %d keys, C macro specifies %d' _log_error(info_data, msg % (layout_name, len(layout['layout']), len(info_data['layouts'][layout_name]['layout']))) else: for new_key, existing_key in zip(layout['layout'], info_data['layouts'][layout_name]['layout']): existing_key.update(new_key) else: if not all('matrix' in key_data.keys() for key_data in layout['layout']): _log_error(info_data, f'Layout "{layout_name}" has no "matrix" definition in either "info.json" or "<keyboard>.h"!') else: layout['c_macro'] = False info_data['layouts'][layout_name] = layout # Update info_data with the new data if 'layouts' in new_info_data: del new_info_data['layouts'] deep_update(info_data, new_info_data) return info_data
def format_json(cli): """Format a json file. """ json_file = json_load(cli.args.json_file) if cli.args.format == 'auto': try: validate(json_file, 'qmk.keyboard.v1') json_encoder = InfoJSONEncoder except ValidationError as e: cli.log.warning('File %s did not validate as a keyboard:\n\t%s', cli.args.json_file, e) cli.log.info('Treating %s as a keymap file.', cli.args.json_file) json_encoder = KeymapJSONEncoder elif cli.args.format == 'keyboard': json_encoder = InfoJSONEncoder elif cli.args.format == 'keymap': json_encoder = KeymapJSONEncoder else: # This should be impossible cli.log.error('Unknown format: %s', cli.args.format) return False if json_encoder == KeymapJSONEncoder and 'layout' in json_file: # Attempt to format the keycodes. layout = json_file['layout'] info_data = info_json(json_file['keyboard']) if layout in info_data.get('layout_aliases', {}): layout = json_file['layout'] = info_data['layout_aliases'][layout] if layout in info_data.get('layouts'): for layer_num, layer in enumerate(json_file['layers']): current_layer = [] last_row = 0 for keymap_key, info_key in zip( layer, info_data['layouts'][layout]['layout']): if last_row != info_key['y']: current_layer.append('JSON_NEWLINE') last_row = info_key['y'] current_layer.append(keymap_key) json_file['layers'][layer_num] = current_layer # Display the results print(json.dumps(json_file, cls=json_encoder))
def generate_config_items(kb_info_json, config_h_lines): """Iterate through the info_config map to generate basic config values. """ info_config_map = json_load(Path('data/mappings/info_config.json')) for config_key, info_dict in info_config_map.items(): info_key = info_dict['info_key'] key_type = info_dict.get('value_type', 'str') to_config = info_dict.get('to_config', True) if not to_config: continue try: config_value = kb_info_json[info_key] except KeyError: continue if key_type.startswith('array'): config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append( f'# define {config_key} {{ {", ".join(map(str, config_value))} }}' ) config_h_lines.append(f'#endif // {config_key}') elif key_type == 'bool': if config_value: config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append(f'# define {config_key}') config_h_lines.append(f'#endif // {config_key}') elif key_type == 'mapping': for key, value in config_value.items(): config_h_lines.append('') config_h_lines.append(f'#ifndef {key}') config_h_lines.append(f'# define {key} {value}') config_h_lines.append(f'#endif // {key}') elif key_type == 'bcd_version': (major, minor, revision) = config_value.split('.') config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append( f'# define {config_key} 0x{major.zfill(2)}{minor}{revision}') config_h_lines.append(f'#endif // {config_key}') else: config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append(f'# define {config_key} {config_value}') config_h_lines.append(f'#endif // {config_key}')
def _extract_config_h(info_data, config_c): """Pull some keyboard information from existing config.h files """ # Pull in data from the json map dotty_info = dotty(info_data) info_config_map = json_load(Path('data/mappings/info_config.json')) for config_key, info_dict in info_config_map.items(): info_key = info_dict['info_key'] key_type = info_dict.get('value_type', 'raw') try: if config_key in config_c and info_dict.get('invalid', False): _log_error( info_data, '%s in config.h is no longer a valid option' % config_key) elif config_key in config_c and info_dict.get('deprecated', False): _log_warning( info_data, '%s in config.h is deprecated and will be removed at a later date' % config_key) if config_key in config_c and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get( 'warn_duplicate', True): _log_warning( info_data, '%s in config.h is overwriting %s in info.json' % (config_key, info_key)) dotty_info[info_key] = _config_to_json(key_type, config_c[config_key]) except Exception as e: _log_warning(info_data, f'{config_key}->{info_key}: {e}') info_data.update(dotty_info) # Pull data that easily can't be mapped in json _extract_matrix_info(info_data, config_c) _extract_audio(info_data, config_c) _extract_secure_unlock(info_data, config_c) _extract_split_main(info_data, config_c) _extract_split_transport(info_data, config_c) _extract_split_right_pins(info_data, config_c) _extract_device_version(info_data) return info_data
def parse_configurator_json(configurator_file): """Open and parse a configurator json export """ # FIXME(skullydazed/anyone): Add validation here user_keymap = json.load(configurator_file) orig_keyboard = user_keymap['keyboard'] aliases = json_load(Path('data/mappings/keyboard_aliases.json')) if orig_keyboard in aliases: if 'target' in aliases[orig_keyboard]: user_keymap['keyboard'] = aliases[orig_keyboard]['target'] if 'layouts' in aliases[orig_keyboard] and user_keymap['layout'] in aliases[orig_keyboard]['layouts']: user_keymap['layout'] = aliases[orig_keyboard]['layouts'][user_keymap['layout']] return user_keymap
def generate_config_h(cli): """Generates the info_config.h file. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.keyboard)) # Build the info_config.h file. config_h_lines = [ '/* This file was generated by `qmk generate-config-h`. Do not edit or copy.', ' */', '', '#pragma once' ] generate_config_items(kb_info_json, config_h_lines) generate_matrix_size(kb_info_json, config_h_lines) if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) if 'split' in kb_info_json: generate_split_config(kb_info_json, config_h_lines) # Show the results config_h = '\n'.join(config_h_lines) if cli.args.output: cli.args.output.parent.mkdir(parents=True, exist_ok=True) if cli.args.output.exists(): cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak')) cli.args.output.write_text(config_h) if not cli.args.quiet: cli.log.info('Wrote info_config.h to %s.', cli.args.output) else: print(config_h)
def keyboard_folder(keyboard): """Returns the actual keyboard folder. This checks aliases and DEFAULT_FOLDER to resolve the actual path for a keyboard. """ aliases = json_load(Path('data/mappings/keyboard_aliases.json')) if keyboard in aliases: keyboard = aliases[keyboard].get('target', keyboard) rules_mk_file = Path(base_path, keyboard, 'rules.mk') if rules_mk_file.exists(): rules_mk = parse_rules_mk_file(rules_mk_file) keyboard = rules_mk.get('DEFAULT_FOLDER', keyboard) if not qmk.path.is_keyboard(keyboard): raise ValueError(f'Invalid keyboard: {keyboard}') return keyboard
def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ # Determine our keyboard/keymap if cli.args.keymap: km = locate_keymap(cli.args.keyboard, cli.args.keymap) km_json = json_load(km) validate(km_json, 'qmk.keymap.v1') kb_info_json = dotty(km_json.get('config', {})) else: kb_info_json = dotty(info_json(cli.args.keyboard)) info_rules_map = json_load(Path('data/mappings/info_rules.json')) rules_mk_lines = [ '# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', '' ] # Iterate through the info_rules map to generate basic rules for rules_key, info_dict in info_rules_map.items(): new_entry = process_mapping_rule(kb_info_json, rules_key, info_dict) if new_entry: rules_mk_lines.append(new_entry) # Iterate through features to enable/disable them if 'features' in kb_info_json: for feature, enabled in kb_info_json['features'].items(): if feature == 'bootmagic_lite' and enabled: rules_mk_lines.append('BOOTMAGIC_ENABLE ?= lite') else: feature = feature.upper() enabled = 'yes' if enabled else 'no' rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') # Set SPLIT_TRANSPORT, if needed if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': rules_mk_lines.append('SPLIT_TRANSPORT ?= custom') # Set CUSTOM_MATRIX, if needed if kb_info_json.get('matrix_pins', {}).get('custom'): if kb_info_json.get('matrix_pins', {}).get('custom_lite'): rules_mk_lines.append('CUSTOM_MATRIX ?= lite') else: rules_mk_lines.append('CUSTOM_MATRIX ?= yes') # Show the results rules_mk = '\n'.join(rules_mk_lines) + '\n' if cli.args.output: cli.args.output.parent.mkdir(parents=True, exist_ok=True) if cli.args.output.exists(): cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak')) cli.args.output.write_text(rules_mk) if cli.args.quiet: if cli.args.escape: print(cli.args.output.as_posix().replace(' ', '\\ ')) else: print(cli.args.output) else: cli.log.info('Wrote rules.mk to %s.', cli.args.output) else: print(rules_mk)
def _extract_rules_mk(info_data): """Pull some keyboard information from existing rules.mk files """ rules = rules_mk(info_data['keyboard_folder']) info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4')) if info_data['processor'] in CHIBIOS_PROCESSORS: arm_processor_rules(info_data, rules) elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS: avr_processor_rules(info_data, rules) else: cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor'])) unknown_processor_rules(info_data, rules) # Pull in data from the json map dotty_info = dotty(info_data) info_rules_map = json_load(Path('data/mappings/info_rules.json')) for rules_key, info_dict in info_rules_map.items(): info_key = info_dict['info_key'] key_type = info_dict.get('value_type', 'str') try: if rules_key in rules and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): _log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key)) if key_type.startswith('array'): if '.' in key_type: key_type, array_type = key_type.split('.', 1) else: array_type = None rules_value = rules[rules_key].replace('{', '').replace('}', '').strip() if array_type == 'int': dotty_info[info_key] = list(map(int, rules_value.split(','))) else: dotty_info[info_key] = rules_value.split(',') elif key_type == 'list': dotty_info[info_key] = rules[rules_key].split() elif key_type == 'bool': dotty_info[info_key] = rules[rules_key] in true_values elif key_type == 'hex': dotty_info[info_key] = '0x' + rules[rules_key][2:].upper() elif key_type == 'int': dotty_info[info_key] = int(rules[rules_key]) else: dotty_info[info_key] = rules[rules_key] except Exception as e: _log_warning(info_data, f'{rules_key}->{info_key}: {e}') info_data.update(dotty_info) # Merge in config values that can't be easily mapped _extract_features(info_data, rules) return info_data
def generate_api(cli): """Generates the QMK API data. """ api_data_dir = Path('api_data') v1_dir = api_data_dir / 'v1' 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 keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target if not api_data_dir.exists(): api_data_dir.mkdir() kb_all = {} usb_list = {} # Generate and write keyboard specific JSON files for keyboard_name in list_keyboards(): kb_all[keyboard_name] = info_json(keyboard_name) keyboard_dir = v1_dir / 'keyboards' / keyboard_name keyboard_info = keyboard_dir / 'info.json' keyboard_readme = keyboard_dir / 'readme.md' keyboard_readme_src = find_readme(keyboard_name) keyboard_dir.mkdir(parents=True, exist_ok=True) keyboard_json = json.dumps({'last_updated': current_datetime(), 'keyboards': {keyboard_name: kb_all[keyboard_name]}}) if not cli.args.dry_run: keyboard_info.write_text(keyboard_json) cli.log.debug('Wrote file %s', keyboard_info) if keyboard_readme_src: copyfile(keyboard_readme_src, keyboard_readme) cli.log.debug('Copied %s -> %s', keyboard_readme_src, keyboard_readme) if 'usb' in kb_all[keyboard_name]: usb = kb_all[keyboard_name]['usb'] if 'vid' in usb and usb['vid'] not in usb_list: usb_list[usb['vid']] = {} if 'pid' in usb and usb['pid'] not in usb_list[usb['vid']]: usb_list[usb['vid']][usb['pid']] = {} if 'vid' in usb and 'pid' in usb: usb_list[usb['vid']][usb['pid']][keyboard_name] = usb # Generate data for the global files keyboard_list = sorted(kb_all) keyboard_aliases = json_load(Path('data/mappings/keyboard_aliases.json')) keyboard_metadata = { 'last_updated': current_datetime(), 'keyboards': keyboard_list, 'keyboard_aliases': keyboard_aliases, 'usb': usb_list, } # Write the global JSON files keyboard_all_json = json.dumps({'last_updated': current_datetime(), 'keyboards': kb_all}, cls=InfoJSONEncoder) usb_json = json.dumps({'last_updated': current_datetime(), 'usb': usb_list}, cls=InfoJSONEncoder) keyboard_list_json = json.dumps({'last_updated': current_datetime(), 'keyboards': keyboard_list}, cls=InfoJSONEncoder) keyboard_aliases_json = json.dumps({'last_updated': current_datetime(), 'keyboard_aliases': keyboard_aliases}, cls=InfoJSONEncoder) keyboard_metadata_json = json.dumps(keyboard_metadata, cls=InfoJSONEncoder) if not cli.args.dry_run: keyboard_all_file.write_text(keyboard_all_json) usb_file.write_text(usb_json) keyboard_list_file.write_text(keyboard_list_json) keyboard_aliases_file.write_text(keyboard_aliases_json) keyboard_metadata_file.write_text(keyboard_metadata_json)
def new_keyboard(cli): """Creates a new keyboard. """ cli.log.info( '{style_bright}Generating a new QMK keyboard directory{style_normal}') cli.echo('') kb_name = cli.args.keyboard if cli.args.keyboard else prompt_keyboard() user_name = cli.config.new_keyboard.name if cli.config.new_keyboard.name else prompt_user( ) real_name = cli.args.realname or cli.config.new_keyboard.name if cli.args.realname or cli.config.new_keyboard.name else prompt_name( user_name) default_layout = cli.args.layout if cli.args.layout else prompt_layout() mcu = cli.args.type if cli.args.type else prompt_mcu() if not validate_keyboard_name(kb_name): cli.log.error( 'Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.' ) return 1 if keyboard(kb_name).exists(): cli.log.error( f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.' ) return 1 # Preprocess any development_board presets if mcu in dev_boards: defaults_map = json_load(Path('data/mappings/defaults.json')) board = defaults_map['development_board'][mcu] mcu = board['processor'] bootloader = board['bootloader'] else: bootloader = select_default_bootloader(mcu) tokens = { # Comment here is to force multiline formatting 'YEAR': str(date.today().year), 'KEYBOARD': kb_name, 'USER_NAME': user_name, 'REAL_NAME': real_name, 'LAYOUT': default_layout, 'MCU': mcu, 'BOOTLOADER': bootloader } if cli.config.general.verbose: cli.log.info("Creating keyboard with:") for key, value in tokens.items(): cli.echo(f" {key.ljust(10)}: {value}") # TODO: detach community layout and rename to just "LAYOUT" if default_layout == 'none of the above': default_layout = "ortho_4x4" # begin with making the deepest folder in the tree keymaps_path = keyboard(kb_name) / 'keymaps/' keymaps_path.mkdir(parents=True) # copy in keymap.c or keymap.json community_keymap = Path(COMMUNITY / f'{default_layout}/default_{default_layout}/') shutil.copytree(community_keymap, keymaps_path / 'default') # process template files for file in list(TEMPLATE.iterdir()): replace_placeholders(file, keyboard(kb_name) / file.name, tokens) # merge in infos community_info = Path(COMMUNITY / f'{default_layout}/info.json') augment_community_info(community_info, keyboard(kb_name) / community_info.name) cli.log.info( f'{{fg_green}}Created a new keyboard called {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}' ) cli.log.info( f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},' ) cli.log.info('or open the directory in your preferred text editor.') cli.log.info( f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}." )
def generate_config_h(cli): """Generates the info_config.h file. """ # Determine our keyboard(s) if not cli.config.generate_config_h.keyboard: cli.log.error('Missing parameter: --keyboard') cli.subcommands['info'].print_help() return False if not is_keyboard(cli.config.generate_config_h.keyboard): cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) return False # Build the info_config.h file. kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard)) info_config_map = json_load(Path('data/mappings/info_config.json')) config_h_lines = [ '/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once' ] # Iterate through the info_config map to generate basic things for config_key, info_dict in info_config_map.items(): info_key = info_dict['info_key'] key_type = info_dict.get('value_type', 'str') to_config = info_dict.get('to_config', True) if not to_config: continue try: config_value = kb_info_json[info_key] except KeyError: continue if key_type.startswith('array'): config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append( f'# define {config_key} {{ {", ".join(map(str, config_value))} }}' ) config_h_lines.append(f'#endif // {config_key}') elif key_type == 'bool': if config_value: config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append(f'# define {config_key}') config_h_lines.append(f'#endif // {config_key}') elif key_type == 'mapping': for key, value in config_value.items(): config_h_lines.append('') config_h_lines.append(f'#ifndef {key}') config_h_lines.append(f'# define {key} {value}') config_h_lines.append(f'#endif // {key}') else: config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append(f'# define {config_key} {config_value}') config_h_lines.append(f'#endif // {config_key}') if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) # Show the results config_h = '\n'.join(config_h_lines) if cli.args.output: cli.args.output.parent.mkdir(parents=True, exist_ok=True) if cli.args.output.exists(): cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak')) cli.args.output.write_text(config_h) if not cli.args.quiet: cli.log.info('Wrote info_config.h to %s.', cli.args.output) else: print(config_h)