aboutsummaryrefslogtreecommitdiff
path: root/lib/python/qmk
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python/qmk')
-rw-r--r--lib/python/qmk/cli/__init__.py1
-rw-r--r--lib/python/qmk/cli/chibios/__init__.py1
-rw-r--r--lib/python/qmk/cli/chibios/confmigrate.py161
-rwxr-xr-xlib/python/qmk/cli/doctor.py2
-rw-r--r--lib/python/qmk/questions.py183
5 files changed, 164 insertions, 184 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py
index 77724a244..10536bb23 100644
--- a/lib/python/qmk/cli/__init__.py
+++ b/lib/python/qmk/cli/__init__.py
@@ -8,6 +8,7 @@ from milc import cli
8 8
9from . import c2json 9from . import c2json
10from . import cformat 10from . import cformat
11from . import chibios
11from . import clean 12from . import clean
12from . import compile 13from . import compile
13from . import config 14from . import config
diff --git a/lib/python/qmk/cli/chibios/__init__.py b/lib/python/qmk/cli/chibios/__init__.py
new file mode 100644
index 000000000..4301837de
--- /dev/null
+++ b/lib/python/qmk/cli/chibios/__init__.py
@@ -0,0 +1 @@
from . import confmigrate
diff --git a/lib/python/qmk/cli/chibios/confmigrate.py b/lib/python/qmk/cli/chibios/confmigrate.py
new file mode 100644
index 000000000..eae294a0c
--- /dev/null
+++ b/lib/python/qmk/cli/chibios/confmigrate.py
@@ -0,0 +1,161 @@
1"""This script automates the copying of the default keymap into your own keymap.
2"""
3import re
4import sys
5import os
6
7from qmk.constants import QMK_FIRMWARE
8from qmk.path import normpath
9from milc import cli
10
11
12def eprint(*args, **kwargs):
13 print(*args, file=sys.stderr, **kwargs)
14
15
16fileHeader = """\
17/* Copyright 2020 QMK
18 *
19 * This program is free software: you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation, either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 */
32
33/*
34 * This file was auto-generated by:
35 * `qmk chibios-confupdate -i {0} -r {1}`
36 */
37
38#pragma once
39"""
40
41
42def collect_defines(filepath):
43 with open(filepath, 'r') as f:
44 content = f.read()
45 define_search = re.compile(r'(?m)^#\s*define\s+(?:.*\\\r?\n)*.*$', re.MULTILINE)
46 value_search = re.compile(r'^#\s*define\s+(?P<name>[a-zA-Z0-9_]+(\([^\)]*\))?)\s*(?P<value>.*)', re.DOTALL)
47 define_matches = define_search.findall(content)
48
49 defines = {"keys": [], "dict": {}}
50 for define_match in define_matches:
51 value_match = value_search.search(define_match)
52 defines["keys"].append(value_match.group("name"))
53 defines["dict"][value_match.group("name")] = value_match.group("value")
54 return defines
55
56
57def check_diffs(input_defs, reference_defs):
58 not_present_in_input = []
59 not_present_in_reference = []
60 to_override = []
61
62 for key in reference_defs["keys"]:
63 if key not in input_defs["dict"]:
64 not_present_in_input.append(key)
65 continue
66
67 for key in input_defs["keys"]:
68 if key not in input_defs["dict"]:
69 not_present_in_input.append(key)
70 continue
71
72 for key in input_defs["keys"]:
73 if key in reference_defs["keys"] and input_defs["dict"][key] != reference_defs["dict"][key]:
74 to_override.append((key, input_defs["dict"][key]))
75
76 return (to_override, not_present_in_input, not_present_in_reference)
77
78
79def migrate_chconf_h(to_override, outfile):
80 print(fileHeader.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile)
81
82 for override in to_override:
83 print("#define %s %s" % (override[0], override[1]), file=outfile)
84 print("", file=outfile)
85
86 print("#include_next <chconf.h>\n", file=outfile)
87
88
89def migrate_halconf_h(to_override, outfile):
90 print(fileHeader.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile)
91
92 for override in to_override:
93 print("#define %s %s" % (override[0], override[1]), file=outfile)
94 print("", file=outfile)
95
96 print("#include_next <halconf.h>\n", file=outfile)
97
98
99def migrate_mcuconf_h(to_override, outfile):
100 print(fileHeader.format(cli.args.input.relative_to(QMK_FIRMWARE), cli.args.reference.relative_to(QMK_FIRMWARE)), file=outfile)
101
102 print("#include_next <mcuconf.h>\n", file=outfile)
103
104 for override in to_override:
105 print("#undef %s" % (override[0]), file=outfile)
106 print("#define %s %s" % (override[0], override[1]), file=outfile)
107 print("", file=outfile)
108
109
110@cli.argument('-i', '--input', type=normpath, arg_only=True, help='Specify input config file.')
111@cli.argument('-r', '--reference', type=normpath, arg_only=True, help='Specify the reference file to compare against')
112@cli.argument('-o', '--overwrite', arg_only=True, action='store_true', help='Overwrites the input file during migration.')
113@cli.argument('-d', '--delete', arg_only=True, action='store_true', help='If the file has no overrides, migration will delete the input file.')
114@cli.subcommand('Generates a migrated ChibiOS configuration file, as a result of comparing the input against a reference')
115def chibios_confmigrate(cli):
116 """Generates a usable ChibiOS replacement configuration file, based on a fully-defined conf and a reference config.
117 """
118
119 input_defs = collect_defines(cli.args.input)
120 reference_defs = collect_defines(cli.args.reference)
121
122 (to_override, not_present_in_input, not_present_in_reference) = check_diffs(input_defs, reference_defs)
123
124 if len(not_present_in_input) > 0:
125 eprint("Keys not in input, but present inside reference (potential manual migration required):")
126 for key in not_present_in_input:
127 eprint(" %s" % (key))
128
129 if len(not_present_in_reference) > 0:
130 eprint("Keys not in reference, but present inside input (potential manual migration required):")
131 for key in not_present_in_reference:
132 eprint(" %s" % (key))
133
134 if len(to_override) == 0:
135 eprint('No overrides found! If there were no missing keys above, it should be safe to delete the input file.')
136 if cli.args.delete:
137 os.remove(cli.args.input)
138 else:
139 eprint('Overrides found:')
140 for override in to_override:
141 eprint("%40s: %s -> %s" % (override[0], reference_defs["dict"][override[0]].encode('unicode_escape').decode("utf-8"), override[1].encode('unicode_escape').decode("utf-8")))
142
143 eprint('--------------------------------------')
144
145 if "CHCONF_H" in input_defs["dict"] or "_CHCONF_H_" in input_defs["dict"]:
146 migrate_chconf_h(to_override, outfile=sys.stdout)
147 if cli.args.overwrite:
148 with open(cli.args.input, "w") as out_file:
149 migrate_chconf_h(to_override, outfile=out_file)
150
151 elif "HALCONF_H" in input_defs["dict"] or "_HALCONF_H_" in input_defs["dict"]:
152 migrate_halconf_h(to_override, outfile=sys.stdout)
153 if cli.args.overwrite:
154 with open(cli.args.input, "w") as out_file:
155 migrate_halconf_h(to_override, outfile=out_file)
156
157 elif "MCUCONF_H" in input_defs["dict"] or "_MCUCONF_H_" in input_defs["dict"]:
158 migrate_mcuconf_h(to_override, outfile=sys.stdout)
159 if cli.args.overwrite:
160 with open(cli.args.input, "w") as out_file:
161 migrate_mcuconf_h(to_override, outfile=out_file)
diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py
index a5eda555f..4fe318b63 100755
--- a/lib/python/qmk/cli/doctor.py
+++ b/lib/python/qmk/cli/doctor.py
@@ -10,9 +10,9 @@ from pathlib import Path
10from enum import Enum 10from enum import Enum
11 11
12from milc import cli 12from milc import cli
13from milc.questions import yesno
13from qmk import submodules 14from qmk import submodules
14from qmk.constants import QMK_FIRMWARE 15from qmk.constants import QMK_FIRMWARE
15from qmk.questions import yesno
16from qmk.commands import run 16from qmk.commands import run
17 17
18 18
diff --git a/lib/python/qmk/questions.py b/lib/python/qmk/questions.py
deleted file mode 100644
index 865c6bbdc..000000000
--- a/lib/python/qmk/questions.py
+++ /dev/null
@@ -1,183 +0,0 @@
1"""Functions to collect user input.
2"""
3
4from milc import cli
5
6try:
7 from milc import format_ansi
8except ImportError:
9 from milc.ansi import format_ansi
10
11
12def yesno(prompt, *args, default=None, **kwargs):
13 """Displays prompt to the user and gets a yes or no response.
14
15 Returns True for a yes and False for a no.
16
17 If you add `--yes` and `--no` arguments to your program the user can answer questions by passing command line flags.
18
19 @add_argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
20 @add_argument('-n', '--no', action='store_true', arg_only=True, help='Answer no to all questions.')
21
22 Arguments:
23 prompt
24 The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.
25
26 default
27 Whether to default to a Yes or No when the user presses enter.
28
29 None- force the user to enter Y or N
30
31 True- Default to yes
32
33 False- Default to no
34 """
35 if not args and kwargs:
36 args = kwargs
37
38 if 'no' in cli.args and cli.args.no:
39 return False
40
41 if 'yes' in cli.args and cli.args.yes:
42 return True
43
44 if default is not None:
45 if default:
46 prompt = prompt + ' [Y/n] '
47 else:
48 prompt = prompt + ' [y/N] '
49
50 while True:
51 cli.echo('')
52 answer = input(format_ansi(prompt % args))
53 cli.echo('')
54
55 if not answer and prompt is not None:
56 return default
57
58 elif answer.lower() in ['y', 'yes']:
59 return True
60
61 elif answer.lower() in ['n', 'no']:
62 return False
63
64
65def question(prompt, *args, default=None, confirm=False, answer_type=str, validate=None, **kwargs):
66 """Prompt the user to answer a question with a free-form input.
67
68 Arguments:
69 prompt
70 The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.
71
72 default
73 The value to return when the user doesn't enter any value. Use None to prompt until they enter a value.
74
75 confirm
76 Present the user with a confirmation dialog before accepting their answer.
77
78 answer_type
79 Specify a type function for the answer. Will re-prompt the user if the function raises any errors. Common choices here include int, float, and decimal.Decimal.
80
81 validate
82 This is an optional function that can be used to validate the answer. It should return True or False and have the following signature:
83
84 def function_name(answer, *args, **kwargs):
85 """
86 if not args and kwargs:
87 args = kwargs
88
89 if default is not None:
90 prompt = '%s [%s] ' % (prompt, default)
91
92 while True:
93 cli.echo('')
94 answer = input(format_ansi(prompt % args))
95 cli.echo('')
96
97 if answer:
98 if validate is not None and not validate(answer, *args, **kwargs):
99 continue
100
101 elif confirm:
102 if yesno('Is the answer "%s" correct?', answer, default=True):
103 try:
104 return answer_type(answer)
105 except Exception as e:
106 cli.log.error('Could not convert answer (%s) to type %s: %s', answer, answer_type.__name__, str(e))
107
108 else:
109 try:
110 return answer_type(answer)
111 except Exception as e:
112 cli.log.error('Could not convert answer (%s) to type %s: %s', answer, answer_type.__name__, str(e))
113
114 elif default is not None:
115 return default
116
117
118def choice(heading, options, *args, default=None, confirm=False, prompt='Please enter your choice: ', **kwargs):
119 """Present the user with a list of options and let them pick one.
120
121 Users can enter either the number or the text of their choice.
122
123 This will return the value of the item they choose, not the numerical index.
124
125 Arguments:
126 heading
127 The text to place above the list of options.
128
129 options
130 A sequence of items to choose from.
131
132 default
133 The index of the item to return when the user doesn't enter any value. Use None to prompt until they enter a value.
134
135 confirm
136 Present the user with a confirmation dialog before accepting their answer.
137
138 prompt
139 The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.
140 """
141 if not args and kwargs:
142 args = kwargs
143
144 if prompt and default:
145 prompt = prompt + ' [%s] ' % (default + 1,)
146
147 while True:
148 # Prompt for an answer.
149 cli.echo('')
150 cli.echo(heading % args)
151 cli.echo('')
152 for i, option in enumerate(options, 1):
153 cli.echo('\t{fg_cyan}%d.{fg_reset} %s', i, option)
154
155 cli.echo('')
156 answer = input(format_ansi(prompt))
157 cli.echo('')
158
159 # If the user types in one of the options exactly use that
160 if answer in options:
161 return answer
162
163 # Massage the answer into a valid integer
164 if answer == '' and default:
165 answer = default
166 else:
167 try:
168 answer = int(answer) - 1
169 except Exception:
170 # Normally we would log the exception here, but in the interest of clean UI we do not.
171 cli.log.error('Invalid choice: %s', answer + 1)
172 continue
173
174 # Validate the answer
175 if answer >= len(options) or answer < 0:
176 cli.log.error('Invalid choice: %s', answer + 1)
177 continue
178
179 if confirm and not yesno('Is the answer "%s" correct?', answer + 1, default=True):
180 continue
181
182 # Return the answer they chose.
183 return options[answer]