Compare commits

...

8 commits

Author SHA1 Message Date
Zach White
e293bf243d refactor doctor.py into a directory 2021-06-21 22:48:20 -07:00
Zach White
286acfe7fd move all our file formatters to the format dir 2021-06-21 22:24:34 -07:00
Zach White
6d20b28354 make our schema validation more compact and flexible 2021-06-21 12:30:26 -07:00
Zach White
846da06380 cleanup 2021-06-21 09:58:11 -07:00
Zach White
243fc17e41 make flake8 happy 2021-06-20 12:27:51 -07:00
Zach White
f394f23ac6 fix formatting after vscode broke it 2021-06-20 11:57:37 -07:00
Zach White
0ca53fa307 optimize our jsonschema using refs 2021-06-20 11:52:57 -07:00
Zach White
8c361d6c41 fix some broken info.json files 2021-06-20 11:52:57 -07:00
22 changed files with 661 additions and 507 deletions

View file

@ -1,34 +1,22 @@
{ {
"$id": "qmk.api.keyboard.v1",
"allOf": [ "allOf": [
{"$ref": "qmk.keyboard.v1"}, {"$ref": "qmk.keyboard.v1"},
{ {
"$id": "qmk.api.keyboard.v1", "properties": {
"keymaps": { "keymaps": {
"type": "string" "type": "object",
}, "properties": {
"parse_errors": { "url": {"type": "string"}
"type": "array",
"items": {
"type": "string"
} }
}, },
"parse_warnings": { "parse_errors": {"$ref": "qmk.definitions.v1#/string_array"},
"type": "array", "parse_warnings": {"$ref": "qmk.definitions.v1#/string_array"},
"items": { "processor_type": {"type": "string"},
"type": "string" "protocol": {"type": "string"},
} "keyboard_folder": {"type": "string"},
}, "platform": {"type": "string"}
"processor_type": {
"type": "string"
},
"protocol": {
"type": "string"
},
"keyboard_folder": {
"type": "string"
},
"platform": {
"type": "string"
} }
} }
] ]

View file

@ -0,0 +1,107 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "qmk.definitions.v1",
"title": "Common definitions used across QMK's jsonschemas.",
"type": "object",
"boolean_array": {
"type": "object",
"additionalProperties": {"type": "boolean"}
},
"filename": {
"type": "string",
"minLength": 1,
"pattern": "^[0-9a-z_]*$"
},
"hex_number_2d": {
"type": "string",
"pattern": "^0x[0-9A-F]{2}$"
},
"hex_number_4d": {
"type": "string",
"pattern": "^0x[0-9A-F]{4}$"
},
"text_identifier": {
"type": "string",
"minLength": 1,
"maxLength": 250
},
"layout_macro": {
"oneOf": [
{
"type": "string",
"enum": ["LAYOUT", "LAYOUT_planck_1x2uC"]
},
{
"type": "string",
"pattern": "^LAYOUT_[0-9a-z_]*$"
}
]
},
"key_unit": {
"type": "number",
"min": 0.25
},
"mcu_pin_array": {
"type": "array",
"items": {"$ref": "#/mcu_pin"}
},
"mcu_pin": {
"oneOf": [
{
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
},
{
"type": "string",
"pattern": "^LINE_PIN\\d{1,2}$"
},
{
"type": "number",
"multipleOf": 1
},
{
"type": "null"
}
]
},
"signed_decimal": {
"type": "number"
},
"signed_int": {
"type": "number",
"multipleOf": 1
}
"signed_int_8": {
"type": "number",
"min": -127,
"max": 127,
"multipleOf": 1
}
"string_array": {
"type": "array",
"items": {
"type": "string"
}
},
"string_object": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"unsigned_decimal": {
"type": "number",
"min": 0
},
"unsigned_int": {
"type": "number",
"min": 0,
"multipleOf": 1
}
"unsigned_int_8": {
"type": "number",
"min": 0,
"max": 255,
"multipleOf": 1
}
}

View file

@ -1,24 +1,12 @@
{ {
"$schema": "http://json-schema.org/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"$id": "qmk.keyboard.v1", "$id": "qmk.keyboard.v1",
"title": "Keyboard Information", "title": "Keyboard Information",
"type": "object", "type": "object",
"properties": { "properties": {
"keyboard_name": { "keyboard_name": {"$ref": "qmk.definitions.v1#/text_identifier"},
"type": "string", "maintainer": {"$ref": "qmk.definitions.v1#/text_identifier"},
"minLength": 2, "manufacturer": {"$ref": "qmk.definitions.v1#/text_identifier"},
"maxLength": 250
},
"maintainer": {
"type": "string",
"minLength": 2,
"maxLength": 250
},
"manufacturer": {
"type": "string",
"minLength": 2,
"maxLength": 250
},
"url": { "url": {
"type": "string", "type": "string",
"format": "uri" "format": "uri"
@ -40,62 +28,25 @@
"type": "string", "type": "string",
"enum": ["COL2ROW", "ROW2COL"] "enum": ["COL2ROW", "ROW2COL"]
}, },
"debounce": { "debounce": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"type": "number", "height": {"$ref": "qmk.definitions.v1#/key_unit"},
"min": 0, "width": {"$ref": "qmk.definitions.v1#/key_unit"},
"multipleOf": 1
},
"height": {
"type": "number",
"min": 0.25
},
"width": {
"type": "number",
"min": 0.25
},
"community_layouts": { "community_layouts": {
"type": "array", "type": "array",
"items": { "items": {"$ref": "qmk.definitions.v1#/filename"}
"type": "string",
"minLength": 2,
"pattern": "^[0-9a-z_]*$"
}
},
"features": {
"type": "object",
"additionalProperties": {"type": "boolean"}
}, },
"features": {"$ref": "qmk.definitions.v1#/boolean_array"},
"indicators": { "indicators": {
"type": "object", "type": "object",
"properties": { "properties": {
"caps_lock": { "caps_lock": {"$ref": "qmk.definitions.v1#/mcu_pin"},
"type": "string", "num_lock": {"$ref": "qmk.definitions.v1#/mcu_pin"},
"pattern": "^[A-K]\\d{1,2}$" "scroll_lock": {"$ref": "qmk.definitions.v1#/mcu_pin"}
},
"num_lock": {
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
},
"scroll_lock": {
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
}
} }
}, },
"layout_aliases": { "layout_aliases": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {"$ref": "qmk.definitions.v1#/layout_macro"}
"oneOf": [
{
"type": "string",
"enum": ["LAYOUT", "LAYOUT_planck_1x2uC"]
},
{
"type": "string",
"pattern": "^LAYOUT_[0-9a-z_]*$"
}
]
}
}, },
"layouts": { "layouts": {
"type": "object", "type": "object",
@ -109,11 +60,7 @@
"c_macro": { "c_macro": {
"type": "boolean" "type": "boolean"
}, },
"key_count": { "key_count": {"$ref": "qmk.definitions.v1#/key_unit"},
"type": "number",
"min": 0,
"multipleOf": 1
},
"layout": { "layout": {
"type": "array", "type": "array",
"items": { "items": {
@ -131,34 +78,14 @@
"multipleOf": 1 "multipleOf": 1
} }
}, },
"h": { "key_count": {"$ref": "qmk.definitions.v1#/key_unit"},
"type": "number", "r": {"$ref": "qmk.definitions.v1#/unsigned_decimal"},
"min": 0.25 "rx": {"$ref": "qmk.definitions.v1#/unsigned_decimal"},
}, "ry": {"$ref": "qmk.definitions.v1#/unsigned_decimal"},
"r": { "h": {"$ref": "qmk.definitions.v1#/key_unit"},
"type": "number", "w": {"$ref": "qmk.definitions.v1#/key_unit"},
"min": 0 "x": {"$ref": "qmk.definitions.v1#/key_unit"},
}, "y": {"$ref": "qmk.definitions.v1#/key_unit"}
"rx": {
"type": "number",
"min": 0
},
"ry": {
"type": "number",
"min": 0
},
"w": {
"type": "number",
"min": 0.25
},
"x": {
"type": "number",
"min": 0
},
"y": {
"type": "number",
"min": 0
}
} }
} }
} }
@ -171,73 +98,10 @@
"properties": { "properties": {
"direct": { "direct": {
"type": "array", "type": "array",
"items": { "items": {$ref": "qmk.definitions.v1#/mcu_pin_array"}
"type": "array",
"items": {
"oneOf": [
{
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
}, },
{ "cols": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
"type": "string", "rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
"pattern": "^LINE_PIN\\d{1,2}$"
},
{
"type": "number",
"multipleOf": 1
},
{
"type": "null"
}
]
}
}
},
"cols": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
},
{
"type": "string",
"pattern": "^LINE_PIN\\d{1,2}$"
},
{
"type": "number",
"multipleOf": 1
},
{
"type": "null"
}
]
}
},
"rows": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
},
{
"type": "string",
"pattern": "^LINE_PIN\\d{1,2}$"
},
{
"type": "number",
"multipleOf": 1
},
{
"type": "null"
}
]
}
}
} }
}, },
"rgblight": { "rgblight": {
@ -250,47 +114,19 @@
"type": "boolean" "type": "boolean"
} }
}, },
"brightness_steps": { "brightness_steps": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"type": "number", "hue_steps": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"min": 0, "led_count": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"multipleOf": 1 "max_brightness": {"$ref": "qmk.definitions.v1#/unsigned_int_8"},
}, "pin": {"$ref": "qmk.definitions.v1#/mcu_pin"},
"hue_steps": { "saturation_steps": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"type": "number",
"min": 0,
"multipleOf": 1
},
"led_count": {
"type": "number",
"min": 0,
"multipleOf": 1
},
"max_brightness": {
"type": "number",
"min": 0,
"max": 255,
"multipleOf": 1
},
"pin": {
"type": "string",
"pattern": "^([A-K]\\d{1,2}|LINE_PIN\\d{1,2})$"
},
"saturation_steps": {
"type": "number",
"min": 0,
"multipleOf": 1
},
"sleep": {"type": "boolean"}, "sleep": {"type": "boolean"},
"split": {"type": "boolean"}, "split": {"type": "boolean"},
"split_count": { "split_count": {
"type": "array", "type": "array",
"minLength": 2, "minLength": 2,
"maxLength": 2, "maxLength": 2,
"items": { "items": {"$ref": "qmk.definitions.v1#/unsigned_int"}
"type": "number",
"min": 0,
"multipleOf": 1
}
} }
} }
}, },
@ -298,40 +134,19 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"device_ver": { "device_ver": {"$ref": "qmk.definitions.v1#/hex_number_4d"},
"type": "string", "pid": {"$ref": "qmk.definitions.v1#/hex_number_4d"},
"pattern": "^[0-9A-F]x[0-9A-F][0-9A-F][0-9A-F][0-9A-F]" "vid": {"$ref": "qmk.definitions.v1#/hex_number_4d"}
},
"pid": {
"type": "string",
"pattern": "^[0-9A-F]x[0-9A-F][0-9A-F][0-9A-F][0-9A-F]"
},
"vid": {
"type": "string",
"pattern": "^[0-9A-F]x[0-9A-F][0-9A-F][0-9A-F][0-9A-F]"
}
} }
}, },
"qmk_lufa_bootloader": { "qmk_lufa_bootloader": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"esc_output": { "esc_output": {"$ref": "qmk.definitions.v1#/mcu_pin"},
"type": "string", "esc_input": {"$ref": "qmk.definitions.v1#/mcu_pin"},
"pattern": "^[A-K]\\d{1,2}$" "led": {"$ref": "qmk.definitions.v1#/mcu_pin"},
}, "speaker": {"$ref": "qmk.definitions.v1#/mcu_pin"}
"esc_input": {
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
},
"led": {
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
},
"speaker": {
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
}
} }
} }
} }

