From 445cd95d1779709673857c36b752afa6327afff1 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 17 Oct 2020 21:01:11 -0700 Subject: 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 --- lib/python/milc.py | 78 +++++++++++++++++++++++++++++++++++--------- lib/python/qmk/cli/doctor.py | 22 ++++--------- 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 eb18984eb3..0cdd43dc89 100644 --- a/lib/python/milc.py +++ b/lib/python/milc.py @@ -18,9 +18,11 @@ import logging import os import re import shlex +import subprocess import sys from decimal import Decimal from pathlib import Path +from platform import platform from tempfile import NamedTemporaryFile from time import sleep @@ -94,29 +96,54 @@ def format_ansi(text): return text + ansi_colors['style_reset_all'] -class ANSIFormatter(logging.Formatter): - """A log formatter that inserts ANSI color. +class ANSIFormatterMixin(object): + """A log formatter mixin that inserts ANSI color. """ def format(self, record): - msg = super(ANSIFormatter, self).format(record) + msg = super(ANSIFormatterMixin, self).format(record) return format_ansi(msg) -class ANSIEmojiLoglevelFormatter(ANSIFormatter): - """A log formatter that makes the loglevel an emoji on UTF capable terminals. +class ANSIStrippingMixin(object): + """A log formatter mixin that strips ANSI. + """ + def format(self, record): + msg = super(ANSIStrippingMixin, self).format(record) + record.levelname = ansi_escape.sub('', record.levelname) + return ansi_escape.sub('', msg) + + +class EmojiLoglevelMixin(object): + """A log formatter mixin that makes the loglevel an emoji on UTF capable terminals. """ def format(self, record): if UNICODE_SUPPORT: record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) - return super(ANSIEmojiLoglevelFormatter, self).format(record) + return super(EmojiLoglevelMixin, self).format(record) -class ANSIStrippingFormatter(ANSIFormatter): - """A log formatter that strips ANSI. +class ANSIFormatter(ANSIFormatterMixin, logging.Formatter): + """A log formatter that colorizes output. """ - def format(self, record): - msg = super(ANSIStrippingFormatter, self).format(record) - return ansi_escape.sub('', msg) + pass + + +class ANSIStrippingFormatter(ANSIStrippingMixin, ANSIFormatterMixin, logging.Formatter): + """A log formatter that strips ANSI + """ + pass + + +class ANSIEmojiLoglevelFormatter(EmojiLoglevelMixin, ANSIFormatterMixin, logging.Formatter): + """A log formatter that adds Emoji and ANSI + """ + pass + + +class ANSIStrippingEmojiLoglevelFormatter(ANSIStrippingMixin, EmojiLoglevelMixin, ANSIFormatterMixin, logging.Formatter): + """A log formatter that adds Emoji and strips ANSI + """ + pass class Configuration(object): @@ -288,11 +315,12 @@ class MILC(object): self.config_file = None self.default_arguments = {} self.version = 'unknown' - self.release_lock() + self.platform = platform() # Figure out our program name self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0] self.prog_name = self.prog_name.split('/')[-1] + self.release_lock() # Initialize all the things self.read_config_file() @@ -315,6 +343,8 @@ class MILC(object): strings. If *args or **kwargs are passed they will be used to %-format the strings. + + If `self.config.general.color` is False any ANSI escape sequences in the text will be stripped. """ if args and kwargs: raise RuntimeError('You can only specify *args or **kwargs, not both!') @@ -322,8 +352,27 @@ class MILC(object): args = args or kwargs text = format_ansi(text) + if not self.config.general.color: + text = ansi_escape.sub('', text) + print(text % args) + def run(self, command, *args, **kwargs): + """Run a command with subprocess.run + The *args and **kwargs arguments get passed directly to `subprocess.run`. + """ + if isinstance(command, str): + raise TypeError('`command` must be a non-text sequence such as list or tuple.') + + if 'windows' in self.platform.lower(): + safecmd = map(shlex.quote, command) + safecmd = ' '.join(safecmd) + command = [os.environ['SHELL'], '-c', safecmd] + + self.log.debug('Running command: %s', command) + + return subprocess.run(command, *args, **kwargs) + def initialize_argparse(self): """Prepare to process arguments from sys.argv. """ @@ -678,14 +727,13 @@ class MILC(object): self.log_print_level = logging.DEBUG self.log_file = self.config['general']['log_file'] or self.log_file - self.log_file_format = self.config['general']['log_file_fmt'] self.log_file_format = ANSIStrippingFormatter(self.config['general']['log_file_fmt'], self.config['general']['datetime_fmt']) self.log_format = self.config['general']['log_fmt'] if self.config.general.color: - self.log_format = ANSIEmojiLoglevelFormatter(self.args.log_fmt, self.config.general.datetime_fmt) + self.log_format = ANSIEmojiLoglevelFormatter(self.config.general.log_fmt, self.config.general.datetime_fmt) else: - self.log_format = ANSIStrippingFormatter(self.args.log_fmt, self.config.general.datetime_fmt) + self.log_format = ANSIStrippingEmojiLoglevelFormatter(self.config.general.log_fmt, self.config.general.datetime_fmt) if self.log_file: 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 7fafd57575..9983865b31 100755 --- a/lib/python/qmk/cli/doctor.py +++ b/lib/python/qmk/cli/doctor.py @@ -156,24 +156,16 @@ def check_udev_rules(): _udev_rule("03EB", "2FF3"), # ATmega16U4 _udev_rule("03EB", "2FF4"), # ATmega32U4 _udev_rule("03EB", "2FF9"), # AT90USB64 - _udev_rule("03EB", "2FFB") # AT90USB128 - }, - 'kiibohd': { - _udev_rule("1C11", "B007") + _udev_rule("03EB", "2FFB") # AT90USB128 }, + 'kiibohd': {_udev_rule("1C11", "B007")}, 'stm32': { _udev_rule("1EAF", "0003"), # STM32duino - _udev_rule("0483", "DF11") # STM32 DFU - }, - 'bootloadhid': { - _udev_rule("16C0", "05DF") - }, - 'usbasploader': { - _udev_rule("16C0", "05DC") - }, - 'massdrop': { - _udev_rule("03EB", "6124") + _udev_rule("0483", "DF11") # STM32 DFU }, + 'bootloadhid': {_udev_rule("16C0", "05DF")}, + 'usbasploader': {_udev_rule("16C0", "05DC")}, + 'massdrop': {_udev_rule("03EB", "6124")}, 'caterina': { # Spark Fun Electronics _udev_rule("1B4F", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz @@ -190,7 +182,7 @@ def check_udev_rules(): _udev_rule("239A", "000E", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz # dog hunter AG _udev_rule("2A03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo - _udev_rule("2A03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro + _udev_rule("2A03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro } } diff --git a/lib/python/qmk/questions.py b/lib/python/qmk/questions.py index 27f43ac1e9..865c6bbdc5 100644 --- a/lib/python/qmk/questions.py +++ b/lib/python/qmk/questions.py @@ -1,7 +1,12 @@ """Functions to collect user input. """ -from milc import cli, format_ansi +from milc import cli + +try: + from milc import format_ansi +except ImportError: + from milc.ansi import format_ansi def yesno(prompt, *args, default=None, **kwargs): -- cgit v1.2.1