info.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. """Keyboard information script.
  2. Compile an info.json for a particular keyboard and pretty-print it.
  3. """
  4. import sys
  5. import json
  6. from milc import cli
  7. from qmk.json_encoders import InfoJSONEncoder
  8. from qmk.constants import COL_LETTERS, ROW_LETTERS
  9. from qmk.decorators import automagic_keyboard, automagic_keymap
  10. from qmk.keyboard import keyboard_completer, keyboard_folder, render_layouts, render_layout, rules_mk
  11. from qmk.info import info_json, keymap_json
  12. from qmk.keymap import locate_keymap
  13. from qmk.path import is_keyboard
  14. UNICODE_SUPPORT = sys.stdout.encoding.lower().startswith('utf')
  15. def show_keymap(kb_info_json, title_caps=True):
  16. """Render the keymap in ascii art.
  17. """
  18. keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap)
  19. if keymap_path and keymap_path.suffix == '.json':
  20. keymap_data = json.load(keymap_path.open(encoding='utf-8'))
  21. layout_name = keymap_data['layout']
  22. layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name) # Resolve alias names
  23. for layer_num, layer in enumerate(keymap_data['layers']):
  24. if title_caps:
  25. cli.echo('{fg_cyan}Keymap %s Layer %s{fg_reset}:', cli.config.info.keymap, layer_num)
  26. else:
  27. cli.echo('{fg_cyan}keymap.%s.layer.%s{fg_reset}:', cli.config.info.keymap, layer_num)
  28. print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, layer))
  29. def show_layouts(kb_info_json, title_caps=True):
  30. """Render the layouts with info.json labels.
  31. """
  32. for layout_name, layout_art in render_layouts(kb_info_json, cli.config.info.ascii).items():
  33. title = f'Layout {layout_name.title()}' if title_caps else f'layouts.{layout_name}'
  34. cli.echo('{fg_cyan}%s{fg_reset}:', title)
  35. print(layout_art) # Avoid passing dirty data to cli.echo()
  36. def show_matrix(kb_info_json, title_caps=True):
  37. """Render the layout with matrix labels in ascii art.
  38. """
  39. for layout_name, layout in kb_info_json['layouts'].items():
  40. # Build our label list
  41. labels = []
  42. for key in layout['layout']:
  43. if 'matrix' in key:
  44. row = ROW_LETTERS[key['matrix'][0]]
  45. col = COL_LETTERS[key['matrix'][1]]
  46. labels.append(row + col)
  47. else:
  48. labels.append('')
  49. # Print the header
  50. if title_caps:
  51. cli.echo('{fg_blue}Matrix for "%s"{fg_reset}:', layout_name)
  52. else:
  53. cli.echo('{fg_blue}matrix_%s{fg_reset}:', layout_name)
  54. print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, labels))
  55. def print_friendly_output(kb_info_json):
  56. """Print the info.json in a friendly text format.
  57. """
  58. cli.echo('{fg_blue}Keyboard Name{fg_reset}: %s', kb_info_json.get('keyboard_name', 'Unknown'))
  59. cli.echo('{fg_blue}Manufacturer{fg_reset}: %s', kb_info_json.get('manufacturer', 'Unknown'))
  60. if 'url' in kb_info_json:
  61. cli.echo('{fg_blue}Website{fg_reset}: %s', kb_info_json.get('url', ''))
  62. if kb_info_json.get('maintainer', 'qmk') == 'qmk':
  63. cli.echo('{fg_blue}Maintainer{fg_reset}: QMK Community')
  64. else:
  65. cli.echo('{fg_blue}Maintainer{fg_reset}: %s', kb_info_json['maintainer'])
  66. cli.echo('{fg_blue}Keyboard Folder{fg_reset}: %s', kb_info_json.get('keyboard_folder', 'Unknown'))
  67. cli.echo('{fg_blue}Layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
  68. cli.echo('{fg_blue}Processor{fg_reset}: %s', kb_info_json.get('processor', 'Unknown'))
  69. cli.echo('{fg_blue}Bootloader{fg_reset}: %s', kb_info_json.get('bootloader', 'Unknown'))
  70. if 'layout_aliases' in kb_info_json:
  71. aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()]
  72. cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),))
  73. def print_text_output(kb_info_json):
  74. """Print the info.json in a plain text format.
  75. """
  76. for key in sorted(kb_info_json):
  77. if key == 'layouts':
  78. cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
  79. else:
  80. cli.echo('{fg_blue}%s{fg_reset}: %s', key, kb_info_json[key])
  81. if cli.config.info.layouts:
  82. show_layouts(kb_info_json, False)
  83. if cli.config.info.matrix:
  84. show_matrix(kb_info_json, False)
  85. if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
  86. show_keymap(kb_info_json, False)
  87. def print_dotted_output(kb_info_json, prefix=''):
  88. """Print the info.json in a plain text format with dot-joined keys.
  89. """
  90. for key in sorted(kb_info_json):
  91. new_prefix = f'{prefix}.{key}' if prefix else key
  92. if key in ['parse_errors', 'parse_warnings']:
  93. continue
  94. elif key == 'layouts' and prefix == '':
  95. cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
  96. elif isinstance(kb_info_json[key], dict):
  97. print_dotted_output(kb_info_json[key], new_prefix)
  98. elif isinstance(kb_info_json[key], list):
  99. cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key]))))
  100. else:
  101. cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
  102. def print_parsed_rules_mk(keyboard_name):
  103. rules = rules_mk(keyboard_name)
  104. for k in sorted(rules.keys()):
  105. print('%s = %s' % (k, rules[k]))
  106. return
  107. @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
  108. @cli.argument('-km', '--keymap', help='Keymap to show info for (Optional).')
  109. @cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.')
  110. @cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.')
  111. @cli.argument('-f', '--format', default='friendly', arg_only=True, help='Format to display the data in (friendly, text, json) (Default: friendly).')
  112. @cli.argument('--ascii', action='store_true', default=not UNICODE_SUPPORT, help='Render layout box drawings in ASCII only.')
  113. @cli.argument('-r', '--rules-mk', action='store_true', help='Render the parsed values of the keyboard\'s rules.mk file.')
  114. @cli.subcommand('Keyboard information.')
  115. @automagic_keyboard
  116. @automagic_keymap
  117. def info(cli):
  118. """Compile an info.json for a particular keyboard and pretty-print it.
  119. """
  120. # Determine our keyboard(s)
  121. if not cli.config.info.keyboard:
  122. cli.log.error('Missing parameter: --keyboard')
  123. cli.subcommands['info'].print_help()
  124. return False
  125. if not is_keyboard(cli.config.info.keyboard):
  126. cli.log.error('Invalid keyboard: "%s"', cli.config.info.keyboard)
  127. return False
  128. if bool(cli.args.rules_mk):
  129. print_parsed_rules_mk(cli.config.info.keyboard)
  130. return False
  131. # default keymap stored in config file should be ignored
  132. if cli.config_source.info.keymap == 'config_file':
  133. cli.config_source.info.keymap = None
  134. # Build the info.json file
  135. if cli.config.info.keymap:
  136. kb_info_json = keymap_json(cli.config.info.keyboard, cli.config.info.keymap)
  137. else:
  138. kb_info_json = info_json(cli.config.info.keyboard)
  139. # Output in the requested format
  140. if cli.args.format == 'json':
  141. print(json.dumps(kb_info_json, cls=InfoJSONEncoder))
  142. return True
  143. elif cli.args.format == 'text':
  144. print_dotted_output(kb_info_json)
  145. title_caps = False
  146. elif cli.args.format == 'friendly':
  147. print_friendly_output(kb_info_json)
  148. title_caps = True
  149. else:
  150. cli.log.error('Unknown format: %s', cli.args.format)
  151. return False
  152. # Output requested extras
  153. if cli.config.info.layouts:
  154. show_layouts(kb_info_json, title_caps)
  155. if cli.config.info.matrix:
  156. show_matrix(kb_info_json, title_caps)
  157. if cli.config.info.keymap:
  158. show_keymap(kb_info_json, title_caps)