View file

@ -1,15 +1,143 @@
{ {
"keyboard_name": "nop60", "keyboard_name": "nop60",
"url": "",
"maintainer": "nasp", "maintainer": "nasp",
"width": 15,
"height": 5, "height": 5,
"width": 15,
"url": "",
"layouts": { "layouts": {
"LAYOUT": { "LAYOUT": {
"2x3u": [{"label":"Esc", "x":0, "y":0}, {"label":"!", "x":1, "y":0}, {"label":"@", "x":2, "y":0}, {"label":"#", "x":3, "y":0}, {"label":"$", "x":4, "y":0}, {"label":"%", "x":5, "y":0}, {"label":"^", "x":6, "y":0}, {"label":"&", "x":7, "y":0}, {"label":"*", "x":8, "y":0}, {"label":"(", "x":9, "y":0}, {"label":")", "x":10, "y":0}, {"label":"_", "x":11, "y":0}, {"label":"+", "x":12, "y":0}, {"label":"~", "x":13, "y":0}, {"label":"Bksp", "x":14, "y":0}, {"label":"Tab", "x":0, "y":1, "w":1.5}, {"label":"Q", "x":1.5, "y":1}, {"label":"W", "x":2.5, "y":1}, {"label":"E", "x":3.5, "y":1}, {"label":"R", "x":4.5, "y":1}, {"label":"T", "x":5.5, "y":1}, {"label":"Y", "x":6.5, "y":1}, {"label":"U", "x":7.5, "y":1}, {"label":"I", "x":8.5, "y":1}, {"label":"O", "x":9.5, "y":1}, {"label":"P", "x":10.5, "y":1}, {"label":"{", "x":11.5, "y":1}, {"label":"}", "x":12.5, "y":1}, {"label":"|", "x":13.5, "y":1, "w":1.5}, {"label":"Caps Lock", "x":0, "y":2, "w":1.75}, {"label":"A", "x":1.75, "y":2}, {"label":"S", "x":2.75, "y":2}, {"label":"D", "x":3.75, "y":2}, {"label":"F", "x":4.75, "y":2}, {"label":"G", "x":5.75, "y":2}, {"label":"H", "x":6.75, "y":2}, {"label":"J", "x":7.75, "y":2}, {"label":"K", "x":8.75, "y":2}, {"label":"L", "x":9.75, "y":2}, {"label":":", "x":10.75, "y":2}, {"label":"\"", "x":11.75, "y":2}, {"label":"Enter", "x":12.75, "y":2, "w":2.25}, {"label":"Shift", "x":0, "y":3, "w":2.25}, {"label":"Z", "x":2.25, "y":3}, {"label":"X", "x":3.25, "y":3}, {"label":"C", "x":4.25, "y":3}, {"label":"V", "x":5.25, "y":3}, {"label":"B", "x":6.25, "y":3}, {"label":"N", "x":7.25, "y":3}, {"label":"M", "x":8.25, "y":3}, {"label":"<", "x":9.25, "y":3}, {"label":">", "x":10.25, "y":3}, {"label":"?", "x":11.25, "y":3}, {"label":"Shift", "x":12.25, "y":3, "w":1.75}, {"label":"Fn", "x":14, "y":3}, {"label":"Ctrl", "x":0, "y":4, "w":1.5}, {"label":"Win", "x":1.5, "y":4}, {"label":"Alt", "x":2.5, "y":4, "w":1.5}, {"x":4, "y":4, "w":3}, {"x":7, "y":4}, {"x":8, "y":4, "w":3}, {"label":"Alt", "x":11, "y":4, "w":1.5}, {"label":"Win", "x":12.5, "y":4}, {"label":"Ctrl", "x":13.5, "y":4, "w":1.5}] "layout": [
{ "label": "Esc", "x": 0, "y": 0 },
{ "label": "!", "x": 1, "y": 0 },
{ "label": "@", "x": 2, "y": 0 },
{ "label": "#", "x": 3, "y": 0 },
{ "label": "$", "x": 4, "y": 0 },
{ "label": "%", "x": 5, "y": 0 },
{ "label": "^", "x": 6, "y": 0 },
{ "label": "&", "x": 7, "y": 0 },
{ "label": "*", "x": 8, "y": 0 },
{ "label": "(", "x": 9, "y": 0 },
{ "label": ")", "x": 10, "y": 0 },
{ "label": "_", "x": 11, "y": 0 },
{ "label": "+", "x": 12, "y": 0 },
{ "label": "~", "x": 13, "y": 0 },
{ "label": "Bksp", "x": 14, "y": 0 },
{ "label": "Tab", "w": 1.5, "x": 0, "y": 1 },
{ "label": "Q", "x": 1.5, "y": 1 },
{ "label": "W", "x": 2.5, "y": 1 },
{ "label": "E", "x": 3.5, "y": 1 },
{ "label": "R", "x": 4.5, "y": 1 },
{ "label": "T", "x": 5.5, "y": 1 },
{ "label": "Y", "x": 6.5, "y": 1 },
{ "label": "U", "x": 7.5, "y": 1 },
{ "label": "I", "x": 8.5, "y": 1 },
{ "label": "O", "x": 9.5, "y": 1 },
{ "label": "P", "x": 10.5, "y": 1 },
{ "label": "{", "x": 11.5, "y": 1 },
{ "label": "}", "x": 12.5, "y": 1 },
{ "label": "|", "w": 1.5, "x": 13.5, "y": 1 },
{ "label": "Caps Lock", "w": 1.75, "x": 0, "y": 2 },
{ "label": "A", "x": 1.75, "y": 2 },
{ "label": "S", "x": 2.75, "y": 2 },
{ "label": "D", "x": 3.75, "y": 2 },
{ "label": "F", "x": 4.75, "y": 2 },
{ "label": "G", "x": 5.75, "y": 2 },
{ "label": "H", "x": 6.75, "y": 2 },
{ "label": "J", "x": 7.75, "y": 2 },
{ "label": "K", "x": 8.75, "y": 2 },
{ "label": "L", "x": 9.75, "y": 2 },
{ "label": ":", "x": 10.75, "y": 2 },
{ "label": "\"", "x": 11.75, "y": 2 },
{ "label": "Enter", "w": 2.25, "x": 12.75, "y": 2 },
{ "label": "Shift", "w": 2.25, "x": 0, "y": 3 },
{ "label": "Z", "x": 2.25, "y": 3 },
{ "label": "X", "x": 3.25, "y": 3 },
{ "label": "C", "x": 4.25, "y": 3 },
{ "label": "V", "x": 5.25, "y": 3 },
{ "label": "B", "x": 6.25, "y": 3 },
{ "label": "N", "x": 7.25, "y": 3 },
{ "label": "M", "x": 8.25, "y": 3 },
{ "label": "<", "x": 9.25, "y": 3 },
{ "label": ">", "x": 10.25, "y": 3 },
{ "label": "?", "x": 11.25, "y": 3 },
{ "label": "Shift", "w": 1.75, "x": 12.25, "y": 3 },
{ "label": "Fn", "x": 14, "y": 3 },
{ "label": "Ctrl", "w": 1.5, "x": 0, "y": 4 },
{ "label": "Win", "x": 1.5, "y": 4 },
{ "label": "Alt", "w": 1.5, "x": 2.5, "y": 4 },
{ "w": 7, "x": 4, "y": 4 },
{ "label": "Alt", "w": 1.5, "x": 11, "y": 4 },
{ "label": "Win", "x": 12.5, "y": 4 },
{ "label": "Ctrl", "w": 1.5, "x": 13.5, "y": 4 }
]
}, },
"LAYOUT": { "LAYOUT_2x3u": {
"7u": [{"label":"Esc", "x":0, "y":0}, {"label":"!", "x":1, "y":0}, {"label":"@", "x":2, "y":0}, {"label":"#", "x":3, "y":0}, {"label":"$", "x":4, "y":0}, {"label":"%", "x":5, "y":0}, {"label":"^", "x":6, "y":0}, {"label":"&", "x":7, "y":0}, {"label":"*", "x":8, "y":0}, {"label":"(", "x":9, "y":0}, {"label":")", "x":10, "y":0}, {"label":"_", "x":11, "y":0}, {"label":"+", "x":12, "y":0}, {"label":"~", "x":13, "y":0}, {"label":"Bksp", "x":14, "y":0}, {"label":"Tab", "x":0, "y":1, "w":1.5}, {"label":"Q", "x":1.5, "y":1}, {"label":"W", "x":2.5, "y":1}, {"label":"E", "x":3.5, "y":1}, {"label":"R", "x":4.5, "y":1}, {"label":"T", "x":5.5, "y":1}, {"label":"Y", "x":6.5, "y":1}, {"label":"U", "x":7.5, "y":1}, {"label":"I", "x":8.5, "y":1}, {"label":"O", "x":9.5, "y":1}, {"label":"P", "x":10.5, "y":1}, {"label":"{", "x":11.5, "y":1}, {"label":"}", "x":12.5, "y":1}, {"label":"|", "x":13.5, "y":1, "w":1.5}, {"label":"Caps Lock", "x":0, "y":2, "w":1.75}, {"label":"A", "x":1.75, "y":2}, {"label":"S", "x":2.75, "y":2}, {"label":"D", "x":3.75, "y":2}, {"label":"F", "x":4.75, "y":2}, {"label":"G", "x":5.75, "y":2}, {"label":"H", "x":6.75, "y":2}, {"label":"J", "x":7.75, "y":2}, {"label":"K", "x":8.75, "y":2}, {"label":"L", "x":9.75, "y":2}, {"label":":", "x":10.75, "y":2}, {"label":"\"", "x":11.75, "y":2}, {"label":"Enter", "x":12.75, "y":2, "w":2.25}, {"label":"Shift", "x":0, "y":3, "w":2.25}, {"label":"Z", "x":2.25, "y":3}, {"label":"X", "x":3.25, "y":3}, {"label":"C", "x":4.25, "y":3}, {"label":"V", "x":5.25, "y":3}, {"label":"B", "x":6.25, "y":3}, {"label":"N", "x":7.25, "y":3}, {"label":"M", "x":8.25, "y":3}, {"label":"<", "x":9.25, "y":3}, {"label":">", "x":10.25, "y":3}, {"label":"?", "x":11.25, "y":3}, {"label":"Shift", "x":12.25, "y":3, "w":1.75}, {"label":"Fn", "x":14, "y":3}, {"label":"Ctrl", "x":0, "y":4, "w":1.5}, {"label":"Win", "x":1.5, "y":4}, {"label":"Alt", "x":2.5, "y":4, "w":1.5}, {"x":4, "y":4, "w":7}, {"label":"Alt", "x":11, "y":4, "w":1.5}, {"label":"Win", "x":12.5, "y":4}, {"label":"Ctrl", "x":13.5, "y":4, "w":1.5}] "layout": [
{ "label": "Esc", "x": 0, "y": 0 },
{ "label": "!", "x": 1, "y": 0 },
{ "label": "@", "x": 2, "y": 0 },
{ "label": "#", "x": 3, "y": 0 },
{ "label": "$", "x": 4, "y": 0 },
{ "label": "%", "x": 5, "y": 0 },
{ "label": "^", "x": 6, "y": 0 },
{ "label": "&", "x": 7, "y": 0 },
{ "label": "*", "x": 8, "y": 0 },
{ "label": "(", "x": 9, "y": 0 },
{ "label": ")", "x": 10, "y": 0 },
{ "label": "_", "x": 11, "y": 0 },
{ "label": "+", "x": 12, "y": 0 },
{ "label": "~", "x": 13, "y": 0 },
{ "label": "Bksp", "x": 14, "y": 0 },
{ "label": "Tab", "w": 1.5, "x": 0, "y": 1 },
{ "label": "Q", "x": 1.5, "y": 1 },
{ "label": "W", "x": 2.5, "y": 1 },
{ "label": "E", "x": 3.5, "y": 1 },
{ "label": "R", "x": 4.5, "y": 1 },
{ "label": "T", "x": 5.5, "y": 1 },
{ "label": "Y", "x": 6.5, "y": 1 },
{ "label": "U", "x": 7.5, "y": 1 },
{ "label": "I", "x": 8.5, "y": 1 },
{ "label": "O", "x": 9.5, "y": 1 },
{ "label": "P", "x": 10.5, "y": 1 },
{ "label": "{", "x": 11.5, "y": 1 },
{ "label": "}", "x": 12.5, "y": 1 },
{ "label": "|", "w": 1.5, "x": 13.5, "y": 1 },
{ "label": "Caps Lock", "w": 1.75, "x": 0, "y": 2 },
{ "label": "A", "x": 1.75, "y": 2 },
{ "label": "S", "x": 2.75, "y": 2 },
{ "label": "D", "x": 3.75, "y": 2 },
{ "label": "F", "x": 4.75, "y": 2 },
{ "label": "G", "x": 5.75, "y": 2 },
{ "label": "H", "x": 6.75, "y": 2 },
{ "label": "J", "x": 7.75, "y": 2 },
{ "label": "K", "x": 8.75, "y": 2 },
{ "label": "L", "x": 9.75, "y": 2 },
{ "label": ":", "x": 10.75, "y": 2 },
{ "label": "\"", "x": 11.75, "y": 2 },
{ "label": "Enter", "w": 2.25, "x": 12.75, "y": 2 },
{ "label": "Shift", "w": 2.25, "x": 0, "y": 3 },
{ "label": "Z", "x": 2.25, "y": 3 },
{ "label": "X", "x": 3.25, "y": 3 },
{ "label": "C", "x": 4.25, "y": 3 },
{ "label": "V", "x": 5.25, "y": 3 },
{ "label": "B", "x": 6.25, "y": 3 },
{ "label": "N", "x": 7.25, "y": 3 },
{ "label": "M", "x": 8.25, "y": 3 },
{ "label": "<", "x": 9.25, "y": 3 },
{ "label": ">", "x": 10.25, "y": 3 },
{ "label": "?", "x": 11.25, "y": 3 },
{ "label": "Shift", "w": 1.75, "x": 12.25, "y": 3 },
{ "label": "Fn", "x": 14, "y": 3 },
{ "label": "Ctrl", "w": 1.5, "x": 0, "y": 4 },
{ "label": "Win", "x": 1.5, "y": 4 },
{ "label": "Alt", "w": 1.5, "x": 2.5, "y": 4 },
{ "w": 3, "x": 4, "y": 4 },
{ "x": 7, "y": 4 },
{ "w": 3, "x": 8, "y": 4 },
{ "label": "Alt", "w": 1.5, "x": 11, "y": 4 },
{ "label": "Win", "x": 12.5, "y": 4 },
{ "label": "Ctrl", "w": 1.5, "x": 13.5, "y": 4 }
]
} }
} }
} }

