From bdd71eef07c6b902b37a774ef7b5b9005c922ea2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 13 Nov 2023 18:45:39 +0100 Subject: [PATCH] test black --- lib/python/pyflyby/_cmdline.py | 2 +- lib/python/pyflyby/_format.py | 8 ++- lib/python/pyflyby/_importstmt.py | 98 +++++++++++++++++++++++++------ 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/lib/python/pyflyby/_cmdline.py b/lib/python/pyflyby/_cmdline.py index 13550950..c2dc174c 100644 --- a/lib/python/pyflyby/_cmdline.py +++ b/lib/python/pyflyby/_cmdline.py @@ -207,7 +207,7 @@ def callback(option, opt_str, value, parser): help=hfmt(''' (Default) Don't align the 'from __future__ import ...' statement.''')) - group.add_option('--width', type='int', default=79, metavar='N', + group.add_option('--width', type='int', default=None, metavar='N', help=hfmt(''' Maximum line length (default: 79).''')) group.add_option('--black', action='store_true', default=False, diff --git a/lib/python/pyflyby/_format.py b/lib/python/pyflyby/_format.py index 7ca0101d..9e64609e 100644 --- a/lib/python/pyflyby/_format.py +++ b/lib/python/pyflyby/_format.py @@ -8,7 +8,8 @@ class FormatParams(object): - max_line_length = 79 + max_line_length = None + _max_line_lenght_default = 79 wrap_paren = True indent = 4 hanging_indent = 'never' @@ -37,6 +38,9 @@ def __new__(cls, *args, **kwargs): raise ValueError("bad kwarg %r" % (key,)) return self + def __repr__(self): + return f'<{self.__class__.__name__} {self.__dict__}>' + def fill(tokens, sep=(", ", ""), prefix="", suffix="", newline="\n", max_line_length=80): @@ -125,7 +129,7 @@ def pyfill(prefix, tokens, params=FormatParams()): :rtype: ``str`` """ - N = params.max_line_length + N = params.max_line_length or params._max_line_lenght_default if params.wrap_paren: # Check how we will break up the tokens. len_full = sum(len(tok) for tok in tokens) + 2 * (len(tokens)-1) diff --git a/lib/python/pyflyby/_importstmt.py b/lib/python/pyflyby/_importstmt.py index 9ddbf2ee..3700babb 100644 --- a/lib/python/pyflyby/_importstmt.py +++ b/lib/python/pyflyby/_importstmt.py @@ -16,6 +16,41 @@ from pyflyby._util import (Inf, cached_attribute, cmp, longest_common_prefix) +from black import (find_pyproject_toml, format_str, + parse_pyproject_toml, format_str, + TargetVersion, FileMode as Mode) + + +def read_black_config(): + """Read the black configuration from ``pyproject.toml`` + + + """ + value = find_pyproject_toml('.') + + raw_config = parse_pyproject_toml(value) + + config = {} + for key in [ + "line_length", + "skip_magic_trailing_comma", + "skip_string_normalization", + ]: + if key in raw_config: + config[key] = raw_config[key] + if "target_version" in raw_config: + target_version = raw_config["target_version"] + if isinstance(target_version, str): + config["target_version"] = target_version + elif isinstance(target_version, list): + # Convert TOML list to a Python set + config["target_version"] = set(target_version) + else: + raise ValueError( + f"Invalid config for black = {target_version!r} in {value}" + ) + return config + class ImportFormatParams(FormatParams): align_imports = True @@ -491,26 +526,53 @@ def pretty_print(self, params=FormatParams(), return res @staticmethod - def run_black(str_to_format, params): - black_cmd = [ - f'black --line-length {str(params.max_line_length)} -c "{str_to_format.strip()}"', - ] - try: - completed_process = subprocess.run( - black_cmd, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - ) + def run_black(src_contents: str, params) -> str: + """Run the black formatter for the Python source code given as a string - if completed_process.returncode == 0: - formatted_code = completed_process.stdout - return formatted_code + This is adapted from https://github.com/akaihola/darker + + """ + black_config = read_black_config() + mode = dict() + if "line_length" in black_config: + mode["line_length"] = ( + params.max_line_length + if params.max_line_length + else black_config["line_length"] + ) + if "target_version" in black_config: + if isinstance(black_config["target_version"], set): + target_versions_in = black_config["target_version"] else: - raise ValueError(completed_process.stderr) - except Exception: - raise + target_versions_in = {black_config["target_version"]} + all_target_versions = { + tgt_v.name.lower(): tgt_v for tgt_v in TargetVersion + } + bad_target_versions = target_versions_in - set(all_target_versions) + if bad_target_versions: + raise ConfigurationError( + f"Invalid target version(s) {bad_target_versions}" + ) + mode["target_versions"] = { + all_target_versions[n] for n in target_versions_in + } + if "skip_magic_trailing_comma" in black_config: + mode["magic_trailing_comma"] = not black_config[ + "skip_magic_trailing_comma" + ] + if "skip_string_normalization" in black_config: + # The ``black`` command line argument is + # ``--skip-string-normalization``, but the parameter for + # ``black.Mode`` needs to be the opposite boolean of + # ``skip-string-normalization``, hence the inverse boolean + mode["string_normalization"] = not black_config[ + "skip_string_normalization" + ] + + # The custom handling of empty and all-whitespace files below will be unnecessary if + # https://github.com/psf/black/pull/2484 lands in Black. + contents_for_black = src_contents + return format_str(contents_for_black, mode=Mode(**mode)) @property def _data(self):