summaryrefslogtreecommitdiff
path: root/src/tox/config
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2020-07-27 09:41:51 +0100
committerGitHub <noreply@github.com>2020-07-27 09:41:51 +0100
commitdf44c1c8bee69090064e567142e438ccb73a3d23 (patch)
treebdfeac0a040d3ca434aee6b3b93ca69ee0d3a276 /src/tox/config
parentc91a77a8a27dc3d0cca34b05021b9f35cd42e2d4 (diff)
downloadtox-git-df44c1c8bee69090064e567142e438ccb73a3d23.tar.gz
Color support (#1630)
Diffstat (limited to 'src/tox/config')
-rw-r--r--src/tox/config/cli/parse.py4
-rw-r--r--src/tox/config/cli/parser.py39
-rw-r--r--src/tox/config/source/api.py19
-rw-r--r--src/tox/config/source/ini/__init__.py2
-rw-r--r--src/tox/config/source/ini/convert.py3
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