View file

@ -1,7 +1,5 @@
{ {
"keyboard_name": "Chevron", "keyboard_name": "Chevron",
"url": "",
"maintainer": "",
"width": 14.5, "width": 14.5,
"height": 5, "height": 5,
"layouts": { "layouts": {

View file

@ -7,7 +7,7 @@
"layouts": { "layouts": {
"LAYOUT_ortho_5x15": { "LAYOUT_ortho_5x15": {
"layout": [ "layout": [
{"label": "Esc", "X": 0, "y": 0}, {"label": "Esc", "x": 0, "y": 0},
{"label": "1", "x": 1, "y": 0}, {"label": "1", "x": 1, "y": 0},
{"label": "2", "x": 2, "y": 0}, {"label": "2", "x": 2, "y": 0},
{"label": "3", "x": 3, "y": 0}, {"label": "3", "x": 3, "y": 0},
@ -22,21 +22,21 @@
{"label": "NumLock", "x": 12, "y": 0}, {"label": "NumLock", "x": 12, "y": 0},
{"label": "/", "x": 13, "y": 0}, {"label": "/", "x": 13, "y": 0},
{"label": "*", "x": 14, "y": 0}, {"label": "*", "x": 14, "y": 0},
{"label": "Tab", "X": 0, "y": 1}, {"label": "Tab", "x": 0, "y": 1},
{"label": "Q", "X": 1, "y": 1}, {"label": "Q", "x": 1, "y": 1},
{"label": "W", "X": 2, "y": 1}, {"label": "W", "x": 2, "y": 1},
{"label": "E", "X": 3, "y": 1}, {"label": "E", "x": 3, "y": 1},
{"label": "R", "X": 4, "y": 1}, {"label": "R", "x": 4, "y": 1},
{"label": "T", "X": 5, "y": 1}, {"label": "T", "x": 5, "y": 1},
{"label": "Y", "X": 6, "y": 1}, {"label": "Y", "x": 6, "y": 1},
{"label": "U", "X": 7, "y": 1}, {"label": "U", "x": 7, "y": 1},
{"label": "I", "X": 8, "y": 1}, {"label": "I", "x": 8, "y": 1},
{"label": "O", "X": 9, "y": 1}, {"label": "O", "x": 9, "y": 1},
{"label": "P", "X": 10, "y": 1}, {"label": "P", "x": 10, "y": 1},
{"label": "|\n\\", "X": 11, "y": 1}, {"label": "|\n\\", "x": 11, "y": 1},
{"label": "7\nHome", "X": 12, "y": 1}, {"label": "7\nHome", "x": 12, "y": 1},
{"label": "8\nUp", "X": 13, "y": 1}, {"label": "8\nUp", "x": 13, "y": 1},
{"label": "9\nPgUp", "X": 14, "y": 1}, {"label": "9\nPgUp", "x": 14, "y": 1},
{"label": "Caps", "x": 0, "y": 2}, {"label": "Caps", "x": 0, "y": 2},
{"label": "A", "x": 1, "y": 2}, {"label": "A", "x": 1, "y": 2},
{"label": "S", "x": 2, "y": 2}, {"label": "S", "x": 2, "y": 2},
@ -67,21 +67,21 @@
{"label": "1\nEnd", "x": 12, "y": 3}, {"label": "1\nEnd", "x": 12, "y": 3},
{"label": "2\nDown", "x": 13, "y": 3}, {"label": "2\nDown", "x": 13, "y": 3},
{"label": "3\nPgDn", "x": 14, "y": 3}, {"label": "3\nPgDn", "x": 14, "y": 3},
{"label": "Ctrl", "X": 0, "y": 4}, {"label": "Ctrl", "x": 0, "y": 4},
{"label": "Win", "X": 1, "y": 4}, {"label": "Win", "x": 1, "y": 4},
{"label": "Alt", "X": 2, "y": 4}, {"label": "Alt", "x": 2, "y": 4},
{"label": "Fn", "X": 3, "y": 4}, {"label": "Fn", "x": 3, "y": 4},
{"label": "Lower", "X": 4, "y": 4}, {"label": "Lower", "x": 4, "y": 4},
{"label": "Space", "X": 5, "y": 4}, {"label": "Space", "x": 5, "y": 4},
{"label": "Space", "X": 6, "y": 4}, {"label": "Space", "x": 6, "y": 4},
{"label": "Raise", "X": 7, "y": 4}, {"label": "Raise", "x": 7, "y": 4},
{"label": "Alt", "X": 8, "y": 4}, {"label": "Alt", "x": 8, "y": 4},
{"label": "Win", "X": 9, "y": 4}, {"label": "Win", "x": 9, "y": 4},
{"label": "Menu", "X": 10, "y": 4}, {"label": "Menu", "x": 10, "y": 4},
{"label": "Ctrl", "X": 11, "y": 4}, {"label": "Ctrl", "x": 11, "y": 4},
{"label": "0\nIns", "X": 12, "y": 4}, {"label": "0\nIns", "x": 12, "y": 4},
{"label": ".\nDel", "X": 13, "y": 4}, {"label": ".\nDel", "x": 13, "y": 4},
{"label": "Enter", "X": 14, "y": 4} {"label": "Enter", "x": 14, "y": 4}
] ]
} }
} }

View file

@ -38,7 +38,10 @@ subcommands = [
'qmk.cli.doctor', 'qmk.cli.doctor',
'qmk.cli.fileformat', 'qmk.cli.fileformat',
'qmk.cli.flash', 'qmk.cli.flash',
'qmk.cli.format.c',
'qmk.cli.format.json', 'qmk.cli.format.json',
'qmk.cli.format.python',
'qmk.cli.format.text',
'qmk.cli.generate.api', 'qmk.cli.generate.api',
'qmk.cli.generate.config_h', 'qmk.cli.generate.config_h',
'qmk.cli.generate.dfu_header', 'qmk.cli.generate.dfu_header',

139
lib/python/qmk/cli/cformat.py Normal file → Executable file
View file

@ -1,137 +1,28 @@
"""Format C code according to QMK's style. """Point people to the new command name.
""" """
from os import path import sys
from shutil import which from pathlib import Path
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
from argcomplete.completers import FilesCompleter
from milc import cli from milc import cli
from qmk.path import normpath
from qmk.c_parse import c_source_files
c_file_suffixes = ('c', 'h', 'cpp')
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
def find_clang_format():
"""Returns the path to clang-format.
"""
for clang_version in range(20, 6, -1):
binary = f'clang-format-{clang_version}'
if which(binary):
return binary
return 'clang-format'
def find_diffs(files):
"""Run clang-format and diff it against a file.
"""
found_diffs = False
for file in files:
cli.log.debug('Checking for changes in %s', file)
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True)
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True)
if diff.returncode != 0:
print(diff.stdout)
found_diffs = True
return found_diffs
def cformat_run(files):
"""Spawn clang-format subprocess with proper arguments
"""
# Determine which version of clang-format to use
clang_format = [find_clang_format(), '-i']
try:
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL)
cli.log.info('Successfully formatted the C code.')
return True
except CalledProcessError as e:
cli.log.error('Error formatting C code!')
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode)
cli.log.debug('STDOUT:')
cli.log.debug(e.stdout)
cli.log.debug('STDERR:')
cli.log.debug(e.stderr)
return False
def filter_files(files, core_only=False):
"""Yield only files to be formatted and skip the rest
"""
if core_only:
# Filter non-core files
for index, file in enumerate(files):
# The following statement checks each file to see if the file path is
# - in the core directories
# - not in the ignored directories
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored):
files[index] = None
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file)
for file in files:
if file and file.name.split('.')[-1] in c_file_suffixes:
yield file
else:
cli.log.debug('Skipping file %s', file)
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.") @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.') @cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.') @cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.') @cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.')
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.') @cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.')
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True) @cli.subcommand('Pointer to the new command name: qmk format-c.', hidden=False if cli.config.user.developer else True)
def cformat(cli): def cformat(cli):
"""Format C code according to QMK's style. """Pointer to the new command name: qmk format-c.
""" """
# Find the list of files to format cli.log.warning('"qmk cformat" has been renamed to "qmk format-c". Please use the new command in the future.')
if cli.args.files: argv = [sys.executable, *sys.argv]
files = list(filter_files(cli.args.files, cli.args.core_only)) argv[argv.index('cformat')] = 'format-c'
script_path = Path(argv[1])
script_path_exe = Path(f'{argv[1]}.exe')
if not files: if not script_path.exists() and script_path_exe.exists():
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files))) # For reasons I don't understand ".exe" is stripped from the script name on windows.
exit(0) argv[1] = str(script_path_exe)
if cli.args.all_files: return cli.run(argv, capture_output=False).returncode
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files)))
elif cli.args.all_files:
all_files = c_source_files(core_dirs)
files = list(filter_files(all_files, True))
else:
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs]
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL)
if git_diff.returncode != 0:
cli.log.error("Error running %s", git_diff_cmd)
print(git_diff.stderr)
return git_diff.returncode
files = []
for file in git_diff.stdout.strip().split('\n'):
if not any([file.startswith(ignore) for ignore in ignored]):
if path.exists(file) and file.split('.')[-1] in c_file_suffixes:
files.append(file)
# Sanity check
if not files:
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files')
return False
# Run clang-format on the files we've found
if cli.args.dry_run:
return not find_diffs(files)
else:
return cformat_run(files)

