diff options
author | Bernát Gábor <bgabor8@bloomberg.net> | 2020-07-27 09:41:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-27 09:41:51 +0100 |
commit | df44c1c8bee69090064e567142e438ccb73a3d23 (patch) | |
tree | bdfeac0a040d3ca434aee6b3b93ca69ee0d3a276 /src/tox/config | |
parent | c91a77a8a27dc3d0cca34b05021b9f35cd42e2d4 (diff) | |
download | tox-git-df44c1c8bee69090064e567142e438ccb73a3d23.tar.gz |
Color support (#1630)
Diffstat (limited to 'src/tox/config')
-rw-r--r-- | src/tox/config/cli/parse.py | 4 | ||||
-rw-r--r-- | src/tox/config/cli/parser.py | 39 | ||||
-rw-r--r-- | src/tox/config/source/api.py | 19 | ||||
-rw-r--r-- | src/tox/config/source/ini/__init__.py | 2 | ||||
-rw-r--r-- | src/tox/config/source/ini/convert.py | 3 |
5 files changed, 53 insertions, 14 deletions
diff --git a/src/tox/config/cli/parse.py b/src/tox/config/cli/parse.py index a31a01f6..5ba8c5f1 100644 --- a/src/tox/config/cli/parse.py +++ b/src/tox/config/cli/parse.py @@ -9,7 +9,7 @@ def get_options(*args) -> Tuple[Parsed, List[str], Dict[str, Handler]]: guess_verbosity = _get_base(args) handlers, parsed, unknown = _get_core(args) if guess_verbosity != parsed.verbosity: - setup_report(parsed.verbosity) # pragma: no cover + setup_report(parsed.verbosity, parsed.is_colored) # pragma: no cover return parsed, unknown, handlers @@ -17,7 +17,7 @@ def _get_base(args): tox_parser = ToxParser.base() parsed, unknown = tox_parser.parse(args) guess_verbosity = parsed.verbosity - setup_report(guess_verbosity) + setup_report(guess_verbosity, parsed.is_colored) return guess_verbosity diff --git a/src/tox/config/cli/parser.py b/src/tox/config/cli/parser.py index 19cd3be4..4fff9c84 100644 --- a/src/tox/config/cli/parser.py +++ b/src/tox/config/cli/parser.py @@ -1,9 +1,12 @@ import argparse import logging +import os +import sys from argparse import SUPPRESS, Action, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace from itertools import chain from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, TypeVar +from tox.config.source.ini import StrConvert from tox.plugin.util import NAME from tox.session.state import State @@ -42,8 +45,16 @@ class ArgumentParserWithEnvAndConfig(ArgumentParser): def get_type(action): of_type = getattr(action, "of_type", None) if of_type is None: - # noinspection PyProtectedMember - if action.default is not None: + if isinstance(action, argparse._StoreAction) and action.choices: # noqa + loc = locals() + if sys.version_info >= (3, 8): + from typing import Literal # noqa + else: + from typing_extensions import Literal # noqa + loc["Literal"] = Literal + as_literal = f"Literal[{', '.join(repr(i) for i in action.choices)}]" + of_type = eval(as_literal, globals(), loc) + elif action.default is not None: of_type = type(action.default) elif isinstance(action, argparse._StoreConstAction) and action.const is not None: # noqa of_type = type(action.const) @@ -71,6 +82,10 @@ class Parsed(Namespace): def verbosity(self) -> int: return max(self.verbose - self.quiet, 0) + @property + def is_colored(self) -> True: + return self.colored == "yes" + Handler = Callable[[State], Optional[int]] @@ -121,12 +136,20 @@ class ToxParser(ArgumentParserWithEnvAndConfig): verbosity_group = self.add_argument_group( f"verbosity=verbose-quiet, default {logging.getLevelName(LEVELS[3])}, map {level_map}", ) - verbosity_exclusive = verbosity_group.add_mutually_exclusive_group() - verbosity_exclusive.add_argument( - "-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2, - ) - verbosity_exclusive.add_argument( - "-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0, + verbosity = verbosity_group.add_mutually_exclusive_group() + verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2) + verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0) + + converter = StrConvert() + if converter.to_bool(os.environ.get("NO_COLOR", "")): + color = "no" + elif converter.to_bool(os.environ.get("FORCE_COLOR", "")): + color = "yes" + else: + color = "yes" if sys.stdout.isatty() else "no" + + verbosity_group.add_argument( + "--colored", default=color, choices=["yes", "no"], help="should output be enriched with colors", ) self.fix_defaults() diff --git a/src/tox/config/source/api.py b/src/tox/config/source/api.py index d548f16c..7d8405a8 100644 --- a/src/tox/config/source/api.py +++ b/src/tox/config/source/api.py @@ -1,3 +1,4 @@ +import sys from abc import ABC, abstractmethod from collections import OrderedDict from pathlib import Path @@ -5,6 +6,11 @@ from typing import Any, Dict, List, Sequence, Set, Union from tox.execute.request import shell_cmd +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + _NO_MAPPING = object() @@ -39,7 +45,8 @@ class EnvList: class Convert(ABC): def to(self, raw, of_type): - if getattr(of_type, "__module__", None) == "typing": + from_module = getattr(of_type, "__module__", None) + if from_module in ("typing", "typing_extensions"): return self._to_typing(raw, of_type) elif issubclass(of_type, Path): return self.to_path(raw) @@ -54,7 +61,7 @@ class Convert(ABC): return of_type(raw) def _to_typing(self, raw, of_type): - origin = getattr(of_type, "__origin__", None) + origin = getattr(of_type, "__origin__", getattr(of_type, "__class__", None)) if origin is not None: result = _NO_MAPPING # type: Any if origin in (list, List): @@ -72,6 +79,14 @@ class Convert(ABC): else: new_type = next(i for i in of_type.__args__ if i != type(None)) # noqa result = self._to_typing(raw, new_type) + elif origin == Literal or origin == type(Literal): + if sys.version_info >= (3, 7): + choice = of_type.__args__ + else: + choice = of_type.__values__ + if raw not in choice: + raise ValueError(f"{raw} must be one of {choice}") + result = raw if result is not _NO_MAPPING: return result raise TypeError(f"{raw} cannot cast to {of_type!r}") diff --git a/src/tox/config/source/ini/__init__.py b/src/tox/config/source/ini/__init__.py index e1cd135a..9ec14e6f 100644 --- a/src/tox/config/source/ini/__init__.py +++ b/src/tox/config/source/ini/__init__.py @@ -170,7 +170,7 @@ class IniLoader(StrConvert, Loader): if self._section is None: raise KeyError(key) value = self._section[key] - collapsed_newlines = value.replace("\\\n", "") # collapse explicit line splits + collapsed_newlines = value.replace("\\\r", "").replace("\\\n", "") # collapse explicit line splits replace_executed = replace(collapsed_newlines, conf, as_name, self._section_loader) # do replacements factor_selected = filter_for_env(replace_executed, as_name) # select matching factors # extend factors diff --git a/src/tox/config/source/ini/convert.py b/src/tox/config/source/ini/convert.py index 7e0b002e..155365a9 100644 --- a/src/tox/config/source/ini/convert.py +++ b/src/tox/config/source/ini/convert.py @@ -13,6 +13,7 @@ class StrConvert(Convert): @staticmethod def to_list(value): splitter = "\n" if "\n" in value else "," + splitter = splitter.replace("\r", "") for token in value.split(splitter): value = token.strip() if value: @@ -48,7 +49,7 @@ class StrConvert(Convert): return EnvList(elements) TRUTHFUL_VALUES = {"true", "1", "yes", "on"} - FALSY_VALUES = {"false", "0", "no", "off"} + FALSY_VALUES = {"false", "0", "no", "off", ""} VALID_BOOL = list(sorted(TRUTHFUL_VALUES | FALSY_VALUES)) @staticmethod |