From fe603948a9f46690185daaf2f7f1890412ec7704 Mon Sep 17 00:00:00 2001 From: Ben Blazak Date: Thu, 23 Aug 2012 01:31:25 -0700 Subject: [PATCH] wrote the ui-info generating script for Ben (aka OrangeJewce) - also changed the kbfun function comments to be easier to parse - also added running the generating script to the toplevel build process --- build-scripts/gen-ui-info.py | 355 +++++++++++++++++++++++++ makefile | 19 +- src/TODO | 5 +- src/lib/key-functions/public/basic.c | 28 +- src/lib/key-functions/public/device.c | 14 + src/lib/key-functions/public/numpad.c | 21 +- src/lib/key-functions/public/special.c | 33 ++- 7 files changed, 440 insertions(+), 35 deletions(-) create mode 100755 build-scripts/gen-ui-info.py diff --git a/build-scripts/gen-ui-info.py b/build-scripts/gen-ui-info.py new file mode 100755 index 0000000..c60cca0 --- /dev/null +++ b/build-scripts/gen-ui-info.py @@ -0,0 +1,355 @@ +#! /usr/bin/env python3 +# ----------------------------------------------------------------------------- + +""" +Generate UI info file (in JSON) (format version: 0) + +The file will contain: +{ + ".meta-data": { + "version": , + "date-generated": , + }, + "keyboard-functions": { + <(function name)>: { + "position": , + "length": , + "comments": { + "name": , + "description": , + "notes": [ + , + ... + ], + ... + } + }, + ... + }, + "layout-matrices": { + <(matrix name)>: { + "position": , + "length": + }, + ... + }, + "miscellaneous": { + "git-commit-date": , + "git-commit-id": , + "number-of-layers": + } +} + +Depends on: +- the project source code +- the project '.map' file (generated by the compiler) +----------------------------------------------------------------------------- +Copyright (c) 2012 Ben Blazak +Released under The MIT License (MIT) (see "license.md") +Project located at +----------------------------------------------------------------------------- +""" + +# ----------------------------------------------------------------------------- + +import argparse +import json +import os +import re +import subprocess +import sys + +# ----------------------------------------------------------------------------- + +def gen_static(git_commit_date=None, git_commit_id=None): + """Generate static information""" + + date = None + if os.name == 'posix': + date = subprocess.getoutput('date --rfc-3339 s') + + return { + '.meta-data': { + 'version': 0, # the format version number + 'date-generated': date, + }, + '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): + """ + 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) + }, + } + +# ----------------------------------------------------------------------------- + +def parse_mapfile(map_file_path): + """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 (all 3) layout matrices""" + + search = re.search(r'0x\S+\s+(0x\S+)', line) + # (length for (size of, in bytes) a layer of 1 byte objects) + base_length = int( int( search.group(1), 16 ) / 5 ) + + next_lines = ''.join([next(f), next(f), next(f)]) + layout_position = re.search( + r'(0x\S+)\s+_kb_layout'+'\n', next_lines ) . group(1) + layout_press_position = re.search( + r'(0x\S+)\s+_kb_layout_press'+'\n', next_lines ) . group(1) + layout_release_position = re.search( + r'(0x\S+)\s+_kb_layout_release'+'\n', next_lines ) . group(1) + layout_position = int(layout_position, 16) + layout_press_position = int(layout_press_position, 16) + layout_release_position = int(layout_release_position, 16) + + if not ( layout_position + and layout_press_position + and layout_release_position ): + raise Exception( + "parse_mapfile: not all layout matrices were found" ) + + return { + 'layout-matrices': { + '_kb_layout': { + 'position': layout_position, + 'length': base_length, + }, + '_kb_layout_press': { + 'position': layout_press_position, + 'length': base_length * 2, + }, + '_kb_layout_release': { + 'position': layout_release_position, + 'length': base_length * 2, + }, + }, + } + + # --- 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 + + +def parse_source_code(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), + }, + }, + } + + # --- parse_source_code() --- + + # normalize paths + source_dir_path = os.path.abspath(source_code_path) + # check paths + if not os.path.exists(source_code_path): + raise ValueError("invalid 'source_dir_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 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. + """ + + 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 + + return a + +# ----------------------------------------------------------------------------- + +def main(): + arg_parser = argparse.ArgumentParser( + description = 'Generate project data for use with the UI' ) + + 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 ) + + args = arg_parser.parse_args(sys.argv[1:]) + + output = {} + dict_merge(output, gen_static(args.git_commit_date, args.git_commit_id)) + dict_merge(output, parse_mapfile(args.map_file_path)) + dict_merge(output, parse_source_code(args.source_code_path)) + dict_merge(output, gen_derived(output)) + + print(json.dumps(output, sort_keys=True, indent=4)) + +# ----------------------------------------------------------------------------- + +if __name__ == '__main__': + main() + diff --git a/makefile b/makefile index 2d03791..a4c013c 100644 --- a/makefile +++ b/makefile @@ -20,13 +20,13 @@ # the base name of the file or package to distribute NAME := ergodox-firmware -# the branch of the git repo we're currently on -BRANCH := $(shell git branch -l | grep '*' | cut -c 3-) -# a version identifier -VERSION := $(shell git log -n 1 | grep 'commit' | cut -c 8-14)--$(shell date +'%Y%m%dT%H%M%S') +# git info +GIT_BRANCH := $(shell git branch -l | grep '*' | cut -c 3-) +GIT_COMMIT_DATE := $(shell git log -n 1 --pretty --date=iso | grep 'Date' | cut -c 9- ) +GIT_COMMIT_ID := $(shell git log -n 1 | grep 'commit' | cut -c 8-) # name to use for the final distribution file or package -TARGET := $(NAME)--$(BRANCH)--$(VERSION) +TARGET := $(NAME)--$(GIT_BRANCH)--$(shell date -d "$(GIT_COMMIT_DATE)" +'%Y%m%dT%H%M%S')--$(shell echo $(GIT_COMMIT_ID) | cut -c 1-7) # the build dir BUILD := build @@ -43,6 +43,8 @@ clean: -rm -r '$(BUILD)' dist: + # make sure we're checked in + git commit -a # set up the build dir -rm -r '$(BUILD)/$(TARGET)'* -mkdir -p '$(BUILD)/$(TARGET)' @@ -53,6 +55,13 @@ dist: ( cd src; \ cp firmware.hex firmware.eep firmware.map \ '../$(BUILD)/$(TARGET)' ) + # run secondary build scripts + ( ./build-scripts/gen-ui-info.py \ + --git-commit-date '$(GIT_COMMIT_DATE)' \ + --git-commit-id '$(GIT_COMMIT_ID)' \ + --map-file-path '$(BUILD)/$(TARGET)/firmware.map' \ + --source-code-path 'src' \ + ) > '$(BUILD)/$(TARGET)/firmware--ui-info.json' # make into a zip archive ( cd '$(BUILD)/$(TARGET)'; \ zip '../$(TARGET).zip' \ diff --git a/src/TODO b/src/TODO index cdff2a6..3f7468b 100644 --- a/src/TODO +++ b/src/TODO @@ -1,7 +1,4 @@ -- write the python script for Ben, to extract all that useful stuff from the - .map file and comments - -- then i'll think about how to implement variable length arrays, and write some +- think about how to implement variable length arrays, and write some - then i'll think about how to use them as stacks, to deal with layer issues diff --git a/src/lib/key-functions/public/basic.c b/src/lib/key-functions/public/basic.c index fe1d43f..99cf31c 100644 --- a/src/lib/key-functions/public/basic.c +++ b/src/lib/key-functions/public/basic.c @@ -24,8 +24,11 @@ // ---------------------------------------------------------------------------- /* - * Press|Release - * - Generate a normal keypress or keyrelease + * [name] + * Press|Release + * + * [description] + * Generate a normal keypress or keyrelease */ void kbfun_press_release(void) { uint8_t keycode = kb_layout_get(layer, row, col); @@ -33,8 +36,11 @@ void kbfun_press_release(void) { } /* - * Toggle - * - Toggle the key pressed or unpressed + * [name] + * Toggle + * + * [description] + * Toggle the key pressed or unpressed */ void kbfun_toggle(void) { uint8_t keycode = kb_layout_get(layer, row, col); @@ -46,8 +52,11 @@ void kbfun_toggle(void) { } /* - * Increase layer - * - Increment the current layer by the value specified in the keymap (for all + * [name] + * Increase layer + * + * [description] + * Increment the current layer by the value specified in the keymap (for all * non-masked keys) */ void kbfun_layer_inc(void) { @@ -56,8 +65,11 @@ void kbfun_layer_inc(void) { } /* - * Decrease layer - * - Decrement the current layer by the value specified in the keymap (for all + * [name] + * Decrease layer + * + * [description] + * Decrement the current layer by the value specified in the keymap (for all * non-masked keys) */ void kbfun_layer_dec(void) { diff --git a/src/lib/key-functions/public/device.c b/src/lib/key-functions/public/device.c index bf5cd90..6386666 100644 --- a/src/lib/key-functions/public/device.c +++ b/src/lib/key-functions/public/device.c @@ -12,6 +12,20 @@ #include "../public.h" +// ---------------------------------------------------------------------------- +// descriptions +// ---------------------------------------------------------------------------- + +/* + * [name] + * Jump to Bootloader + * + * [description] + * For reflashing the controller + */ +void kbfun_jump_to_bootloader(void); + + // ---------------------------------------------------------------------------- #if MAKEFILE_BOARD == teensy-2-0 // ---------------------------------------------------------------------------- diff --git a/src/lib/key-functions/public/numpad.c b/src/lib/key-functions/public/numpad.c index aaa1f6e..c497940 100644 --- a/src/lib/key-functions/public/numpad.c +++ b/src/lib/key-functions/public/numpad.c @@ -110,8 +110,11 @@ static void _toggle_numpad(uint8_t numpad_layer) { // ---------------------------------------------------------------------------- /* - * Numpad toggle - * - Toggles the numpad and sets numlock on (for active) or off (for inactive) + * [name] + * Numpad toggle + * + * [description] + * Toggles the numpad and sets numlock on (for active) or off (for inactive) * with it, if it's not already in that state */ void kbfun_layermask_numpad_toggle(void) { @@ -120,8 +123,11 @@ void kbfun_layermask_numpad_toggle(void) { } /* - * Numpad on - * - Set the numpad on (along with numlock, if it's not already) + * [name] + * Numpad on + * + * [description] + * Set the numpad on (along with numlock, if it's not already) */ void kbfun_layermask_numpad_on(void) { uint8_t keycode = kb_layout_get(layer, row, col); @@ -130,8 +136,11 @@ void kbfun_layermask_numpad_on(void) { } /* - * Numpad off - * - Set the numpad off (along with numlock, if it's not already) + * [name] + * Numpad off + * + * [description] + * Set the numpad off (along with numlock, if it's not already) */ void kbfun_layermask_numpad_off(void) { uint8_t keycode = kb_layout_get(layer, row, col); diff --git a/src/lib/key-functions/public/special.c b/src/lib/key-functions/public/special.c index 645408a..48b8496 100644 --- a/src/lib/key-functions/public/special.c +++ b/src/lib/key-functions/public/special.c @@ -28,13 +28,16 @@ // ---------------------------------------------------------------------------- /* - * Increase layer, Execute key - * - Increment the current layer by the value specified in the keymap (for all + * [name] + * Increase layer, Execute key + * + * [description] + * Increment the current layer by the value specified in the keymap (for all * non-masked keys), and execute (usually press|release) the key in the same * position on that new layer * - * Note - * - Meant to be paired with `kbfun_layer_dec_exec()` + * [note] + * Meant to be paired with `kbfun_layer_dec_exec()` */ void kbfun_layer_inc_exec(void) { uint8_t keycode = kb_layout_get(layer, row, col); @@ -51,13 +54,16 @@ void kbfun_layer_inc_exec(void) { /* - * Decrease layer, Execute key - * - Decrement the current layer by the value specified in the keymap (for all + * [name] + * Decrease layer, Execute key + * + * [description] + * Decrement the current layer by the value specified in the keymap (for all * non-masked keys), and execute (usually press|release) the key in the same * position on that new layer * - * Note - * - Meant to be paired with `kbfun_layer_inc_exec()` + * [note] + * Meant to be paired with `kbfun_layer_inc_exec()` */ void kbfun_layer_dec_exec(void) { uint8_t keycode = kb_layout_get(layer, row, col); @@ -74,13 +80,16 @@ void kbfun_layer_dec_exec(void) { /* - * Two keys => capslock - * - When assigned to two keys (e.g. the physical left and right shift keys) + * [name] + * Two keys => capslock + * + * [description] + * When assigned to two keys (e.g. the physical left and right shift keys) * (in both the press and release matrices), pressing and holding down one of * the keys will make the second key toggle capslock * - * Note - * - If either of the shifts are pressed when the second key is pressed, they + * [note] + * If either of the shifts are pressed when the second key is pressed, they * wil be released so that capslock will register properly when pressed. * Capslock will then be pressed and released, and the original state of the * shifts will be restored