View file

@ -0,0 +1,5 @@
"""QMK Doctor
Check out the user's QMK environment and make sure it's ready to compile.
"""
from .main import doctor

View file

@ -1,4 +1,4 @@
"""OS-agnostic helper functions """Check for specific programs.
""" """
from enum import Enum from enum import Enum
import re import re
@ -30,7 +30,7 @@ ESSENTIAL_BINARIES = {
} }
def parse_gcc_version(version): def _parse_gcc_version(version):
m = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version) m = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version)
return { return {
@ -40,7 +40,7 @@ def parse_gcc_version(version):
} }
def check_arm_gcc_version(): def _check_arm_gcc_version():
"""Returns True if the arm-none-eabi-gcc version is not known to cause problems. """Returns True if the arm-none-eabi-gcc version is not known to cause problems.
""" """
if 'output' in ESSENTIAL_BINARIES['arm-none-eabi-gcc']: if 'output' in ESSENTIAL_BINARIES['arm-none-eabi-gcc']:
@ -50,7 +50,7 @@ def check_arm_gcc_version():
return CheckStatus.OK # Right now all known arm versions are ok return CheckStatus.OK # Right now all known arm versions are ok
def check_avr_gcc_version(): def _check_avr_gcc_version():
"""Returns True if the avr-gcc version is not known to cause problems. """Returns True if the avr-gcc version is not known to cause problems.
""" """
rc = CheckStatus.ERROR rc = CheckStatus.ERROR
@ -60,7 +60,7 @@ def check_avr_gcc_version():
cli.log.info('Found avr-gcc version %s', version_number) cli.log.info('Found avr-gcc version %s', version_number)
rc = CheckStatus.OK rc = CheckStatus.OK
parsed_version = parse_gcc_version(version_number) parsed_version = _parse_gcc_version(version_number)
if parsed_version['major'] > 8: if parsed_version['major'] > 8:
cli.log.warning('{fg_yellow}We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.') cli.log.warning('{fg_yellow}We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.')
rc = CheckStatus.WARNING rc = CheckStatus.WARNING
@ -68,7 +68,7 @@ def check_avr_gcc_version():
return rc return rc
def check_avrdude_version(): def _check_avrdude_version():
if 'output' in ESSENTIAL_BINARIES['avrdude']: if 'output' in ESSENTIAL_BINARIES['avrdude']:
last_line = ESSENTIAL_BINARIES['avrdude']['output'].split('\n')[-2] last_line = ESSENTIAL_BINARIES['avrdude']['output'].split('\n')[-2]
version_number = last_line.split()[2][:-1] version_number = last_line.split()[2][:-1]
@ -77,7 +77,7 @@ def check_avrdude_version():
return CheckStatus.OK return CheckStatus.OK
def check_dfu_util_version(): def _check_dfu_util_version():
if 'output' in ESSENTIAL_BINARIES['dfu-util']: if 'output' in ESSENTIAL_BINARIES['dfu-util']:
first_line = ESSENTIAL_BINARIES['dfu-util']['output'].split('\n')[0] first_line = ESSENTIAL_BINARIES['dfu-util']['output'].split('\n')[0]
version_number = first_line.split()[1] version_number = first_line.split()[1]
@ -86,7 +86,7 @@ def check_dfu_util_version():
return CheckStatus.OK return CheckStatus.OK
def check_dfu_programmer_version(): def _check_dfu_programmer_version():
if 'output' in ESSENTIAL_BINARIES['dfu-programmer']: if 'output' in ESSENTIAL_BINARIES['dfu-programmer']:
first_line = ESSENTIAL_BINARIES['dfu-programmer']['output'].split('\n')[0] first_line = ESSENTIAL_BINARIES['dfu-programmer']['output'].split('\n')[0]
version_number = first_line.split()[1] version_number = first_line.split()[1]
@ -111,7 +111,7 @@ def check_binary_versions():
"""Check the versions of ESSENTIAL_BINARIES """Check the versions of ESSENTIAL_BINARIES
""" """
versions = [] versions = []
for check in (check_arm_gcc_version, check_avr_gcc_version, check_avrdude_version, check_dfu_util_version, check_dfu_programmer_version): for check in (_check_arm_gcc_version, _check_avr_gcc_version, _check_avrdude_version, _check_dfu_util_version, _check_dfu_programmer_version):
versions.append(check()) versions.append(check())
return versions return versions

