Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to force syntax highlighting using config file, env var or command line. Enable syntax highlighting in GitHub Action. #353

Merged
merged 19 commits into from
Apr 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Added
- Linters can now be installed and run in the GitHub Action using the ``lint:`` option.
- Sort imports only if the range of modified lines overlaps with changes resulting from
sorting the imports.
- Allow force enabling/disabling of syntax highlighting using the ``color`` option in
``pyproject.toml``, the ``PY_COLORS`` environment variable, and the
``--color``/``--no-color`` command line options.
- Syntax highlighting is now enabled by default in the GitHub Action.

Fixed
-----
Expand Down
53 changes: 46 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -292,24 +292,34 @@ The following `command line arguments`_ can also be used to modify the defaults:
are not found, Darker works against ``HEAD``.
--diff
Don't write the files back, just output a diff for each file on stdout. Highlight
syntax on screen if the ``pygments`` package is available.
syntax if on a terminal and the ``pygments`` package is available, or if enabled
by configuration.
-d, --stdout
Force complete reformatted output to stdout, instead of in-place. Only valid if
there's just one file to reformat.
there's just one file to reformat. Highlight syntax if on a terminal and the
``pygments`` package is available, or if enabled by configuration.
--check
Don't write the files back, just return the status. Return code 0 means nothing
would change. Return code 1 means some files would be reformatted.
-i, --isort
Also sort imports using the ``isort`` package
-L CMD, --lint CMD
Also run a linter on changed files. ``CMD`` can be a name of path of the linter
binary, or a full quoted command line
binary, or a full quoted command line. Highlight linter output syntax if on a
terminal and the ``pygments`` package is available, or if enabled by
configuration.
-c PATH, --config PATH
Ask ``black`` and ``isort`` to read configuration from ``PATH``.
-v, --verbose
Show steps taken and summarize modifications
-q, --quiet
Reduce amount of output
--color
Enable syntax highlighting even for non-terminal output. Overrides the
environment variable PY_COLORS=0
--no-color
Disable syntax highlighting even for terminal output. Overrides the environment
variable PY_COLORS=1
-S, --skip-string-normalization
Don't normalize string quotes or prefixes
--no-skip-string-normalization
Expand All @@ -322,7 +332,7 @@ The following `command line arguments`_ can also be used to modify the defaults:
-l LENGTH, --line-length LENGTH
How many characters per line to allow [default: 88]
-W WORKERS, --workers WORKERS
How many parallel workers to allow [default: 1]
How many parallel workers to allow, or ``0`` for one per core [default: 1]

To change default values for these options for a given project,
add a ``[tool.darker]`` section to ``pyproject.toml`` in the project's root directory.
Expand Down Expand Up @@ -373,6 +383,8 @@ other tools. For example, VSCode only expects the reformat diff, so

*New in version 1.5.0:* The ``-W`` / ``--workers`` command line option

*New in version 1.5.0:* The ``--color`` and ``--no-color`` command line options

.. _Black documentation about pyproject.toml: https://black.readthedocs.io/en/stable/pyproject_toml.html
.. _isort documentation about config files: https://timothycrosley.github.io/isort/docs/configuration/config_files/
.. _command line arguments: https://black.readthedocs.io/en/stable/installation_and_usage.html#command-line-options
Expand Down Expand Up @@ -621,7 +633,7 @@ Create a file named ``.github/workflows/darker.yml`` inside your repository with
- uses: actions/setup-python@v2
- uses: akaihola/darker@1.4.2
with:
options: "--check --diff"
options: "--check --diff --color"
revision: "master..."
src: "./src"
version: "1.4.2"
Expand All @@ -636,7 +648,8 @@ We recommend to pin this to a specific release.
``"version:"`` specifies which version of Darker to run in the GitHub Action.
It defaults to the same version as in ``"uses:"``,
but you can force it to use a different version as well.
Only versions available from PyPI are supported, so no commit SHAs or branch names.
Darker versions available from PyPI are supported, as well as commit SHAs or branch
names, prefixed with an ``@`` symbol (e.g. ``version: "@master"``).

