src/black/strings.py
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | """
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | Simple formatting on strings. Further string formatting code is in trans.py.
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | """
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | import re
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | import sys
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | from functools import lru_cache
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | from typing import Final, List, Match, Pattern
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | from black._width_table import WIDTH_TABLE
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | from blib2to3.pytree import Leaf
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | STRING_PREFIX_CHARS: Final = "furbFURB" # All possible string prefix characters.
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | STRING_PREFIX_RE: Final = re.compile(
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | r"^([" + STRING_PREFIX_CHARS + r"]*)(.*)$", re.DOTALL
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | )
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | FIRST_NON_WHITESPACE_RE: Final = re.compile(r"\s*\t+\s*(\S)")
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | UNICODE_ESCAPE_RE: Final = re.compile(
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | r"(?P<backslashes>\\+)(?P<body>"
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | r"(u(?P<u>[a-fA-F0-9]{4}))" # Character with 16-bit hex value xxxx
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | r"|(U(?P<U>[a-fA-F0-9]{8}))" # Character with 32-bit hex value xxxxxxxx
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | r"|(x(?P<x>[a-fA-F0-9]{2}))" # Character with hex value hh
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | r"|(N\{(?P<N>[a-zA-Z0-9 \-]{2,})\})" # Character named name in the Unicode database
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | r")",
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | re.VERBOSE,
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | )
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0007 0003 0002 0000 0004 0001 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:
0007 0003 0002 0000 0004 0001 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """Replace `regex` with `replacement` twice on `original`.
0007 0003 0002 0000 0004 0001 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
0007 0003 0002 0000 0004 0001 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | This is used by string normalization to perform replaces on
0007 0003 0002 0000 0004 0001 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | overlapping matches.
0007 0003 0002 0000 0004 0001 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """
0007 0003 0002 0000 0004 0001 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | return regex.sub(replacement, regex.sub(replacement, original))
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0007 0005 0003 0000 0004 0000 0000 -- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | def has_triple_quotes(string: str) -> bool:
0007 0005 0003 0000 0004 0000 0000 -- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | """
0007 0005 0003 0000 0004 0000 0000 -- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | Returns:
0007 0005 0003 0000 0004 0000 0000 -- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | True iff @string starts with three quotation characters.
0007 0005 0003 0000 0004 0000 0000 -- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | """
0007 0005 0003 0000 0004 0000 0000 -- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | raw_string = string.lstrip(STRING_PREFIX_CHARS)
0007 0005 0003 0000 0004 0000 0000 -- 01 001 002 001 002 003 003 0004.75 0002.38 0000.50 | return raw_string[:3] in {'"""', "'''"}
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | def lines_with_leading_tabs_expanded(s: str) -> List[str]:
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | """
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | Splits string into lines and expands only leading tabs (following the normal
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | Python rules)
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | """
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | lines = []
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | for line in s.splitlines():
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | # Find the index of the first non-whitespace character after a string of
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | # whitespace that includes at least one tab
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | match = FIRST_NON_WHITESPACE_RE.match(line)
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | if match:
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | first_non_whitespace_idx = match.start(1)
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 |
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | lines.append(
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | line[:first_non_whitespace_idx].expandtabs()
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | + line[first_non_whitespace_idx:]
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | )
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | else:
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | lines.append(line)
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | if s.endswith("\n"):
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | lines.append("")
0022 0014 0015 0002 0004 0001 0002 -- 04 001 002 001 002 003 003 0004.75 0002.38 0000.50 | return lines
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | def fix_docstring(docstring: str, prefix: str) -> str:
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | # https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | if not docstring:
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | return ""
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | lines = lines_with_leading_tabs_expanded(docstring)
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | # Determine minimum indentation (first line doesn't count):
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | indent = sys.maxsize
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | for line in lines[1:]:
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | stripped = line.lstrip()
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | if stripped:
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | indent = min(indent, len(line) - len(stripped))
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | # Remove indentation (first line is special):
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | trimmed = [lines[0].strip()]
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | if indent < sys.maxsize:
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | last_line_idx = len(lines) - 2
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | for i, line in enumerate(lines[1:]):
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | stripped_line = line[indent:].rstrip()
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | if stripped_line or i == last_line_idx:
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | trimmed.append(prefix + stripped_line)
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | else:
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | trimmed.append("")
0022 0020 0019 0003 0000 0000 0003 -- 08 006 012 007 013 018 020 0083.40 0271.05 0003.25 | return "\n".join(trimmed)
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | def get_string_prefix(string: str) -> str:
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | """
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | Pre-conditions:
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | * assert_is_leaf_string(@string)
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 |
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | Returns:
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | @string's prefix (e.g. '', 'r', 'f', or 'rf').
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | """
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | assert_is_leaf_string(string)
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 |
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | prefix = ""
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | prefix_idx = 0
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | while string[prefix_idx] in STRING_PREFIX_CHARS:
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | prefix += string[prefix_idx]
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | prefix_idx += 1
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 |
0017 0009 0008 0000 0006 0003 0000 -- 02 002 006 003 006 008 009 0027.00 0027.00 0001.00 | return prefix
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0033 0012 0017 0000 0013 0003 0000 -- 02 005 009 007 011 014 018 0068.53 0209.40 0003.06 | def assert_is_leaf_string(string: str) -> None:
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | """
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | Checks the pre-condition that @string has the format that you would expect
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | of `leaf.value` where `leaf` is some Leaf such that `leaf.type ==
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | token.STRING`. A more precise description of the pre-conditions that are
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | checked are listed below.
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 |
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | Pre-conditions:
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | * @string starts with either ', ", <prefix>', or <prefix>" where
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | `set(<prefix>)` is some subset of `set(STRING_PREFIX_CHARS)`.
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | * @string ends with a quote character (' or ").
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 |
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | Raises:
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | AssertionError(...) if the pre-conditions listed above are not
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | satisfied.
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | """
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | dquote_idx = string.find('"')
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | squote_idx = string.find("'")
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | if -1 in [dquote_idx, squote_idx]:
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | quote_idx = max(dquote_idx, squote_idx)
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | else:
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | quote_idx = min(squote_idx, dquote_idx)
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 |
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | assert (
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | 0 <= quote_idx < len(string) - 1
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | ), f"{string!r} is missing a starting quote character (' or \")."
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | assert string[-1] in (
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | "'",
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | '"',
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | ), f"{string!r} is missing an ending quote character (' or \")."
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | assert set(string[:quote_idx]).issubset(
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | set(STRING_PREFIX_CHARS)
0033 0012 0017 0000 0013 0003 0000 -- -- 005 009 007 011 014 018 0068.53 0209.40 0003.06 | ), f"{set(string[:quote_idx])} is NOT a subset of {set(STRING_PREFIX_CHARS)}."
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | def normalize_string_prefix(s: str) -> str:
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | """Make all string prefixes lowercase."""
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | match = STRING_PREFIX_RE.match(s)
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | assert match is not None, f"failed to match string {s!r}"
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | orig_prefix = match.group(1)
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | new_prefix = (
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | orig_prefix.replace("F", "f")
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | .replace("B", "b")
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | .replace("U", "")
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | .replace("u", "")
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | )
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 |
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | # Python syntax guarantees max 2 prefixes and that one of them is "r"
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | if len(new_prefix) == 2 and "r" != new_prefix[0].lower():
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | new_prefix = new_prefix[::-1]
0021 0010 0013 0004 0000 0003 0005 -- 03 005 009 005 009 014 014 0053.30 0133.26 0002.50 | return f"{new_prefix}{match.group(2)}"
0021 0010 0013 0004 0000 0003 0005 -- -- --- --- --- --- --- --- ------- ------- ------- |
0021 0010 0013 0004 0000 0003 0005 -- -- --- --- --- --- --- --- ------- ------- ------- |
0021 0010 0013 0004 0000 0003 0005 -- -- --- --- --- --- --- --- ------- ------- ------- | # Re(gex) does actually cache patterns internally but this still improves
0021 0010 0013 0004 0000 0003 0005 -- -- --- --- --- --- --- --- ------- ------- ------- | # performance on a long list literal of strings by 5-9% since lru_cache's
0021 0010 0013 0004 0000 0003 0005 -- -- --- --- --- --- --- --- ------- ------- ------- | # caching overhead is much lower.
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | @lru_cache(maxsize=64)
0003 0003 0003 0000 0000 0000 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def _cached_compile(pattern: str) -> Pattern[str]:
0003 0003 0003 0000 0000 0000 0000 -- 01 000 000 000 000 000 000 0000.00 0000.00 0000.00 | return re.compile(pattern)
0003 0003 0003 0000 0000 0000 0000 -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | def normalize_string_quotes(s: str) -> str:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | """Prefer double quotes but only if it doesn't cause more escaping.
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | Adds or removes backslashes as appropriate. Doesn't parse and fix
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | strings nested in f-strings.
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | """
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | value = s.lstrip(STRING_PREFIX_CHARS)
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if value[:3] == '"""':
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | return s
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | elif value[:3] == "'''":
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | orig_quote = "'''"
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_quote = '"""'
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | elif value[0] == '"':
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | orig_quote = '"'
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_quote = "'"
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | else:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | orig_quote = "'"
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_quote = '"'
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | first_quote_pos = s.find(orig_quote)
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if first_quote_pos == -1:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | return s # There's an internal error
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | prefix = s[:first_quote_pos]
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | unescaped_new_quote = _cached_compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | escaped_new_quote = _cached_compile(rf"([^\\]|^)\\((?:\\\\)*){new_quote}")
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | escaped_orig_quote = _cached_compile(rf"([^\\]|^)\\((?:\\\\)*){orig_quote}")
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | body = s[first_quote_pos + len(orig_quote) : -len(orig_quote)]
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if "r" in prefix.casefold():
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if unescaped_new_quote.search(body):
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | # There's at least one unescaped new_quote in this raw string
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | # so converting is impossible
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | return s
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | # Do not introduce or remove backslashes in raw strings
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_body = body
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | else:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | # remove unnecessary escapes
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_body = sub_twice(escaped_new_quote, rf"\1\2{new_quote}", body)
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if body != new_body:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | # Consider the string without unnecessary escapes as the original
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | body = new_body
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | s = f"{prefix}{orig_quote}{body}{orig_quote}"
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_body = sub_twice(escaped_orig_quote, rf"\1\2{orig_quote}", new_body)
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_body = sub_twice(unescaped_new_quote, rf"\1\\{new_quote}", new_body)
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if "f" in prefix.casefold():
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | matches = re.findall(
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | r"""
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | (?:(?<!\{)|^)\{ # start of the string or a non-{ followed by a single {
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | ([^{].*?) # contents of the brackets except if begins with {{
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | \}(?:(?!\})|$) # A } followed by end of the string or a non-}
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | """,
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_body,
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | re.VERBOSE,
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | )
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | for m in matches:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if "\\" in str(m):
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | # Do not introduce backslashes in interpolated expressions
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | return s
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if new_quote == '"""' and new_body[-1:] == '"':
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | # edge case:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_body = new_body[:-1] + '\\"'
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | orig_escape_count = body.count("\\")
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | new_escape_count = new_body.count("\\")
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if new_escape_count > orig_escape_count:
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | return s # Do not introduce more escaping
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | if new_escape_count == orig_escape_count and orig_quote == '"':
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | return s # Prefer double quotes
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 |
0072 0050 0054 0010 0004 0007 0007 -- 16 007 030 021 038 037 059 0307.36 1362.62 0004.43 | return f"{prefix}{new_quote}{new_body}{new_quote}"
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | def normalize_unicode_escape_sequences(leaf: Leaf) -> None:
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | """Replace hex codes in Unicode escape sequences with lowercase representation."""
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | text = leaf.value
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | prefix = get_string_prefix(text)
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | if "r" in prefix.lower():
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | return
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 |
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | def replace(m: Match[str]) -> str:
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | groups = m.groupdict()
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | back_slashes = groups["backslashes"]
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 |
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | if len(back_slashes) % 2 == 0:
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | return back_slashes + groups["body"]
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 |
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | if groups["u"]:
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | # \u
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | return back_slashes + "u" + groups["u"].lower()
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | elif groups["U"]:
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | # \U
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | return back_slashes + "U" + groups["U"].lower()
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | elif groups["x"]:
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | # \x
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | return back_slashes + "x" + groups["x"].lower()
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | else:
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | assert groups["N"], f"Unexpected match: {m}"
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | # \N{}
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | return back_slashes + "N{" + groups["N"].upper() + "}"
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 |
0029 0021 0020 0004 0000 0004 0005 -- 02 004 022 013 026 026 039 0183.32 0433.30 0002.36 | leaf.value = re.sub(UNICODE_ESCAPE_RE, replace, text)
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- | @lru_cache(maxsize=4096)
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | def char_width(char: str) -> int:
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | """Return the width of a single character as it would be displayed in a
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | terminal or editor (which respects Unicode East Asian Width).
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 |
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | Full width characters are counted as 2, while half width characters are
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | counted as 1. Also control characters are counted as 0.
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | """
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | table = WIDTH_TABLE
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | codepoint = ord(char)
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | highest = len(table) - 1
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | lowest = 0
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | idx = highest // 2
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | while True:
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | start_codepoint, end_codepoint, width = table[idx]
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | if codepoint < start_codepoint:
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | highest = idx - 1
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | elif codepoint > end_codepoint:
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | lowest = idx + 1
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | else:
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | return 0 if width < 0 else width
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | if highest < lowest:
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | break
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | idx = (highest + lowest) // 2
0025 0020 0019 0000 0005 0001 0000 -- 06 005 012 010 020 017 030 0122.62 0510.93 0004.17 | return 1
0025 0020 0019 0000 0005 0001 0000 -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | def str_width(line_str: str) -> int:
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """Return the width of `line_str` as it would be displayed in a terminal
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | or editor (which respects Unicode East Asian Width).
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 |
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | You could utilize this function to determine, for example, if a string
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | is too wide to display in a terminal or editor.
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | """
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | if line_str.isascii():
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | # Fast path for a line consisting of only ASCII characters
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | return len(line_str)
0011 0005 0004 0001 0005 0001 0001 -- 02 000 000 000 000 000 000 0000.00 0000.00 0000.00 | return sum(map(char_width, line_str))
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
---- ---- ---- ---- ---- ---- ---- -- -- --- --- --- --- --- --- ------- ------- ------- |
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | def count_chars_in_width(line_str: str, max_width: int) -> int:
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | """Count the number of characters in `line_str` that would fit in a
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | terminal or editor of `max_width` (which respects Unicode East Asian
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | Width).
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | """
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | total_width = 0
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | for i, char in enumerate(line_str):
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | width = char_width(char)
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | if width + total_width > max_width:
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | return i
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | total_width += width
0012 0009 0008 0000 0004 0000 0000 -- 03 002 004 003 006 006 009 0023.26 0034.90 0001.50 | return len(line_str)