View file

@ -1,11 +1,13 @@
"""OS-specific functions for: Linux """OS-specific functions for: Linux
""" """
from pathlib import Path import platform
import shutil import shutil
from pathlib import Path
from milc import cli from milc import cli
from qmk.constants import QMK_FIRMWARE from qmk.constants import QMK_FIRMWARE
from qmk.os_helpers import CheckStatus from .check import CheckStatus
def _udev_rule(vid, pid=None, *args): def _udev_rule(vid, pid=None, *args):
@ -138,3 +140,23 @@ def check_modem_manager():
"""(TODO): Add check for non-systemd systems """(TODO): Add check for non-systemd systems
""" """
return False return False
def os_test_linux():
"""Run the Linux specific tests.
"""
# Don't bother with udev on WSL, for now
if 'microsoft' in platform.uname().release.lower():
cli.log.info("Detected {fg_cyan}Linux (WSL){fg_reset}.")
# https://github.com/microsoft/WSL/issues/4197
if QMK_FIRMWARE.as_posix().startswith("/mnt"):
cli.log.warning("I/O performance on /mnt may be extremely slow.")
return CheckStatus.WARNING
return CheckStatus.OK
else:
cli.log.info("Detected {fg_cyan}Linux{fg_reset}.")
from .linux import check_udev_rules
return check_udev_rules()