The ``revision: "master..."`` (or ``"main..."``) option instructs Darker
to compare the current branch to the branching point from main branch
Expand All @@ -648,7 +661,7 @@ This is typically the source tree, but you can use ``"."`` (the default)
to also reformat Python files like ``"setup.py"`` in the root of the whole repository.

You can also configure other arguments passed to Darker via ``"options:"``.
It defaults to ``"--check --diff"``.
It defaults to ``"--check --diff --color"``.
You can e.g. add ``"--isort"`` to sort imports, or ``"--verbose"`` for debug logging.

To run linters through Darker, you can provide a comma separated list of linters using
Expand Down Expand Up @@ -723,6 +736,32 @@ Here's an example of `cov_to_lint.py`_ output::
.. _cov_to_lint.py: https://gist.github.com/akaihola/2511fe7d2f29f219cb995649afd3d8d2


Syntax highlighting
===================

Darker automatically enables syntax highlighting for the ``--diff``,
``-d``/``--stdout`` and ``-L``/``--lint`` options if it's running on a terminal and the
Pygments_ package is installed.

You can force enable syntax highlighting on non-terminal output using

- the ``color = true`` option in the ``[tool.darker]`` section of ``pyproject.toml`` of
your Python project's root directory,
- the ``PY_COLORS=1`` environment variable, and
- the ``--color`` command line option for ``darker``.

You can force disable syntax highlighting on terminal output using

- the ``color = false`` option in ``pyproject.toml``,
- the ``PY_COLORS=0`` environment variable, and
- the ``--no-color`` command line option.

In the above lists, latter configuration methods override earlier ones, so the command
line options always take highest precedence.

.. _Pygments: https://pypi.org/project/Pygments/


How does it work?
=================

Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ inputs:
options:
description: >-
Options passed to Darker. Use `darker --help` to see available options.
Default: '--check --diff'
Default: '--check --diff --color'
required: false
default: "--check --diff"
default: "--check --diff --color"
src:
description: "Source to run Darker. Default: '.'"
required: false
Expand Down
7 changes: 5 additions & 2 deletions action/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@

run([sys.executable, "-m", "venv", str(ENV_PATH)], check=True)

req = ["darker[isort]"]
req = ["darker[color,isort]"]
if VERSION:
req[0] += f"=={VERSION}"
if VERSION.startswith("@"):
req[0] = f"git+https://github.com/akaihola/darker{VERSION}#egg={req[0]}"
else:
req[0] += f"=={VERSION}"
linter_options = []
for linter_requirement in parse_requirements(LINT.replace(",", "\n")):
linter = linter_requirement.name
Expand Down
26 changes: 17 additions & 9 deletions action/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,35 @@ def test_creates_virtualenv(tmp_path, main_patch):


@pytest.mark.kwparametrize(
dict(run_main_env={}, expect=["darker[isort]"]),
dict(run_main_env={"INPUT_VERSION": "1.5.0"}, expect=["darker[isort]==1.5.0"]),
dict(run_main_env={}, expect=["darker[color,isort]"]),
dict(
run_main_env={"INPUT_VERSION": "1.5.0"}, expect=["darker[color,isort]==1.5.0"]
),
dict(
run_main_env={"INPUT_VERSION": "@master"},
expect=[
"git+https://github.com/akaihola/darker@master#egg=darker[color,isort]"
],
),
dict(
run_main_env={"INPUT_LINT": "pylint"},
expect=["darker[isort]", "pylint"],
expect=["darker[color,isort]", "pylint"],
),
dict(
run_main_env={"INPUT_LINT": "pylint,flake8"},
expect=["darker[isort]", "pylint", "flake8"],
expect=["darker[color,isort]", "pylint", "flake8"],
),
dict(
run_main_env={"INPUT_LINT": " flake8 "},
expect=["darker[isort]", "flake8"],
expect=["darker[color,isort]", "flake8"],
),
dict(
run_main_env={"INPUT_LINT": " flake8 , pylint "},
expect=["darker[isort]", "flake8", "pylint"],
expect=["darker[color,isort]", "flake8", "pylint"],
),
dict(
run_main_env={"INPUT_LINT": " flake8 >= 3.9.2 , pylint == 2.13.1 "},
expect=["darker[isort]", "flake8>=3.9.2", "pylint==2.13.1"],
expect=["darker[color,isort]", "flake8>=3.9.2", "pylint==2.13.1"],
),
)
def test_installs_packages(tmp_path, main_patch, run_main_env, expect):
Expand Down Expand Up @@ -224,15 +232,15 @@ def test_error_if_pip_fails(tmp_path, capsys):
run_module("main")

