Submitted upstream: <https://github.com/benblazak/ergodox-firmware/pull/99> <https://github.com/benblazak/ergodox-firmware/pull/98> diff --git a/build-scripts/gen-layout.py b/build-scripts/gen-layout.py index fd5e54c..251a463 100755 --- a/build-scripts/gen-layout.py +++ b/build-scripts/gen-layout.py @@ -22,8 +22,10 @@ import sys # ----------------------------------------------------------------------------- -class Namespace(): - pass + +class Namespace: + pass + template = Namespace() doc = Namespace() @@ -31,45 +33,45 @@ info = Namespace() # ----------------------------------------------------------------------------- + def main(): - arg_parser = argparse.ArgumentParser( - description = "Generate a picture of the firmware's " - + "keyboard layout" ) + arg_parser = argparse.ArgumentParser( + description="Generate a picture of the firmware's " + "keyboard layout" + ) - arg_parser.add_argument( - '--ui-info-file', - required = True ) + arg_parser.add_argument("--ui-info-file", required=True) - args = arg_parser.parse_args(sys.argv[1:]) + args = arg_parser.parse_args(sys.argv[1:]) - # constant file paths - args.template_svg_file = './build-scripts/gen_layout/template.svg' - args.template_js_file = './build-scripts/gen_layout/template.js' + # constant file paths + args.template_svg_file = "./build-scripts/gen_layout/template.svg" + args.template_js_file = "./build-scripts/gen_layout/template.js" - # normalize paths - args.ui_info_file = os.path.abspath(args.ui_info_file) - args.template_svg_file = os.path.abspath(args.template_svg_file) - args.template_js_file = os.path.abspath(args.template_js_file) + # normalize paths + args.ui_info_file = os.path.abspath(args.ui_info_file) + args.template_svg_file = os.path.abspath(args.template_svg_file) + args.template_js_file = os.path.abspath(args.template_js_file) - # set vars - doc.main = '' # to store the html document we're generating - template.svg = open(args.template_svg_file).read() - template.js = open(args.template_js_file).read() - info.all = json.loads(open(args.ui_info_file).read()) + # set vars + doc.main = "" # to store the html document we're generating + template.svg = open(args.template_svg_file).read() + template.js = open(args.template_js_file).read() + info.all = json.loads(open(args.ui_info_file).read()) - info.matrix_positions = info.all['mappings']['matrix-positions'] - info.matrix_layout = info.all['mappings']['matrix-layout'] + info.matrix_positions = info.all["mappings"]["matrix-positions"] + info.matrix_layout = info.all["mappings"]["matrix-layout"] - # prefix - doc.prefix = (""" + # prefix + doc.prefix = ( + """ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <html> <head> <script> """ -+ template.js + -""" </script> + + template.js + + """ </script> </head> <body> @@ -78,9 +80,13 @@ def main(): <ul> <li>git commit date: - <code>""" + info.all['miscellaneous']['git-commit-date'] + """</code></li> + <code>""" + + info.all["miscellaneous"]["git-commit-date"] + + """</code></li> <li>git commit id: - <code>""" + info.all['miscellaneous']['git-commit-id'] + """</code></li> + <code>""" + + info.all["miscellaneous"]["git-commit-id"] + + """</code></li> </ul> <h2>Notes</h2> @@ -123,301 +129,293 @@ def main(): <br> -""")[1:-1] +""" + )[1:-1] - # suffix - doc.suffix = (""" + # suffix + doc.suffix = ( + """ </body> </html> -""")[1:-1] - - # substitute into template - # ------- - # note: this is not general enough to handle any possible layout well, at - # the moment. but it should handle more standard ones well. (hopefully - # minor) modifications may be necessary on a case by case basis - # ------- - layer_number = -1 - for (layout, layer) in zip( info.matrix_layout, - range(len(info.matrix_layout))): - layer_number += 1 - svg = template.svg - for (name, (code, press, release)) \ - in zip(info.matrix_positions, layout): - replace = '' - if press == 'kbfun_transparent': - replace = '' - elif press == 'kbfun_shift_press_release': - replace = 'sh ' + keycode_to_string.get(code, '[n/a]') - elif press == 'kbfun_jump_to_bootloader': - replace = '[btldr]' - elif press == 'NULL' and release == 'NULL': - replace = '(null)' - elif re.search(r'numpad', press+release): - replace = '[num]' - elif re.search(r'layer', press+release): - replace = 'la ' + re.findall(r'\d+', press+release)[0] + ' ' - if re.search(r'push', press+release): - replace += '+' - if re.search(r'pop', press+release): - replace += '-' - replace += ' ' + str(code) - else: - replace = keycode_to_string.get(code, '[n/a]') - - svg = re.sub( - '>'+name+'<', '>'+replace+'<', svg ) - svg = re.sub( - r"\('(" + name + r".*)'\)", - r"('\1', " + str(layer) + r")", - svg ) - - doc.main += '<h2>Layer ' + str(layer_number) + '</h2>\n' + svg - - # change the font size - doc.main = re.sub(r'22.5px', '15px', doc.main) - - print(doc.prefix + doc.main + doc.suffix) +""" + )[1:-1] + + # substitute into template + # ------- + # note: this is not general enough to handle any possible layout well, at + # the moment. but it should handle more standard ones well. (hopefully + # minor) modifications may be necessary on a case by case basis + # ------- + layer_number = -1 + for (layout, layer) in zip( + info.matrix_layout, range(len(info.matrix_layout)) + ): + layer_number += 1 + svg = template.svg + for (name, (code, press, release)) in zip( + info.matrix_positions, layout + ): + replace = "" + if press == "kbfun_transparent": + replace = "" + elif press == "kbfun_shift_press_release": + replace = "sh " + keycode_to_string.get(code, "[n/a]") + elif press == "kbfun_jump_to_bootloader": + replace = "[btldr]" + elif press == "NULL" and release == "NULL": + replace = "(null)" + elif re.search(r"numpad", press + release): + replace = "[num]" + elif re.search(r"layer", press + release): + replace = "la " + re.findall(r"\d+", press + release)[0] + " " + if re.search(r"push", press + release): + replace += "+" + if re.search(r"pop", press + release): + replace += "-" + replace += " " + str(code) + else: + replace = keycode_to_string.get(code, "[n/a]") + + svg = re.sub(">" + name + "<", ">" + replace + "<", svg) + svg = re.sub( + r"\('(" + name + r".*)'\)", r"('\1', " + str(layer) + r")", svg + ) + + doc.main += "<h2>Layer " + str(layer_number) + "</h2>\n" + svg + + # change the font size + doc.main = re.sub(r"22.5px", "15px", doc.main) + + print(doc.prefix + doc.main + doc.suffix) + # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- keycode_to_string = { - 0x01: "Error", # ErrorRollOver - 0x02: "POSTFail", - 0x03: "Error", # ErrorUndefined - 0x04: "a A", - 0x05: "b B", - 0x06: "c C", - 0x07: "d D", - 0x08: "e E", - 0x09: "f F", - 0x0A: "g G", - 0x0B: "h H", - 0x0C: "i I", - 0x0D: "j J", - 0x0E: "k K", - 0x0F: "l L", - 0x10: "m M", - 0x11: "n N", - 0x12: "o O", - 0x13: "p P", - 0x14: "q Q", - 0x15: "r R", - 0x16: "s S", - 0x17: "t T", - 0x18: "u U", - 0x19: "v V", - 0x1A: "w W", - 0x1B: "x X", - 0x1C: "y Y", - 0x1D: "z Z", - 0x1E: "1 !", - 0x1F: "2 @", - 0x20: "3 #", - 0x21: "4 $", - 0x22: "5 %", - 0x23: "6 ^", - 0x24: "7 &", - 0x25: "8 *", - 0x26: "9 (", - 0x27: "0 )", - 0x28: "Return", - 0x29: "Esc", - 0x2A: "Backspace", - 0x2B: "Tab", - 0x2C: "Space", - 0x2D: "- _", - 0x2E: "= +", - 0x2F: "[ {", - 0x30: "] }", - 0x31: "\ |", - 0x32: "# ~", - 0x33: "; :", - 0x34: "\' \"", - 0x35: "` ~", - 0x36: ", <", - 0x37: ". >", - 0x38: "/ ?", - 0x39: "Caps", - 0x3A: "F1", - 0x3B: "F2", - 0x3C: "F3", - 0x3D: "F4", - 0x3E: "F5", - 0x3F: "F6", - 0x40: "F7", - 0x41: "F8", - 0x42: "F9", - 0x43: "F10", - 0x44: "F11", - 0x45: "F12", - 0x46: "PrintScreen", - 0x47: "ScrollLock", - 0x48: "Pause", - 0x49: "Ins", # Insert - 0x4A: "Hm", # Home - 0x4B: "Pg\u2191", # up arrow - 0x4C: "Delete", - 0x4D: "End", - 0x4E: "Pg\u2193", # down arrow - 0x4F: "\u2192", # right arrow - 0x50: "\u2190", # left arrow - 0x51: "\u2193", # down arrow - 0x52: "\u2191", # up arrow - - 0x53: "Num", - 0x54: "/", - 0x55: "*", - 0x56: "-", - 0x57: "+", - 0x58: "Enter", - 0x59: "1 End", - 0x5A: "2 \u2193", # down arrow - 0x5B: "3 Pg\u2193", # down arrow - 0x5C: "4 \u2190", # left arrow - 0x5D: "5", - 0x5E: "6 \u2192", # right arrow - 0x5F: "7 Hm", # Home - 0x60: "8 \u2191", # up arrow - 0x61: "9 Pg\u2191", # up arrow - 0x62: "0 Ins", # Insert - 0x63: ". Del", - - 0x64: "\ |", - 0x65: "App", - 0x66: "Power", - - 0x67: "=", - - 0x68: "F13", - 0x69: "F14", - 0x6A: "F15", - 0x6B: "F16", - 0x6C: "F17", - 0x6D: "F18", - 0x6E: "F19", - 0x6F: "F20", - 0x70: "F21", - 0x71: "F22", - 0x72: "F23", - 0x73: "F24", - 0x74: "Exec", - 0x75: "Help", - 0x76: "Menu", - 0x77: "Select", - 0x78: "Stop", - 0x79: "Again", - 0x7A: "Undo", - 0x7B: "Cut", - 0x7C: "Copy", - 0x7D: "Paste", - 0x7E: "Find", - 0x7F: "Mute", - 0x80: "VolUp", - 0x81: "VolDown", - 0x82: "LockingCapsLock", - 0x83: "LockingNumLock", - 0x84: "LockingScrollLock", - - 0x85: ",", - 0x86: "=", - - 0x87: "Int1", - 0x88: "Int2", - 0x89: "Int3", - 0x8A: "Int4", - 0x8B: "Int5", - 0x8C: "Int6", - 0x8D: "Int7", - 0x8E: "Int8", - 0x8F: "Int9", - 0x90: "LANG1", - 0x91: "LANG2", - 0x92: "LANG3", - 0x93: "LANG4", - 0x94: "LANG5", - 0x95: "LANG6", - 0x96: "LANG7", - 0x97: "LANG8", - 0x98: "LANG9", - 0x99: "AlternateErase", - 0x9A: "SysReq_Attention", - 0x9B: "Cancel", - 0x9C: "Clear", - 0x9D: "Prior", - 0x9E: "Return", - 0x9F: "Separator", - 0xA0: "Out", - 0xA1: "Oper", - 0xA2: "Clear_Again", - 0xA3: "CrSel_Props", - 0xA4: "ExSel", - - 0xB0: "00", - 0xB1: "000", - - 0xB2: "Thousands_Sep", - 0xB3: "Decimal_Sep", - 0xB4: "$", - 0xB5: "Currency_Subunit", - - 0xB6: "(", - 0xB7: ")", - 0xB8: "{", - 0xB9: "}", - - 0xBA: "Tab", - 0xBB: "Backspace", - 0xBC: "A", - 0xBD: "B", - 0xBE: "C", - 0xBF: "D", - 0xC0: "E", - 0xC1: "F", - 0xC2: "XOR", - 0xC3: "^", - 0xC4: "%", - 0xC5: "<", - 0xC6: ">", - 0xC7: "&", - 0xC8: "&&", - 0xC9: "|", - 0xCA: "||", - 0xCB: ":", - 0xCC: "#", - 0xCD: "Space", - 0xCE: "@", - 0xCF: "!", - 0xD0: "Mem_Store", - 0xD1: "Mem_Recall", - 0xD2: "Mem_Clear", - 0xD3: "Mem_+", - 0xD4: "Mem_-", - 0xD5: "Mem_*", - 0xD6: "Mem_/", - 0xD7: "+-", - 0xD8: "Clear", - 0xD9: "ClearEntry", - 0xDA: "Binary", - 0xDB: "Octal", - 0xDC: ".", - 0xDD: "Hexadecimal", - - 0xE0: "L-Ctrl", - 0xE1: "L-Shift", - 0xE2: "L-Alt", - 0xE3: "L-GUI", - 0xE4: "R-Ctrl", - 0xE5: "R-Shift", - 0xE6: "R-Alt", - 0xE7: "R-GUI", - } + 0x01: "Error", # ErrorRollOver + 0x02: "POSTFail", + 0x03: "Error", # ErrorUndefined + 0x04: "a A", + 0x05: "b B", + 0x06: "c C", + 0x07: "d D", + 0x08: "e E", + 0x09: "f F", + 0x0A: "g G", + 0x0B: "h H", + 0x0C: "i I", + 0x0D: "j J", + 0x0E: "k K", + 0x0F: "l L", + 0x10: "m M", + 0x11: "n N", + 0x12: "o O", + 0x13: "p P", + 0x14: "q Q", + 0x15: "r R", + 0x16: "s S", + 0x17: "t T", + 0x18: "u U", + 0x19: "v V", + 0x1A: "w W", + 0x1B: "x X", + 0x1C: "y Y", + 0x1D: "z Z", + 0x1E: "1 !", + 0x1F: "2 @", + 0x20: "3 #", + 0x21: "4 $", + 0x22: "5 %", + 0x23: "6 ^", + 0x24: "7 &", + 0x25: "8 *", + 0x26: "9 (", + 0x27: "0 )", + 0x28: "Return", + 0x29: "Esc", + 0x2A: "Backspace", + 0x2B: "Tab", + 0x2C: "Space", + 0x2D: "- _", + 0x2E: "= +", + 0x2F: "[ {", + 0x30: "] }", + 0x31: "\ |", + 0x32: "# ~", + 0x33: "; :", + 0x34: "' \"", + 0x35: "` ~", + 0x36: ", <", + 0x37: ". >", + 0x38: "/ ?", + 0x39: "Caps", + 0x3A: "F1", + 0x3B: "F2", + 0x3C: "F3", + 0x3D: "F4", + 0x3E: "F5", + 0x3F: "F6", + 0x40: "F7", + 0x41: "F8", + 0x42: "F9", + 0x43: "F10", + 0x44: "F11", + 0x45: "F12", + 0x46: "PrintScreen", + 0x47: "ScrollLock", + 0x48: "Pause", + 0x49: "Ins", # Insert + 0x4A: "Hm", # Home + 0x4B: "Pg\u2191", # up arrow + 0x4C: "Delete", + 0x4D: "End", + 0x4E: "Pg\u2193", # down arrow + 0x4F: "\u2192", # right arrow + 0x50: "\u2190", # left arrow + 0x51: "\u2193", # down arrow + 0x52: "\u2191", # up arrow + 0x53: "Num", + 0x54: "/", + 0x55: "*", + 0x56: "-", + 0x57: "+", + 0x58: "Enter", + 0x59: "1 End", + 0x5A: "2 \u2193", # down arrow + 0x5B: "3 Pg\u2193", # down arrow + 0x5C: "4 \u2190", # left arrow + 0x5D: "5", + 0x5E: "6 \u2192", # right arrow + 0x5F: "7 Hm", # Home + 0x60: "8 \u2191", # up arrow + 0x61: "9 Pg\u2191", # up arrow + 0x62: "0 Ins", # Insert + 0x63: ". Del", + 0x64: "\ |", + 0x65: "App", + 0x66: "Power", + 0x67: "=", + 0x68: "F13", + 0x69: "F14", + 0x6A: "F15", + 0x6B: "F16", + 0x6C: "F17", + 0x6D: "F18", + 0x6E: "F19", + 0x6F: "F20", + 0x70: "F21", + 0x71: "F22", + 0x72: "F23", + 0x73: "F24", + 0x74: "Exec", + 0x75: "Help", + 0x76: "Menu", + 0x77: "Select", + 0x78: "Stop", + 0x79: "Again", + 0x7A: "Undo", + 0x7B: "Cut", + 0x7C: "Copy", + 0x7D: "Paste", + 0x7E: "Find", + 0x7F: "Mute", + 0x80: "VolUp", + 0x81: "VolDown", + 0x82: "LockingCapsLock", + 0x83: "LockingNumLock", + 0x84: "LockingScrollLock", + 0x85: ",", + 0x86: "=", + 0x87: "Int1", + 0x88: "Int2", + 0x89: "Int3", + 0x8A: "Int4", + 0x8B: "Int5", + 0x8C: "Int6", + 0x8D: "Int7", + 0x8E: "Int8", + 0x8F: "Int9", + 0x90: "LANG1", + 0x91: "LANG2", + 0x92: "LANG3", + 0x93: "LANG4", + 0x94: "LANG5", + 0x95: "LANG6", + 0x96: "LANG7", + 0x97: "LANG8", + 0x98: "LANG9", + 0x99: "AlternateErase", + 0x9A: "SysReq_Attention", + 0x9B: "Cancel", + 0x9C: "Clear", + 0x9D: "Prior", + 0x9E: "Return", + 0x9F: "Separator", + 0xA0: "Out", + 0xA1: "Oper", + 0xA2: "Clear_Again", + 0xA3: "CrSel_Props", + 0xA4: "ExSel", + 0xB0: "00", + 0xB1: "000", + 0xB2: "Thousands_Sep", + 0xB3: "Decimal_Sep", + 0xB4: "$", + 0xB5: "Currency_Subunit", + 0xB6: "(", + 0xB7: ")", + 0xB8: "{", + 0xB9: "}", + 0xBA: "Tab", + 0xBB: "Backspace", + 0xBC: "A", + 0xBD: "B", + 0xBE: "C", + 0xBF: "D", + 0xC0: "E", + 0xC1: "F", + 0xC2: "XOR", + 0xC3: "^", + 0xC4: "%", + 0xC5: "<", + 0xC6: ">", + 0xC7: "&", + 0xC8: "&&", + 0xC9: "|", + 0xCA: "||", + 0xCB: ":", + 0xCC: "#", + 0xCD: "Space", + 0xCE: "@", + 0xCF: "!", + 0xD0: "Mem_Store", + 0xD1: "Mem_Recall", + 0xD2: "Mem_Clear", + 0xD3: "Mem_+", + 0xD4: "Mem_-", + 0xD5: "Mem_*", + 0xD6: "Mem_/", + 0xD7: "+-", + 0xD8: "Clear", + 0xD9: "ClearEntry", + 0xDA: "Binary", + 0xDB: "Octal", + 0xDC: ".", + 0xDD: "Hexadecimal", + 0xE0: "L-Ctrl", + 0xE1: "L-Shift", + 0xE2: "L-Alt", + 0xE3: "L-GUI", + 0xE4: "R-Ctrl", + 0xE5: "R-Shift", + 0xE6: "R-Alt", + 0xE7: "R-GUI", +} # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- -if __name__ == '__main__': - main() - +if __name__ == "__main__": + main() diff --git a/build-scripts/gen-ui-info.py b/build-scripts/gen-ui-info.py index 1c93d32..0fa52e3 100755 --- a/build-scripts/gen-ui-info.py +++ b/build-scripts/gen-ui-info.py @@ -13,7 +13,16 @@ Depends on: - the project '.map' file (generated by the compiler) """ -_FORMAT_DESCRIPTION = (""" +import argparse +import json +import os +import pathlib +import re +import subprocess +import sys + +_FORMAT_DESCRIPTION = ( + """ /* ---------------------------------------------------------------------------- * Version 0 * ---------------------------------------------------------------------------- @@ -31,7 +40,7 @@ var ui_info = { ".meta-data": { // for the JSON file "version": "<number>", "date-generated": "<string>", // format: RFC 3339 - "description": "<string>", + "description": "<string>", }, "keyboard-functions": { "<(function name)>": { @@ -57,7 +66,7 @@ var ui_info = { "..." }, "mappings": { - /* + /* * The mappings prefixed with 'matrix' have their elements in the same * order as the .hex file (whatever order that is). The mappings * prefixed with 'physical' will have their elements in an order @@ -113,365 +122,304 @@ var ui_info = { "number-of-layers": "<number>" } } -""")[1:-1] +""" +)[1:-1] # ----------------------------------------------------------------------------- -import argparse -import json -import os -import re -import subprocess -import sys - -# ----------------------------------------------------------------------------- def gen_static(current_date=None, git_commit_date=None, git_commit_id=None): - """Generate static information""" - - return { - '.meta-data': { - 'version': 0, # the format version number - 'date-generated': current_date, - 'description': _FORMAT_DESCRIPTION, - }, - 'miscellaneous': { - 'git-commit-date': git_commit_date, # should be passed by makefile - 'git-commit-id': git_commit_id, # should be passed by makefile - }, - } - -def gen_derived(data): - return {} # don't really need this info anymore -# """ -# Generate derived information -# Should be called last -# """ -# return { -# 'miscellaneous': { -# 'number-of-layers': -# int( data['layout-matrices']['_kb_layout']['length']/(6*14) ), -# # because 6*14 is the number of bytes/layer for '_kb_layout' -# # (which is a uint8_t matrix) -# }, -# } - -# ----------------------------------------------------------------------------- + """Generate static information""" -def parse_mapfile(map_file_path): - return {} # don't really need this info anymore -# """Parse the '.map' file""" -# -# def parse_keyboard_function(f, line): -# """Parse keyboard-functions in the '.map' file""" -# -# search = re.search(r'(0x\S+)\s+(0x\S+)', next(f)) -# position = int( search.group(1), 16 ) -# length = int( search.group(2), 16 ) -# -# search = re.search(r'0x\S+\s+(\S+)', next(f)) -# name = search.group(1) -# -# return { -# 'keyboard-functions': { -# name: { -# 'position': position, -# 'length': length, -# }, -# }, -# } -# -# def parse_layout_matrices(f, line): -# """Parse layout matrix information in the '.map' file""" -# -# name = re.search(r'.progmem.data.(_kb_layout\S*)', line).group(1) -# -# search = re.search(r'(0x\S+)\s+(0x\S+)', next(f)) -# position = int( search.group(1), 16 ) -# length = int( search.group(2), 16 ) -# -# return { -# 'layout-matrices': { -# name: { -# 'position': position, -# 'length': length, -# }, -# }, -# } -# -# # --- parse_mapfile() --- -# -# # normalize paths -# map_file_path = os.path.abspath(map_file_path) -# # check paths -# if not os.path.exists(map_file_path): -# raise ValueError("invalid 'map_file_path' given") -# -# output = {} -# -# f = open(map_file_path) -# -# for line in f: -# if re.search(r'^\s*\.text\.kbfun_', line): -# dict_merge(output, parse_keyboard_function(f, line)) -# elif re.search(r'^\s*\.progmem\.data.*layout', line): -# dict_merge(output, parse_layout_matrices(f, line)) -# -# return output + return { + ".meta-data": { + "version": 0, # the format version number + "date-generated": current_date, + "description": _FORMAT_DESCRIPTION, + }, + "miscellaneous": { + "git-commit-date": git_commit_date, # should be passed by makefile + "git-commit-id": git_commit_id, # should be passed by makefile + }, + } def find_keyboard_functions(source_code_path): - """Parse all files in the source directory""" - - def read_comments(f, line): - """ - Read in properly formatted multi-line comments - - Comments must start with '/*' and end with '*/', each on their own - line - """ - comments = '' - while(line.strip() != r'*/'): - comments += line[2:].strip()+'\n' - line = next(f) - return comments - - def parse_comments(comments): - """ - Parse an INI style comment string - - Fields begin with '[field-name]', and continue until the next field, - or the end of the comment - - Fields '[name]', '[description]', and '[note]' are treated specially - """ - - def add_field(output, field, value): - """Put a field+value pair in 'output', the way we want it, if the - pair is valid""" - - value = value.strip() - - if field is not None: - if field in ('name', 'description'): - if field not in output: - output[field] = value - else: - if field == 'note': - field = 'notes' - - if field not in output: - output[field] = [] - - output[field] += [value] - - # --- parse_comments() --- - - output = {} - - field = None - value = None - for line in comments.split('\n'): - line = line.strip() - - if re.search(r'^\[.*\]$', line): - add_field(output, field, value) - field = line[1:-1] - value = None - - else: - if value is None: - value = '' - if len(value) > 0 and value[-1] == '.': - line = ' '+line - value += ' '+line - - add_field(output, field, value) - - return output - - def parse_keyboard_function(f, line, comments): - """Parse keyboard-functions in the source code""" - - search = re.search(r'void\s+(kbfun_\S+)\s*\(void\)', line) - name = search.group(1) - - return { - 'keyboard-functions': { - name: { - 'comments': parse_comments(comments), - }, - }, - } - - # --- find_keyboard_functions() --- - - # normalize paths - source_code_path = os.path.abspath(source_code_path) - # check paths - if not os.path.exists(source_code_path): - raise ValueError("invalid 'source_code_path' given") - - output = {} - - for tup in os.walk(source_code_path): - for file_name in tup[2]: - # normalize paths - file_name = os.path.abspath( os.path.join( tup[0], file_name ) ) - - # ignore non '.c' files - if file_name[-2:] != '.c': - continue - - f = open(file_name) - - comments = '' - for line in f: - if line.strip() == r'/*': - comments = read_comments(f, line) - elif re.search(r'void\s+kbfun_\S+\s*\(void\)', line): - dict_merge( - output, - parse_keyboard_function(f, line, comments) ) - - return output + """Parse all files in the source directory""" + + def read_comments(f, line): + """ + Read in properly formatted multi-line comments + - Comments must start with '/*' and end with '*/', each on their own + line + """ + comments = "" + while line.strip() != r"*/": + comments += line[2:].strip() + "\n" + line = next(f) + return comments + + def parse_comments(comments): + """ + Parse an INI style comment string + - Fields begin with '[field-name]', and continue until the next field, + or the end of the comment + - Fields '[name]', '[description]', and '[note]' are treated specially + """ + + def add_field(output, field, value): + """Put a field+value pair in 'output', the way we want it, if the + pair is valid""" + + value = value.strip() + + if field is not None: + if field in ("name", "description"): + if field not in output: + output[field] = value + else: + if field == "note": + field = "notes" + + if field not in output: + output[field] = [] + + output[field] += [value] + + # --- parse_comments() --- + + output = {} + + field = None + value = None + for line in comments.split("\n"): + line = line.strip() + + if re.search(r"^\[.*\]$", line): + add_field(output, field, value) + field = line[1:-1] + value = None + else: + if value is None: + value = "" + if len(value) > 0 and value[-1] == ".": + line = " " + line + value += " " + line + + add_field(output, field, value) + + return output + + def parse_keyboard_function(f, line, comments): + """Parse keyboard-functions in the source code""" + + search = re.search(r"void\s+(kbfun_\S+)\s*\(void\)", line) + name = search.group(1) + + return { + "keyboard-functions": { + name: { + "comments": parse_comments(comments), + }, + }, + } + + # --- find_keyboard_functions() --- + + # normalize paths + source_code_path = os.path.abspath(source_code_path) + # check paths + if not os.path.exists(source_code_path): + raise ValueError("invalid 'source_code_path' given") + + output = {} + + for tup in os.walk(source_code_path): + for file_name in tup[2]: + # normalize paths + file_name = os.path.abspath(os.path.join(tup[0], file_name)) + + # ignore non '.c' files + if file_name[-2:] != ".c": + continue + + f = open(file_name) + + comments = "" + for line in f: + if line.strip() == r"/*": + comments = read_comments(f, line) + elif re.search(r"void\s+kbfun_\S+\s*\(void\)", line): + dict_merge( + output, parse_keyboard_function(f, line, comments) + ) + + return output def gen_mappings(matrix_file_path, layout_file_path): - # normalize paths - matrix_file_path = os.path.abspath(matrix_file_path) - layout_file_path = os.path.abspath(layout_file_path) - - def parse_matrix_file(matrix_file_path): - match = re.search( # find the whole 'KB_MATRIX_LAYER' macro - r'#define\s+KB_MATRIX_LAYER\s*\(([^)]+)\)[^{]*\{\{([^#]+)\}\}', - open(matrix_file_path).read() ) - - return { - "mappings": { - "physical-positions": re.findall(r'k..', match.group(1)), - "matrix-positions": re.findall(r'k..|na', match.group(2)), - }, - } - - def parse_layout_file(layout_file_path): - match = re.findall( # find each whole '_kb_layout*' matrix definition - r'(_kb_layout\w*)[^=]*=((?:[^{}]*\{){3}[^=]*(?:[^{}]*\}){3})', - subprocess.getoutput("gcc -E '"+layout_file_path+"'") ) - - layout = {} - # collect all the values - for (name, matrix) in match: - layout[name] = [ - re.findall( # find all numbers and function pointers - r'[x0-9A-F]+|&\w+|NULL', - re.sub( # replace '((void *) 0)' with 'NULL' - r'\(\s*\(\s*void\s*\*\s*\)\s*0\s*\)', - 'NULL', - el ) ) - for el in - re.findall( # find each whole layer - r'(?:[^{}]*\{){2}((?:[^}]|\}\s*,)+)(?:[^{}]*\}){2}', - matrix ) ] - - # make the numbers into actual numbers - layout['_kb_layout'] = \ - [[eval(el) for el in layer] for layer in layout['_kb_layout']] - # remove the preceeding '&' from function pointers - for matrix in ('_kb_layout_press', '_kb_layout_release'): - layout[matrix] = \ - [ [re.sub(r'&', '', el) for el in layer] - for layer in layout[matrix] ] - - return { - "mappings": { - "matrix-layout": - # group them all properly - [ [[c, p, r] for (c, p, r) in zip(code, press, release)] - for (code, press, release) in - zip( layout['_kb_layout'], - layout['_kb_layout_press'], - layout['_kb_layout_release'] ) ] - }, - } - - return dict_merge( - parse_matrix_file(matrix_file_path), - parse_layout_file(layout_file_path) ) + # normalize paths + matrix_file_path = os.path.abspath(matrix_file_path) + layout_file_path = os.path.abspath(layout_file_path) + layout_name = pathlib.Path(layout_file_path).with_suffix('').name + + def parse_matrix_file(matrix_file_path): + match = re.search( # find the whole 'KB_MATRIX_LAYER' macro + r"#define\s+KB_MATRIX_LAYER\s*\(([^)]+)\)[^{]*\{\{([^#]+)\}\}", + open(matrix_file_path).read(), + ) + + return { + "mappings": { + "physical-positions": re.findall(r"k..", match.group(1)), + "matrix-positions": re.findall(r"k..|na", match.group(2)), + }, + } + + def parse_layout_file(layout_file_path): + output = subprocess.check_output( + ['avr-gcc', f'-DMAKEFILE_KEYBOARD_LAYOUT={layout_name}', + '-E', layout_file_path], encoding='UTF-8') + match = re.findall( # find each whole '_kb_layout*' matrix definition + r"(_kb_layout\w*)[^=]*=((?:[^{}]*\{){3}[^=]*(?:[^{}]*\}){3})", + output, + ) + + layout = {} + # collect all the values + for (name, matrix) in match: + layout[name] = [ + re.findall( # find all numbers and function pointers + r"[x0-9A-F]+|&\w+|NULL", + re.sub( # replace '((void *) 0)' with 'NULL' + r"\(\s*\(\s*void\s*\*\s*\)\s*0\s*\)", "NULL", el + ), + ) + for el in re.findall( # find each whole layer + r"(?:[^{}]*\{){2}((?:[^}]|\}\s*,)+)(?:[^{}]*\}){2}", matrix + ) + ] + + # make the numbers into actual numbers + layout["_kb_layout"] = [ + [eval(el) for el in layer] for layer in layout["_kb_layout"] + ] + # remove the preceeding '&' from function pointers + for matrix in ("_kb_layout_press", "_kb_layout_release"): + layout[matrix] = [ + [re.sub(r"&", "", el) for el in layer] + for layer in layout[matrix] + ] + + return { + "mappings": { + "matrix-layout": + # group them all properly + [ + [[c, p, r] for (c, p, r) in zip(code, press, release)] + for (code, press, release) in zip( + layout["_kb_layout"], + layout["_kb_layout_press"], + layout["_kb_layout_release"], + ) + ] + }, + } + + return dict_merge( + parse_matrix_file(matrix_file_path), + parse_layout_file(layout_file_path), + ) # ----------------------------------------------------------------------------- + def dict_merge(a, b): - """ - Recursively merge two dictionaries - - I was looking around for an easy way to do this, and found something - [here] - (http://www.xormedia.com/recursively-merge-dictionaries-in-python.html). - This is pretty close, but i didn't copy it exactly. - """ + """ + Recursively merge two dictionaries + - I was looking around for an easy way to do this, and found something + [here] + (http://www.xormedia.com/recursively-merge-dictionaries-in-python.html). + This is pretty close, but i didn't copy it exactly. + """ + + if not isinstance(a, dict) or not isinstance(b, dict): + return b - if not isinstance(a, dict) or not isinstance(b, dict): - return b + for (key, value) in b.items(): + if key in a: + a[key] = dict_merge(a[key], value) + else: + a[key] = value - for (key, value) in b.items(): - if key in a: - a[key] = dict_merge(a[key], value) - else: - a[key] = value + return a - return a # ----------------------------------------------------------------------------- + def main(): - arg_parser = argparse.ArgumentParser( - description = 'Generate project data for use with the UI' ) - - arg_parser.add_argument( - '--current-date', - help = ( "should be in the format rfc-3339 " - + "(e.g. 2006-08-07 12:34:56-06:00)" ), - required = True ) - arg_parser.add_argument( - '--git-commit-date', - help = ( "should be in the format rfc-3339 " - + "(e.g. 2006-08-07 12:34:56-06:00)" ), - required = True ) - arg_parser.add_argument( - '--git-commit-id', - help = "the git commit ID", - required = True ) - arg_parser.add_argument( - '--map-file-path', - help = "the path to the '.map' file", - required = True ) - arg_parser.add_argument( - '--source-code-path', - help = "the path to the source code directory", - required = True ) - arg_parser.add_argument( - '--matrix-file-path', - help = "the path to the matrix file we're using", - required = True ) - arg_parser.add_argument( - '--layout-file-path', - help = "the path to the layout file we're using", - required = True ) - - args = arg_parser.parse_args(sys.argv[1:]) - - output = {} - dict_merge( output, gen_static( args.current_date, - args.git_commit_date, - args.git_commit_id ) ) - dict_merge(output, parse_mapfile(args.map_file_path)) - dict_merge(output, find_keyboard_functions(args.source_code_path)) - dict_merge(output, gen_mappings( args.matrix_file_path, - args.layout_file_path )) - dict_merge(output, gen_derived(output)) - - print(json.dumps(output, sort_keys=True, indent=4)) + arg_parser = argparse.ArgumentParser( + description="Generate project data for use with the UI" + ) + + arg_parser.add_argument( + "--current-date", + help=( + "should be in the format rfc-3339 " + "(e.g. 2006-08-07 12:34:56-06:00)" + ), + required=True, + ) + arg_parser.add_argument( + "--git-commit-date", + help=( + "should be in the format rfc-3339 " + "(e.g. 2006-08-07 12:34:56-06:00)" + ), + required=True, + ) + arg_parser.add_argument( + "--git-commit-id", help="the git commit ID", required=True + ) + arg_parser.add_argument( + "--map-file-path", help="the path to the '.map' file", required=True + ) + arg_parser.add_argument( + "--source-code-path", + help="the path to the source code directory", + required=True, + ) + arg_parser.add_argument( + "--matrix-file-path", + help="the path to the matrix file we're using", + required=True, + ) + arg_parser.add_argument( + "--layout-file-path", + help="the path to the layout file we're using", + required=True, + ) + + args = arg_parser.parse_args(sys.argv[1:]) + + output = {} + dict_merge( + output, + gen_static( + args.current_date, args.git_commit_date, args.git_commit_id + ) + ) + dict_merge(output, find_keyboard_functions(args.source_code_path)) + dict_merge( + output, gen_mappings(args.matrix_file_path, args.layout_file_path) + ) + + print(json.dumps(output, sort_keys=True, indent=4)) -# ----------------------------------------------------------------------------- -if __name__ == '__main__': - main() +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + main() diff --git a/makefile b/makefile index d9fe10c..971ee0e 100644 --- a/makefile +++ b/makefile @@ -58,24 +58,27 @@ SCRIPTS := build-scripts all: dist clean: - git clean -dX # remove ignored files and directories - -rm -r '$(BUILD)' + git clean -fdX # remove ignored files and directories + rm -rf '$(BUILD)' checkin: -git commit -a build-dir: - -rm -r '$(BUILD)/$(TARGET)'* - -mkdir -p '$(BUILD)/$(TARGET)' + rm -rf '$(BUILD)/$(TARGET)'* + mkdir -p '$(BUILD)/$(TARGET)' firmware: cd src; $(MAKE) LAYOUT=$(LAYOUT) all -$(ROOT)/firmware.%: firmware +$(ROOT): + mkdir -p '$@' + +$(ROOT)/firmware.%: firmware $(ROOT) cp 'src/firmware.$*' '$@' -$(ROOT)/firmware--ui-info.json: $(SCRIPTS)/gen-ui-info.py checkin +$(ROOT)/firmware--ui-info.json: $(SCRIPTS)/gen-ui-info.py checkin firmware ( ./'$<' \ --current-date '$(shell $(DATE_PROG) --rfc-3339 s)' \ --git-commit-date '$(GIT_COMMIT_DATE)' \