View file

@ -0,0 +1,13 @@
import platform
from milc import cli
from .check import CheckStatus
def os_test_macos():
"""Run the Mac specific tests.
"""
cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0])
return CheckStatus.OK

View file

@ -7,9 +7,10 @@ from subprocess import DEVNULL
from milc import cli from milc import cli
from milc.questions import yesno from milc.questions import yesno
from qmk import submodules from qmk import submodules
from qmk.constants import QMK_FIRMWARE from qmk.constants import QMK_FIRMWARE
from qmk.os_helpers import CheckStatus, check_binaries, check_binary_versions, check_submodules, check_git_repo from .check import CheckStatus, check_binaries, check_binary_versions, check_submodules, check_git_repo
def os_tests(): def os_tests():
@ -18,53 +19,19 @@ def os_tests():
platform_id = platform.platform().lower() platform_id = platform.platform().lower()
if 'darwin' in platform_id or 'macos' in platform_id: if 'darwin' in platform_id or 'macos' in platform_id:
from .macos import os_test_macos
return os_test_macos() return os_test_macos()
elif 'linux' in platform_id: elif 'linux' in platform_id:
from .linux import os_test_linux
return os_test_linux() return os_test_linux()
elif 'windows' in platform_id: elif 'windows' in platform_id:
from .windows import os_test_windows
return os_test_windows() return os_test_windows()
else: else:
cli.log.warning('Unsupported OS detected: %s', platform_id) cli.log.warning('Unsupported OS detected: %s', platform_id)
return CheckStatus.WARNING return CheckStatus.WARNING
def os_test_linux():
"""Run the Linux specific tests.
"""
# Don't bother with udev on WSL, for now
if 'microsoft' in platform.uname().release.lower():
cli.log.info("Detected {fg_cyan}Linux (WSL){fg_reset}.")
# https://github.com/microsoft/WSL/issues/4197
if QMK_FIRMWARE.as_posix().startswith("/mnt"):
cli.log.warning("I/O performance on /mnt may be extremely slow.")
return CheckStatus.WARNING
return CheckStatus.OK
else:
cli.log.info("Detected {fg_cyan}Linux{fg_reset}.")
from qmk.os_helpers.linux import check_udev_rules
return check_udev_rules()
def os_test_macos():
"""Run the Mac specific tests.
"""
cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0])
return CheckStatus.OK
def os_test_windows():
"""Run the Windows specific tests.
"""
win32_ver = platform.win32_ver()
cli.log.info("Detected {fg_cyan}Windows %s (%s){fg_reset}.", win32_ver[0], win32_ver[1])
return CheckStatus.OK
@cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.') @cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
@cli.argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.') @cli.argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.')
@cli.subcommand('Basic QMK environment checks') @cli.subcommand('Basic QMK environment checks')