assert main_patch.subprocess.run.call_args_list[-1] == call(
[ANY, "-m", "pip", "install", "darker[isort]"],
[ANY, "-m", "pip", "install", "darker[color,isort]"],
check=False,
stdout=PIPE,
stderr=STDOUT,
encoding="utf-8",
)
assert (
capsys.readouterr().out.splitlines()[-1]
== "::error::Failed to install darker[isort]."
== "::error::Failed to install darker[color,isort]."
)
main_patch.sys.exit.assert_called_once_with(42)

Expand Down
1 change: 1 addition & 0 deletions constraints-oldest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ flake8-2020==1.6.1
flake8-bugbear==22.1.11
flake8-comprehensions==3.7.0
mypy==0.940
Pygments==2.4.0
pytest==6.1.0
pytest-flake8==1.0.6
pytest-isort==1.1.0
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pygments.lexers =
[options.extras_require]
isort =
isort>=5.0.1
color =
Pygments>=2.4.0
test =
# NOTE: remember to keep `constraints-oldest.txt` in sync with these
airium>=0.2.3
Expand Down
27 changes: 17 additions & 10 deletions src/darker/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
git_is_repository,
)
from darker.help import ISORT_INSTRUCTION
from darker.highlighting import colorize
from darker.highlighting import colorize, should_use_color
from darker.import_sorting import apply_isort, isort
from darker.linting import run_linters
from darker.utils import GIT_DATEFORMAT, TextDocument, debug_dump, get_common_root
Expand Down Expand Up @@ -269,7 +269,11 @@ def modify_file(path: Path, new_content: TextDocument) -> None:


def print_diff(
path: Path, old: TextDocument, new: TextDocument, root: Path = None
path: Path,
old: TextDocument,
new: TextDocument,
root: Path,
use_color: bool,
) -> None:
"""Print ``black --diff`` style output for the changes

Expand All @@ -281,8 +285,6 @@ def print_diff(
Modification times should be in the format "YYYY-MM-DD HH:MM:SS:mmmmmm +0000"

"""
if root is None:
root = Path.cwd()
relative_path = path.resolve().relative_to(root).as_posix()
diff = "\n".join(
line.rstrip("\n")
Expand All @@ -296,12 +298,12 @@ def print_diff(
n=5, # Black shows 5 lines of context, do the same
)
)
print(colorize(diff, "diff"))
print(colorize(diff, "diff", use_color))


def print_source(new: TextDocument) -> None:
def print_source(new: TextDocument, use_color: bool) -> None:
"""Print the reformatted Python source code"""
if sys.stdout.isatty():
if use_color:
try:
(
highlight,
Expand Down Expand Up @@ -443,6 +445,7 @@ def main(argv: List[str] = None) -> int:
f for f in changed_files_to_process if root / f not in files_to_blacken
}

