diff options
| author | Zach White <skullydazed@gmail.com> | 2020-10-17 21:01:11 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-10-17 21:01:11 -0700 |
| commit | 445cd95d1779709673857c36b752afa6327afff1 (patch) | |
| tree | f722089d82590e624d29776e41431bcaf69e0314 | |
| parent | 7d5ba88e6f88979c017bf27e2ad0f4c7d912f9ec (diff) | |
| download | qmk_firmware-445cd95d1779709673857c36b752afa6327afff1.tar.gz qmk_firmware-445cd95d1779709673857c36b752afa6327afff1.zip | |
Improve ANSI support and --no-color (#10537)
* Improve ANSI support and --no-color
* tweak when levelname gets stripped of ansi
* sync with latest milc
* make questions work with both milc versions
* pyformat
| -rw-r--r-- | lib/python/milc.py | 78 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/doctor.py | 22 | ||||
| -rw-r--r-- | lib/python/qmk/questions.py | 7 |
3 files changed, 76 insertions, 31 deletions
diff --git a/lib/python/milc.py b/lib/python/milc.py index eb18984eb..0cdd43dc8 100644 --- a/lib/python/milc.py +++ b/lib/python/milc.py | |||
| @@ -18,9 +18,11 @@ import logging | |||
| 18 | import os | 18 | import os |
| 19 | import re | 19 | import re |
| 20 | import shlex | 20 | import shlex |
| 21 | import subprocess | ||
| 21 | import sys | 22 | import sys |
| 22 | from decimal import Decimal | 23 | from decimal import Decimal |
| 23 | from pathlib import Path | 24 | from pathlib import Path |
| 25 | from platform import platform | ||
| 24 | from tempfile import NamedTemporaryFile | 26 | from tempfile import NamedTemporaryFile |
| 25 | from time import sleep | 27 | from time import sleep |
| 26 | 28 | ||
| @@ -94,29 +96,54 @@ def format_ansi(text): | |||
| 94 | return text + ansi_colors['style_reset_all'] | 96 | return text + ansi_colors['style_reset_all'] |
| 95 | 97 | ||
| 96 | 98 | ||
| 97 | class ANSIFormatter(logging.Formatter): | 99 | class ANSIFormatterMixin(object): |
| 98 | """A log formatter that inserts ANSI color. | 100 | """A log formatter mixin that inserts ANSI color. |
| 99 | """ | 101 | """ |
| 100 | def format(self, record): | 102 | def format(self, record): |
| 101 | msg = super(ANSIFormatter, self).format(record) | 103 | msg = super(ANSIFormatterMixin, self).format(record) |
| 102 | return format_ansi(msg) | 104 | return format_ansi(msg) |
| 103 | 105 | ||
| 104 | 106 | ||
| 105 | class ANSIEmojiLoglevelFormatter(ANSIFormatter): | 107 | class ANSIStrippingMixin(object): |
| 106 | """A log formatter that makes the loglevel an emoji on UTF capable terminals. | 108 | """A log formatter mixin that strips ANSI. |
| 109 | """ | ||
| 110 | def format(self, record): | ||
| 111 | msg = super(ANSIStrippingMixin, self).format(record) | ||
| 112 | record.levelname = ansi_escape.sub('', record.levelname) | ||
| 113 | return ansi_escape.sub('', msg) | ||
| 114 | |||
| 115 | |||
| 116 | class EmojiLoglevelMixin(object): | ||
| 117 | """A log formatter mixin that makes the loglevel an emoji on UTF capable terminals. | ||
| 107 | """ | 118 | """ |
| 108 | def format(self, record): | 119 | def format(self, record): |
| 109 | if UNICODE_SUPPORT: | 120 | if UNICODE_SUPPORT: |
| 110 | record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) | 121 | record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) |
| 111 | return super(ANSIEmojiLoglevelFormatter, self).format(record) | 122 | return super(EmojiLoglevelMixin, self).format(record) |
| 112 | 123 | ||
| 113 | 124 | ||
| 114 | class ANSIStrippingFormatter(ANSIFormatter): | 125 | class ANSIFormatter(ANSIFormatterMixin, logging.Formatter): |
| 115 | """A log formatter that strips ANSI. | 126 | """A log formatter that colorizes output. |
| 116 | """ | 127 | """ |
| 117 | def format(self, record): | 128 | pass |
| 118 | msg = super(ANSIStrippingFormatter, self).format(record) | 129 | |
| 119 | return ansi_escape.sub('', msg) | 130 | |
| 131 | class ANSIStrippingFormatter(ANSIStrippingMixin, ANSIFormatterMixin, logging.Formatter): | ||
| 132 | """A log formatter that strips ANSI | ||
| 133 | """ | ||
| 134 | pass | ||
| 135 | |||
| 136 | |||
| 137 | class ANSIEmojiLoglevelFormatter(EmojiLoglevelMixin, ANSIFormatterMixin, logging.Formatter): | ||
| 138 | """A log formatter that adds Emoji and ANSI | ||
| 139 | """ | ||
| 140 | pass | ||
| 141 | |||
| 142 | |||
| 143 | class ANSIStrippingEmojiLoglevelFormatter(ANSIStrippingMixin, EmojiLoglevelMixin, ANSIFormatterMixin, logging.Formatter): | ||
| 144 | """A log formatter that adds Emoji and strips ANSI | ||
| 145 | """ | ||
| 146 | pass | ||
| 120 | 147 | ||
| 121 | 148 | ||
| 122 | class Configuration(object): | 149 | class Configuration(object): |
| @@ -288,11 +315,12 @@ class MILC(object): | |||
| 288 | self.config_file = None | 315 | self.config_file = None |
| 289 | self.default_arguments = {} | 316 | self.default_arguments = {} |
| 290 | self.version = 'unknown' | 317 | self.version = 'unknown' |
| 291 | self.release_lock() | 318 | self.platform = platform() |
| 292 | 319 | ||
| 293 | # Figure out our program name | 320 | # Figure out our program name |
| 294 | self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0] | 321 | self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0] |
| 295 | self.prog_name = self.prog_name.split('/')[-1] | 322 | self.prog_name = self.prog_name.split('/')[-1] |
| 323 | self.release_lock() | ||
| 296 | 324 | ||
| 297 | # Initialize all the things | 325 | # Initialize all the things |
| 298 | self.read_config_file() | 326 | self.read_config_file() |
| @@ -315,6 +343,8 @@ class MILC(object): | |||
| 315 | strings. | 343 | strings. |
| 316 | 344 | ||
| 317 | If *args or **kwargs are passed they will be used to %-format the strings. | 345 | If *args or **kwargs are passed they will be used to %-format the strings. |
| 346 | |||
| 347 | If `self.config.general.color` is False any ANSI escape sequences in the text will be stripped. | ||
| 318 | """ | 348 | """ |
| 319 | if args and kwargs: | 349 | if args and kwargs: |
| 320 | raise RuntimeError('You can only specify *args or **kwargs, not both!') | 350 | raise RuntimeError('You can only specify *args or **kwargs, not both!') |
| @@ -322,8 +352,27 @@ class MILC(object): | |||
| 322 | args = args or kwargs | 352 | args = args or kwargs |
| 323 | text = format_ansi(text) | 353 | text = format_ansi(text) |
| 324 | 354 | ||
| 355 | if not self.config.general.color: | ||
| 356 | text = ansi_escape.sub('', text) | ||
| 357 | |||
| 325 | print(text % args) | 358 | print(text % args) |
| 326 | 359 | ||
| 360 | def run(self, command, *args, **kwargs): | ||
| 361 | """Run a command with subprocess.run | ||
| 362 | The *args and **kwargs arguments get passed directly to `subprocess.run`. | ||
| 363 | """ | ||
| 364 | if isinstance(command, str): | ||
| 365 | raise TypeError('`command` must be a non-text sequence such as list or tuple.') | ||
| 366 | |||
| 367 | if 'windows' in self.platform.lower(): | ||
| 368 | safecmd = map(shlex.quote, command) | ||
| 369 | safecmd = ' '.join(safecmd) | ||
| 370 | command = [os.environ['SHELL'], '-c', safecmd] | ||
| 371 | |||
| 372 | self.log.debug('Running command: %s', command) | ||
| 373 | |||
| 374 | return subprocess.run(command, *args, **kwargs) | ||
| 375 | |||
| 327 | def initialize_argparse(self): | 376 | def initialize_argparse(self): |
| 328 | """Prepare to process arguments from sys.argv. | 377 | """Prepare to process arguments from sys.argv. |
| 329 | """ | 378 | """ |
| @@ -678,14 +727,13 @@ class MILC(object): | |||
| 678 | self.log_print_level = logging.DEBUG | 727 | self.log_print_level = logging.DEBUG |
| 679 | 728 | ||
| 680 | self.log_file = self.config['general']['log_file'] or self.log_file | 729 | self.log_file = self.config['general']['log_file'] or self.log_file |
| 681 | self.log_file_format = self.config['general']['log_file_fmt'] | ||
| 682 | self.log_file_format = ANSIStrippingFormatter(self.config['general']['log_file_fmt'], self.config['general']['datetime_fmt']) | 730 | self.log_file_format = ANSIStrippingFormatter(self.config['general']['log_file_fmt'], self.config['general']['datetime_fmt']) |
| 683 | self.log_format = self.config['general']['log_fmt'] | 731 | self.log_format = self.config['general']['log_fmt'] |
| 684 | 732 | ||
| 685 | if self.config.general.color: | 733 | if self.config.general.color: |
| 686 | self.log_format = ANSIEmojiLoglevelFormatter(self.args.log_fmt, self.config.general.datetime_fmt) | 734 | self.log_format = ANSIEmojiLoglevelFormatter(self.config.general.log_fmt, self.config.general.datetime_fmt) |
| 687 | else: | 735 | else: |
| 688 | self.log_format = ANSIStrippingFormatter(self.args.log_fmt, self.config.general.datetime_fmt) | 736 | self.log_format = ANSIStrippingEmojiLoglevelFormatter(self.config.general.log_fmt, self.config.general.datetime_fmt) |
| 689 | 737 | ||
| 690 | if self.log_file: | 738 | if self.log_file: |
| 691 | self.log_file_handler = logging.FileHandler(self.log_file, self.log_file_mode) | 739 | self.log_file_handler = logging.FileHandler(self.log_file, self.log_file_mode) |
diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py index 7fafd5757..9983865b3 100755 --- a/lib/python/qmk/cli/doctor.py +++ b/lib/python/qmk/cli/doctor.py | |||
| @@ -156,24 +156,16 @@ def check_udev_rules(): | |||
| 156 | _udev_rule("03EB", "2FF3"), # ATmega16U4 | 156 | _udev_rule("03EB", "2FF3"), # ATmega16U4 |
| 157 | _udev_rule("03EB", "2FF4"), # ATmega32U4 | 157 | _udev_rule("03EB", "2FF4"), # ATmega32U4 |
| 158 | _udev_rule("03EB", "2FF9"), # AT90USB64 | 158 | _udev_rule("03EB", "2FF9"), # AT90USB64 |
| 159 | _udev_rule("03EB", "2FFB") # AT90USB128 | 159 | _udev_rule("03EB", "2FFB") # AT90USB128 |
| 160 | }, | ||
| 161 | 'kiibohd': { | ||
| 162 | _udev_rule("1C11", "B007") | ||
| 163 | }, | 160 | }, |
| 161 | 'kiibohd': {_udev_rule("1C11", "B007")}, | ||
| 164 | 'stm32': { | 162 | 'stm32': { |
| 165 | _udev_rule("1EAF", "0003"), # STM32duino | 163 | _udev_rule("1EAF", "0003"), # STM32duino |
| 166 | _udev_rule("0483", "DF11") # STM32 DFU | 164 | _udev_rule("0483", "DF11") # STM32 DFU |
| 167 | }, | ||
| 168 | 'bootloadhid': { | ||
| 169 | _udev_rule("16C0", "05DF") | ||
| 170 | }, | ||
| 171 | 'usbasploader': { | ||
| 172 | _udev_rule("16C0", "05DC") | ||
| 173 | }, | ||
| 174 | 'massdrop': { | ||
| 175 | _udev_rule("03EB", "6124") | ||
| 176 | }, | 165 | }, |
| 166 | 'bootloadhid': {_udev_rule("16C0", "05DF")}, | ||
| 167 | 'usbasploader': {_udev_rule("16C0", "05DC")}, | ||
| 168 | 'massdrop': {_udev_rule("03EB", "6124")}, | ||
| 177 | 'caterina': { | 169 | 'caterina': { |
| 178 | # Spark Fun Electronics | 170 | # Spark Fun Electronics |
| 179 | _udev_rule("1B4F", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz | 171 | _udev_rule("1B4F", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz |
| @@ -190,7 +182,7 @@ def check_udev_rules(): | |||
| 190 | _udev_rule("239A", "000E", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz | 182 | _udev_rule("239A", "000E", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz |
| 191 | # dog hunter AG | 183 | # dog hunter AG |
| 192 | _udev_rule("2A03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo | 184 | _udev_rule("2A03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo |
| 193 | _udev_rule("2A03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro | 185 | _udev_rule("2A03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro |
| 194 | } | 186 | } |
| 195 | } | 187 | } |
| 196 | 188 | ||
diff --git a/lib/python/qmk/questions.py b/lib/python/qmk/questions.py index 27f43ac1e..865c6bbdc 100644 --- a/lib/python/qmk/questions.py +++ b/lib/python/qmk/questions.py | |||
| @@ -1,7 +1,12 @@ | |||
| 1 | """Functions to collect user input. | 1 | """Functions to collect user input. |
| 2 | """ | 2 | """ |
| 3 | 3 | ||
| 4 | from milc import cli, format_ansi | 4 | from milc import cli |
| 5 | |||
| 6 | try: | ||
| 7 | from milc import format_ansi | ||
| 8 | except ImportError: | ||
| 9 | from milc.ansi import format_ansi | ||
| 5 | 10 | ||
| 6 | 11 | ||
| 7 | def yesno(prompt, *args, default=None, **kwargs): | 12 | def yesno(prompt, *args, default=None, **kwargs): |