View file

@ -0,0 +1,14 @@
import platform
from milc import cli
from .check import CheckStatus
def os_test_windows():
"""Run the Windows specific tests.
"""
win32_ver = platform.win32_ver()
cli.log.info("Detected {fg_cyan}Windows %s (%s){fg_reset}.", win32_ver[0], win32_ver[1])
return CheckStatus.OK

24
lib/python/qmk/cli/fileformat.py Normal file → Executable file
View file

@ -1,13 +1,23 @@
"""Format files according to QMK's style. """Point people to the new command name.
""" """
import sys
from pathlib import Path
from milc import cli from milc import cli
import subprocess
@cli.subcommand('Pointer to the new command name: qmk format-text.', hidden=False if cli.config.user.developer else True)
@cli.subcommand("Format files according to QMK's style.", hidden=True)
def fileformat(cli): def fileformat(cli):
"""Run several general formatting commands. """Pointer to the new command name: qmk format-text.
""" """
dos2unix = subprocess.run(['bash', '-c', 'git ls-files -z | xargs -0 dos2unix'], stdout=subprocess.DEVNULL) cli.log.warning('"qmk fileformat" has been renamed to "qmk format-text". Please use the new command in the future.')
return dos2unix.returncode argv = [sys.executable, *sys.argv]
argv[argv.index('fileformat')] = 'format-text'
script_path = Path(argv[1])
script_path_exe = Path(f'{argv[1]}.exe')
if not script_path.exists() and script_path_exe.exists():
# For reasons I don't understand ".exe" is stripped from the script name on windows.
argv[1] = str(script_path_exe)
return cli.run(argv, capture_output=False).returncode

View file

@ -0,0 +1,137 @@
"""Format C code according to QMK's style.
"""
from os import path
from shutil import which
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
from argcomplete.completers import FilesCompleter
from milc import cli
from qmk.path import normpath
from qmk.c_parse import c_source_files
c_file_suffixes = ('c', 'h', 'cpp')
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
def find_clang_format():
"""Returns the path to clang-format.
"""
for clang_version in range(20, 6, -1):
binary = f'clang-format-{clang_version}'
if which(binary):
return binary
return 'clang-format'
def find_diffs(files):
"""Run clang-format and diff it against a file.
"""
found_diffs = False
for file in files:
cli.log.debug('Checking for changes in %s', file)
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True)
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True)
if diff.returncode != 0:
print(diff.stdout)
found_diffs = True
return found_diffs
def cformat_run(files):
"""Spawn clang-format subprocess with proper arguments
"""
# Determine which version of clang-format to use
clang_format = [find_clang_format(), '-i']
try:
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL)
cli.log.info('Successfully formatted the C code.')
return True
except CalledProcessError as e:
cli.log.error('Error formatting C code!')
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode)
cli.log.debug('STDOUT:')
cli.log.debug(e.stdout)
cli.log.debug('STDERR:')
cli.log.debug(e.stderr)
return False
def filter_files(files, core_only=False):
"""Yield only files to be formatted and skip the rest
"""
if core_only:
# Filter non-core files
for index, file in enumerate(files):
# The following statement checks each file to see if the file path is
# - in the core directories
# - not in the ignored directories
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored):
files[index] = None
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file)
for file in files:
if file and file.name.split('.')[-1] in c_file_suffixes:
yield file
else:
cli.log.debug('Skipping file %s', file)
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.')
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.')
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
def format_c(cli):
"""Format C code according to QMK's style.
"""
# Find the list of files to format
if cli.args.files:
files = list(filter_files(cli.args.files, cli.args.core_only))
if not files:
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files)))
exit(0)
if cli.args.all_files:
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files)))
elif cli.args.all_files:
all_files = c_source_files(core_dirs)
files = list(filter_files(all_files, True))
else:
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs]
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL)
if git_diff.returncode != 0:
cli.log.error("Error running %s", git_diff_cmd)
print(git_diff.stderr)
return git_diff.returncode
files = []
for file in git_diff.stdout.strip().split('\n'):
if not any([file.startswith(ignore) for ignore in ignored]):
if path.exists(file) and file.split('.')[-1] in c_file_suffixes:
files.append(file)
# Sanity check
if not files:
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files')
return False
# Run clang-format on the files we've found
if cli.args.dry_run:
return not find_diffs(files)
else:
return cformat_run(files)

View file

