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_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 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']: 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 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 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_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 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 info_json(keyboard): """Generate the info.json data for a specific keyboard. """ cur_dir = Path('keyboards') root_rules_mk = parse_rules_mk_file(cur_dir / keyboard / 'rules.mk') if 'DEFAULT_FOLDER' in root_rules_mk: keyboard = root_rules_mk['DEFAULT_FOLDER'] info_data = { 'keyboard_name': str(keyboard), 'keyboard_folder': str(keyboard), 'keymaps': {}, 'layouts': {}, 'parse_errors': [], 'parse_warnings': [], 'maintainer': 'qmk', } # Populate the list of JSON keymaps for keymap in list_keymaps(keyboard, c=False, fullpath=True): info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'} # Populate layout data layouts, aliases = _search_keyboard_h(keyboard) if aliases: info_data['layout_aliases'] = aliases for layout_name, layout_json in layouts.items(): if not layout_name.startswith('LAYOUT_kc'): layout_json['c_macro'] = True info_data['layouts'][layout_name] = layout_json # Merge in the data from info.json, config.h, and rules.mk info_data = merge_info_jsons(keyboard, info_data) info_data = _extract_rules_mk(info_data) info_data = _extract_config_h(info_data) # Ensure that we have matrix row and column counts info_data = _matrix_size(info_data) # Validate against the jsonschema try: validate(info_data, 'qmk.api.keyboard.v1') except jsonschema.ValidationError as e: json_path = '.'.join([str(p) for p in e.absolute_path]) cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) exit(1) # Make sure we have at least one layout if not info_data.get('layouts'): _find_missing_layouts(info_data, keyboard) if not info_data.get('layouts'): _log_error(info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.') # Filter out any non-existing community layouts for layout in info_data.get('community_layouts', []): if not _valid_community_layout(layout): # Ignore layout from future checks info_data['community_layouts'].remove(layout) _log_error(info_data, 'Claims to support a community layout that does not exist: %s' % (layout)) # Make sure we supply layout macros for the community layouts we claim to support for layout in info_data.get('community_layouts', []): layout_name = 'LAYOUT_' + layout if layout_name not in info_data.get('layouts', {}) and layout_name not in info_data.get('layout_aliases', {}): _log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name)) # Check that the reported matrix size is consistent with the actual matrix size _check_matrix(info_data) # Remove newline characters from layout labels _remove_newlines_from_labels(layouts) return info_data