use_color = should_use_color(config["color"])
formatting_failures_on_modified_lines = False
for path, old, new in sorted(
format_edited_parts(
Expand All @@ -458,13 +461,17 @@ def main(argv: List[str] = None) -> int:
):
formatting_failures_on_modified_lines = True
if output_mode == OutputMode.DIFF:
print_diff(path, old, new, root)
print_diff(path, old, new, root, use_color)
elif output_mode == OutputMode.CONTENT:
print_source(new)
print_source(new, use_color)
if write_modified_files:
modify_file(path, new)
linter_failures_on_modified_lines = run_linters(
args.lint, root, changed_files_to_process, revrange
args.lint,
root,
changed_files_to_process,
revrange,
use_color,
)
return (
1
Expand Down
3 changes: 2 additions & 1 deletion src/darker/argparse_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def __call__(
optional_arguments_group = next(
group
for group in parser._action_groups
if group.title == "optional arguments"
# The group title for options differs between Python versions
if group.title in {"optional arguments", "options"}
)
actions = []
for action in optional_arguments_group._group_actions:
Expand Down
10 changes: 7 additions & 3 deletions src/darker/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
get_effective_config,
get_modified_config,
load_config,
override_color_with_environment,
)
from darker.version import __version__

Expand Down Expand Up @@ -52,6 +53,8 @@ def add_arg(help_text: Optional[str], *name_or_flags: str, **kwargs: Any) -> Non
const=-10,
)
add_arg(hlp.QUIET, "-q", "--quiet", action="log_level", dest="log_level", const=10)
add_arg(hlp.COLOR, "--color", action="store_const", dest="color", const=True)
add_arg(hlp.NO_COLOR, "--no-color", action="store_const", dest="color", const=False)
add_arg(hlp.VERSION, "--version", action="version", version=__version__)
add_arg(
hlp.SKIP_STRING_NORMALIZATION,
Expand Down Expand Up @@ -93,8 +96,8 @@ def parse_command_line(argv: List[str]) -> Tuple[Namespace, DarkerConfig, Darker
"""Return the parsed command line, using defaults from a configuration file

Also return the effective configuration which combines defaults, the configuration
read from ``pyproject.toml`` (or the path given in ``--config``), and command line
arguments.
read from ``pyproject.toml`` (or the path given in ``--config``), environment
variables, and command line arguments.

Finally, also return the set of configuration options which differ from defaults.

Expand All @@ -105,7 +108,8 @@ def parse_command_line(argv: List[str]) -> Tuple[Namespace, DarkerConfig, Darker

# 2. Locate `pyproject.toml` based on those paths, or in the current directory if no
# paths were given. Load Darker configuration from it.
config = load_config(args.src)
pyproject_config = load_config(args.src)
config = override_color_with_environment(pyproject_config)

# 3. Use configuration as defaults for re-parsing command line arguments, and don't
# require file/directory paths if they are specified in configuration.
Expand Down
17 changes: 17 additions & 0 deletions src/darker/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Load and save configuration in TOML format"""

import logging
import os
import sys
from argparse import ArgumentParser, Namespace
from pathlib import Path
Expand Down Expand Up @@ -36,6 +37,7 @@ class DarkerConfig(TypedDict, total=False):
lint: List[str]
config: str
log_level: int
color: bool
skip_string_normalization: bool
skip_magic_trailing_comma: bool
line_length: int
Expand Down Expand Up @@ -97,6 +99,21 @@ def validate_config_output_mode(config: DarkerConfig) -> None:
)


def override_color_with_environment(pyproject_config: DarkerConfig) -> DarkerConfig:
"""Override ``color`` if the ``PY_COLORS`` environment variable is '0' or '1'

:param config: The configuration read from ``pyproject.toml``
:return: The modified configuration

"""
py_colors = os.getenv("PY_COLORS")
if py_colors not in {"0", "1"}:
MatthijsBurgh marked this conversation as resolved.
Show resolved Hide resolved
return pyproject_config
config = pyproject_config.copy()
config["color"] = py_colors == "1"
return config


def load_config(srcs: Iterable[str]) -> DarkerConfig:
"""Find and load Darker configuration from given path or pyproject.toml

Expand Down