@ -8,7 +8,7 @@ from jsonschema import ValidationError
from milc import cli from milc import cli
from qmk.info import info_json from qmk.info import info_json
from qmk.json_schema import json_load, keyboard_validate from qmk.json_schema import json_load, validate
from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder
from qmk.path import normpath from qmk.path import normpath
@ -23,14 +23,13 @@ def format_json(cli):
if cli.args.format == 'auto': if cli.args.format == 'auto':
try: try:
keyboard_validate(json_file) validate(json_file, 'qmk.keyboard.v1')
json_encoder = InfoJSONEncoder json_encoder = InfoJSONEncoder
except ValidationError as e: 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.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) cli.log.info('Treating %s as a keymap file.', cli.args.json_file)
json_encoder = KeymapJSONEncoder json_encoder = KeymapJSONEncoder
elif cli.args.format == 'keyboard': elif cli.args.format == 'keyboard':
json_encoder = InfoJSONEncoder json_encoder = InfoJSONEncoder
elif cli.args.format == 'keymap': elif cli.args.format == 'keymap':

View file

@ -0,0 +1,26 @@
"""Format python code according to QMK's style.
"""
from subprocess import CalledProcessError, DEVNULL
from milc import cli
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually format.")
@cli.subcommand("Format python code according to QMK's style.", hidden=False if cli.config.user.developer else True)
def format_python(cli):
"""Format python code according to QMK's style.
"""
edit = '--diff' if cli.args.dry_run else '--in-place'
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python']
try:
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL)
cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.')
return True
except CalledProcessError:
if cli.args.dry_run:
cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!')
else:
cli.log.error('Error formatting python code!')
return False

View file

@ -0,0 +1,27 @@
"""Ensure text files have the proper line endings.
"""
from subprocess import CalledProcessError
from milc import cli
@cli.subcommand("Ensure text files have the proper line endings.", hidden=True)
def format_text(cli):
"""Ensure text files have the proper line endings.
"""
try:
file_list_cmd = cli.run(['git', 'ls-files', '-z'], check=True)
except CalledProcessError as e:
cli.log.error('Could not get file list: %s', e)
exit(1)
except Exception as e:
cli.log.error('Unhandled exception: %s: %s', e.__class__.__name__, e)
cli.log.exception(e)
exit(1)
dos2unix = cli.run(['xargs', '-0', 'dos2unix'], stdin=None, input=file_list_cmd.stdout)
if dos2unix.returncode != 0:
print(dos2unix.stderr)
return dos2unix.returncode

View file

@ -1,26 +1,24 @@
"""Format python code according to QMK's style. """Point people to the new command name.
""" """
from subprocess import CalledProcessError, DEVNULL import sys
from pathlib import Path
from milc import cli from milc import cli
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.") @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually format.")
@cli.subcommand("Format python code according to QMK's style.", hidden=False if cli.config.user.developer else True) @cli.subcommand('Pointer to the new command name: qmk format-python.', hidden=False if cli.config.user.developer else True)
def pyformat(cli): def pyformat(cli):
"""Format python code according to QMK's style. """Pointer to the new command name: qmk format-python.
""" """
edit = '--diff' if cli.args.dry_run else '--in-place' cli.log.warning('"qmk pyformat" has been renamed to "qmk format-python". Please use the new command in the future.')
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python'] argv = [sys.executable, *sys.argv]
try: argv[argv.index('pyformat')] = 'format-python'
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL) script_path = Path(argv[1])
cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.') script_path_exe = Path(f'{argv[1]}.exe')
return True
except CalledProcessError: if not script_path.exists() and script_path_exe.exists():
if cli.args.dry_run: # For reasons I don't understand ".exe" is stripped from the script name on windows.
cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!') argv[1] = str(script_path_exe)
else:
cli.log.error('Error formatting python code!')
return False return cli.run(argv, capture_output=False).returncode

View file

@ -9,7 +9,7 @@ from milc import cli
from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
from qmk.c_parse import find_layouts from qmk.c_parse import find_layouts
from qmk.json_schema import deep_update, json_load, keyboard_validate, keyboard_api_validate from qmk.json_schema import deep_update, json_load, validate
from qmk.keyboard import config_h, rules_mk from qmk.keyboard import config_h, rules_mk
from qmk.keymap import list_keymaps from qmk.keymap import list_keymaps
from qmk.makefile import parse_rules_mk_file from qmk.makefile import parse_rules_mk_file
@ -66,7 +66,7 @@ def info_json(keyboard):
# Validate against the jsonschema # Validate against the jsonschema
try: try:
keyboard_api_validate(info_data) validate(info_data, 'qmk.api.keyboard.v1')
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
json_path = '.'.join([str(p) for p in e.absolute_path]) json_path = '.'.join([str(p) for p in e.absolute_path])
@ -496,7 +496,7 @@ def merge_info_jsons(keyboard, info_data):
continue continue
try: try:
keyboard_validate(new_info_data) validate(new_info_data, 'qmk.keyboard.v1')
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
json_path = '.'.join([str(p) for p in e.absolute_path]) 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('Not including data from file: %s', info_file)

View file

@ -24,9 +24,10 @@ def json_load(json_file):
def load_jsonschema(schema_name): def load_jsonschema(schema_name):
"""Read a jsonschema file from disk. """Read a jsonschema file from disk.
FIXME(skullydazed/anyone): Refactor to make this a public function.
""" """
if Path(schema_name).exists():
return json_load(schema_name)
schema_path = Path(f'data/schemas/{schema_name}.jsonschema') schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
if not schema_path.exists(): if not schema_path.exists():
@ -35,28 +36,33 @@ def load_jsonschema(schema_name):
return json_load(schema_path) return json_load(schema_path)
def keyboard_validate(data): def create_validator(schema):
"""Validates data against the keyboard jsonschema. """Creates a validator for the given schema id.
""" """
schema = load_jsonschema('keyboard') schema_store = {}
validator = jsonschema.Draft7Validator(schema).validate
return validator(data) for schema_file in Path('data/schemas').glob('*.jsonschema'):
schema_data = load_jsonschema(schema_file)
if not isinstance(schema_data, dict):
cli.log.debug('Skipping schema file %s', schema_file)
continue
schema_store[schema_data['$id']] = schema_data
resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate
def keyboard_api_validate(data): def validate(data, schema):
"""Validates data against the api_keyboard jsonschema. """Validates data against a schema.
""" """
base = load_jsonschema('keyboard') validator = create_validator(schema)
relative = load_jsonschema('api_keyboard')
resolver = jsonschema.RefResolver.from_schema(base)
validator = jsonschema.Draft7Validator(relative, resolver=resolver).validate
return validator(data) return validator(data)
def deep_update(origdict, newdict): def deep_update(origdict, newdict):
"""Update a dictionary in place, recursing to do a deep copy. """Update a dictionary in place, recursing to do a depth-first deep copy.
""" """
for key, value in newdict.items(): for key, value in newdict.items():
if isinstance(value, Mapping): if isinstance(value, Mapping):