add updates libs
This commit is contained in:
+195
-74
@@ -1,53 +1,56 @@
|
||||
import dataclasses
|
||||
import itertools
|
||||
import textwrap
|
||||
from typing import List, Tuple, Union
|
||||
import sys
|
||||
|
||||
from tclint.syntax_tree import (
|
||||
Node,
|
||||
Script,
|
||||
Command,
|
||||
Comment,
|
||||
CommandSub,
|
||||
BareWord,
|
||||
QuotedWord,
|
||||
BracedWord,
|
||||
CompoundBareWord,
|
||||
VarSub,
|
||||
ArgExpansion,
|
||||
Expression,
|
||||
BracedExpression,
|
||||
ParenExpression,
|
||||
UnaryOp,
|
||||
BinaryOp,
|
||||
TernaryOp,
|
||||
Function,
|
||||
)
|
||||
from tclint.parser import Parser
|
||||
from tclint.syntax_tree import List as ListNode
|
||||
from tclint.syntax_tree import (
|
||||
ArgExpansion,
|
||||
BareWord,
|
||||
BinaryOp,
|
||||
BracedExpression,
|
||||
BracedWord,
|
||||
Command,
|
||||
CommandSub,
|
||||
Comment,
|
||||
CompoundBareWord,
|
||||
Expression,
|
||||
Function,
|
||||
List,
|
||||
Node,
|
||||
ParenExpression,
|
||||
QuotedWord,
|
||||
Script,
|
||||
TernaryOp,
|
||||
UnaryOp,
|
||||
VarSub,
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LiteralBlock:
|
||||
block: List[str]
|
||||
pos: Tuple[int, int]
|
||||
end_pos: Tuple[int, int]
|
||||
block: list[str]
|
||||
pos: tuple[int, int]
|
||||
end_pos: tuple[int, int]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class FormatterOpts:
|
||||
indent: str
|
||||
spaces_in_braces: bool
|
||||
balanced_spaces_in_braces: bool
|
||||
max_blank_lines: int
|
||||
indent_namespace_eval: bool
|
||||
indent_mixed_tab_size: int
|
||||
emacs: bool
|
||||
debug_whitespace: bool
|
||||
|
||||
|
||||
class Formatter:
|
||||
def __init__(self, opts: FormatterOpts):
|
||||
self.opts = opts
|
||||
self.indent_mixed_tab_size = opts.indent_mixed_tab_size
|
||||
|
||||
def _indent(self, lines: List[str], indent: str) -> List[str]:
|
||||
def _indent(self, lines: list[str], indent: str) -> list[str]:
|
||||
indented = []
|
||||
for line in lines:
|
||||
if line == "":
|
||||
@@ -57,17 +60,70 @@ class Formatter:
|
||||
|
||||
return indented
|
||||
|
||||
def _brace(self, lines: List[str]) -> List[str]:
|
||||
spaces_in_braces = " " if self.opts.spaces_in_braces else ""
|
||||
def space(self, debug_char, space=None):
|
||||
"""Returns a string required for indentation or separation.
|
||||
|
||||
By default, just returns a string of space. If enabled using
|
||||
`--debug-whitespace`, returns a string of debug_char.
|
||||
"""
|
||||
assert len(debug_char) == 1
|
||||
if space is None:
|
||||
space = self.opts.indent
|
||||
if self.opts.debug_whitespace:
|
||||
# Enable this to return a string of debug_char.
|
||||
return len(space) * debug_char
|
||||
return space
|
||||
|
||||
def get_spaces_in_braces(self, space: tuple[int, int]):
|
||||
spaces_in_braces = self.space("A", " ") if self.opts.spaces_in_braces else ""
|
||||
if not self.opts.balanced_spaces_in_braces:
|
||||
# No balancing.
|
||||
return spaces_in_braces
|
||||
|
||||
if space[0] == -1 and space[1] == -1:
|
||||
# No info to do balancing.
|
||||
return spaces_in_braces
|
||||
|
||||
assert not (space[0] == -1 and space[1] != -1)
|
||||
if space[0] != -1 and space[1] == -1:
|
||||
# we've got empty braces. Keep "{}" and "{ }" as is, but normalize
|
||||
# more than one space to a single space.
|
||||
return min(space[0], 1) * self.space("B", " ")
|
||||
|
||||
# Normalize more than one space to a single space.
|
||||
before = min(space[0], 1)
|
||||
after = min(space[1], 1)
|
||||
if before + after == 1:
|
||||
# If we have an unbalanced expression like "{1 }" or "{ 1}",
|
||||
# transform it to either "{1}" or "{ 1 }", using spaces_in_braces.
|
||||
return spaces_in_braces
|
||||
|
||||
# Check that we have a balanced expression.
|
||||
assert before == after
|
||||
# Keep either "{1}" or "{ 1 }".
|
||||
return before * self.space("C", " ")
|
||||
|
||||
def _brace(self, lines: list[str], space: tuple[int, int]) -> list[str]:
|
||||
"""Format content between braces.
|
||||
|
||||
The space argument indicates the amount of space in the input, for
|
||||
instance:
|
||||
- (1, 0) to represent 1 space before and no space after, for "{ 1}", and
|
||||
- (0, -1) to represent no space, for "{}".
|
||||
"""
|
||||
|
||||
spaces_in_braces = self.get_spaces_in_braces(space)
|
||||
if lines == [""]:
|
||||
# Empty braces.
|
||||
return ["{" + spaces_in_braces + "}"]
|
||||
|
||||
# Not empty braces.
|
||||
braced_lines = lines[:]
|
||||
braced_lines[0] = "{" + spaces_in_braces + lines[0]
|
||||
braced_lines[-1] += spaces_in_braces + "}"
|
||||
return braced_lines
|
||||
|
||||
def format(self, *nodes: Union[Node, LiteralBlock]) -> List[str]:
|
||||
def format(self, *nodes: Node | LiteralBlock) -> list[str]:
|
||||
formatted = []
|
||||
for node in nodes:
|
||||
if isinstance(node, Script):
|
||||
@@ -90,7 +146,7 @@ class Formatter:
|
||||
formatted += self.format_var_sub(node)
|
||||
elif isinstance(node, ArgExpansion):
|
||||
formatted += self.format_arg_expansion(node)
|
||||
elif isinstance(node, ListNode):
|
||||
elif isinstance(node, List):
|
||||
formatted += self.format_list(node)
|
||||
elif isinstance(node, Expression):
|
||||
formatted += self.format_expression(node)
|
||||
@@ -113,10 +169,47 @@ class Formatter:
|
||||
|
||||
return formatted
|
||||
|
||||
def reindent(self, lines: list[str]) -> list[str]:
|
||||
"""Apply mixed space/tab indentation scheme.
|
||||
|
||||
Apply the mixed space/tab indentation scheme as requested by
|
||||
--indent=mixed,<s>,<t>.
|
||||
|
||||
The input is lines with indentation in the form of spaces and/or tabs.
|
||||
This function transforms the indentation into a number of tabs,
|
||||
followed by a number of spaces.
|
||||
|
||||
A more structural way of doing this would be to model input lines as a
|
||||
tuple of an indentation level and a string, and use this function to
|
||||
expand the indentation level, but that requires broader changes.
|
||||
"""
|
||||
tab_size = self.indent_mixed_tab_size
|
||||
if tab_size == 0:
|
||||
return lines
|
||||
|
||||
fixed_lines = []
|
||||
for line in lines:
|
||||
# Split line into leading whitespace, and the rest.
|
||||
after = line.lstrip()
|
||||
split_pos = len(line) - len(after)
|
||||
leading = line[0:split_pos]
|
||||
|
||||
# Expand tabs.
|
||||
leading = leading.expandtabs(tab_size)
|
||||
|
||||
# Tabify.
|
||||
leading = leading.replace(" " * tab_size, "\t")
|
||||
|
||||
fixed_lines.append(leading + after)
|
||||
|
||||
return fixed_lines
|
||||
|
||||
def format_top(self, script: str, parser: Parser) -> str:
|
||||
tree = parser.parse(script)
|
||||
self.script = script.split("\n")
|
||||
return "\n".join(self.format_script_contents(tree)) + "\n"
|
||||
lines = self.format_script_contents(tree)
|
||||
lines = self.reindent(lines)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
def format_partial(self, script: str, parser: Parser) -> str:
|
||||
"""Formats a partial Tcl script.
|
||||
@@ -139,11 +232,13 @@ class Formatter:
|
||||
tree = parser.parse(script)
|
||||
self.script = script.split("\n")
|
||||
|
||||
formatted = "\n".join(self.format_script_contents(tree))
|
||||
lines = self._indent(self.format_script_contents(tree), indent)
|
||||
lines = self.reindent(lines)
|
||||
formatted = "\n".join(lines)
|
||||
|
||||
return leading + textwrap.indent(formatted, indent) + trailing
|
||||
return leading + formatted + trailing
|
||||
|
||||
def format_script_contents(self, script: Union[Script, CommandSub]) -> List[str]:
|
||||
def format_script_contents(self, script: Script | CommandSub) -> list[str]:
|
||||
to_format = []
|
||||
skip_formatting_start = None
|
||||
for child in script.children:
|
||||
@@ -210,10 +305,17 @@ class Formatter:
|
||||
|
||||
return formatted
|
||||
|
||||
def format_script(self, script: Script, should_indent=True) -> List[str]:
|
||||
def format_script(self, script: Script, should_indent=True) -> list[str]:
|
||||
lines = self.format_script_contents(script)
|
||||
if script.pos[0] == script.end_pos[0]:
|
||||
return self._brace(lines)
|
||||
space_before = -1
|
||||
space_after = -1
|
||||
if len(script.children) != 0:
|
||||
space_before = script.children[0].pos[1] - script.pos[1] - 1
|
||||
space_after = script.end_pos[1] - script.children[-1].end_pos[1] - 1
|
||||
else:
|
||||
space_before = script.end_pos[1] - script.pos[1] - 2
|
||||
return self._brace(lines, (space_before, space_after))
|
||||
|
||||
# Usually, we enforce that multi-line scripts start on a new line after the open
|
||||
# brace. However, if a comment was originally on the same line as the open brace
|
||||
@@ -225,15 +327,15 @@ class Formatter:
|
||||
and isinstance(script.children[0], Comment)
|
||||
and script.pos[0] == script.children[0].pos[0]
|
||||
):
|
||||
open_brace += " " + lines[0]
|
||||
open_brace += self.space("D", " ") + lines[0]
|
||||
lines = lines[1:]
|
||||
|
||||
if should_indent:
|
||||
return [open_brace] + self._indent(lines, self.opts.indent) + ["}"]
|
||||
return [open_brace] + self._indent(lines, self.space("E")) + ["}"]
|
||||
else:
|
||||
return [open_brace] + lines + ["}"]
|
||||
|
||||
def format_command(self, command: Command) -> List[str]:
|
||||
def format_command(self, command: Command) -> list[str]:
|
||||
is_namespace_eval = (
|
||||
command.routine.contents == "namespace"
|
||||
and len(command.args) > 0
|
||||
@@ -251,23 +353,27 @@ class Formatter:
|
||||
child_lines = self.format(child)
|
||||
|
||||
if last_line == child.pos[0]:
|
||||
formatted[-1] += " "
|
||||
formatted[-1] += self.space("F", " ")
|
||||
if self.opts.emacs and child_lines[0][-1] == "\\":
|
||||
base_indent = (len(formatted[-1])) * self.space("G", " ")
|
||||
else:
|
||||
base_indent = ""
|
||||
formatted[-1] += child_lines[0]
|
||||
else:
|
||||
formatted[-1] += " \\"
|
||||
formatted.append(self.opts.indent + child_lines[0])
|
||||
formatted[-1] += self.space("H", " ") + "\\"
|
||||
formatted.append(self.space("I") + child_lines[0])
|
||||
hanging_indent = True
|
||||
|
||||
if hanging_indent:
|
||||
formatted.extend(self._indent(child_lines[1:], self.opts.indent))
|
||||
formatted.extend(self._indent(child_lines[1:], self.space("J")))
|
||||
else:
|
||||
formatted.extend(child_lines[1:])
|
||||
formatted.extend(self._indent(child_lines[1:], base_indent))
|
||||
|
||||
last_line = child.end_pos[0]
|
||||
|
||||
return formatted
|
||||
|
||||
def format_comment(self, comment: Comment) -> List[str]:
|
||||
def format_comment(self, comment: Comment) -> list[str]:
|
||||
return [f"#{comment.value}"]
|
||||
|
||||
def format_command_sub(self, command_sub):
|
||||
@@ -278,21 +384,25 @@ class Formatter:
|
||||
contents = self.format_script_contents(command_sub)
|
||||
if len(command_sub.children) > 1 and len(contents) > 1:
|
||||
formatted.append("[")
|
||||
formatted.extend(self._indent(contents, self.opts.indent))
|
||||
formatted.extend(self._indent(contents, self.space("K")))
|
||||
formatted.append("]")
|
||||
else:
|
||||
formatted.append("[" + contents[0])
|
||||
formatted.extend(contents[1:])
|
||||
if self.opts.emacs:
|
||||
indent = self.space("L", " ")
|
||||
else:
|
||||
indent = ""
|
||||
formatted.extend(self._indent(contents[1:], indent))
|
||||
formatted[-1] += "]"
|
||||
|
||||
return formatted
|
||||
|
||||
def format_bare_word(self, word) -> List[str]:
|
||||
def format_bare_word(self, word) -> list[str]:
|
||||
# Property enforced by parser
|
||||
assert word.contents is not None
|
||||
return [word.contents]
|
||||
|
||||
def format_quoted_word(self, word) -> List[str]:
|
||||
def format_quoted_word(self, word) -> list[str]:
|
||||
if word.contents is not None:
|
||||
return [f'"{word.contents}"']
|
||||
|
||||
@@ -302,11 +412,11 @@ class Formatter:
|
||||
|
||||
return [f'"{formatted}"']
|
||||
|
||||
def format_braced_word(self, word) -> List[str]:
|
||||
def format_braced_word(self, word) -> list[str]:
|
||||
assert word.contents is not None
|
||||
return [f"{{{word.contents}}}"]
|
||||
|
||||
def format_compound_bare_word(self, word) -> List[str]:
|
||||
def format_compound_bare_word(self, word) -> list[str]:
|
||||
formatted = [""]
|
||||
for child in word.children:
|
||||
child_lines = self.format(child)
|
||||
@@ -315,7 +425,7 @@ class Formatter:
|
||||
|
||||
return formatted
|
||||
|
||||
def format_var_sub(self, varsub) -> List[str]:
|
||||
def format_var_sub(self, varsub) -> list[str]:
|
||||
# We might be able to make the formatter infer whether braces are required, and
|
||||
# remove them from the syntax tree. For now it's easier to just mimic the
|
||||
# original format.
|
||||
@@ -337,20 +447,20 @@ class Formatter:
|
||||
|
||||
return formatted
|
||||
|
||||
def format_arg_expansion(self, arg_expansion) -> List[str]:
|
||||
def format_arg_expansion(self, arg_expansion) -> list[str]:
|
||||
lines = self.format(arg_expansion.list)
|
||||
lines[0] = "{*}" + lines[0]
|
||||
|
||||
return lines
|
||||
|
||||
def format_list(self, list_node) -> List[str]:
|
||||
def format_list(self, list_node) -> list[str]:
|
||||
# Similar to Script, but the contents are a bit more straightforward.
|
||||
contents = [""]
|
||||
last_line = None
|
||||
for child in list_node.children:
|
||||
if last_line is not None:
|
||||
if last_line == child.pos[0]:
|
||||
contents[-1] += " "
|
||||
contents[-1] += self.space("M", " ")
|
||||
else:
|
||||
newlines = child.pos[0] - last_line
|
||||
newlines = min(newlines, 3)
|
||||
@@ -363,18 +473,27 @@ class Formatter:
|
||||
last_line = child.end_pos[0]
|
||||
|
||||
if list_node.pos[0] == list_node.end_pos[0]:
|
||||
return self._brace(contents)
|
||||
space_before = -1
|
||||
space_after = -1
|
||||
if len(list_node.children) != 0:
|
||||
space_before = list_node.children[0].pos[1] - list_node.pos[1] - 1
|
||||
space_after = (
|
||||
list_node.end_pos[1] - list_node.children[-1].end_pos[1] - 1
|
||||
)
|
||||
else:
|
||||
space_before = list_node.end_pos[1] - list_node.pos[1] - 2
|
||||
return self._brace(contents, (space_before, space_after))
|
||||
|
||||
return ["{"] + self._indent(contents, self.opts.indent) + ["}"]
|
||||
return ["{"] + self._indent(contents, self.space("N")) + ["}"]
|
||||
|
||||
def format_expression(self, expr) -> List[str]:
|
||||
def format_expression(self, expr) -> list[str]:
|
||||
formatted = [""]
|
||||
for child in expr.children:
|
||||
lines = self.format(child)
|
||||
formatted[-1] += lines[0]
|
||||
for line in lines[1:]:
|
||||
formatted[-1] += " \\"
|
||||
formatted += self._indent([line], self.opts.indent)
|
||||
formatted += self._indent([line], self.space("O"))
|
||||
|
||||
# Trick: we know there are quotes around the expression if the start of the
|
||||
# expression is a different column than its first child.
|
||||
@@ -385,7 +504,7 @@ class Formatter:
|
||||
|
||||
return formatted
|
||||
|
||||
def format_braced_expression(self, expr) -> List[str]:
|
||||
def format_braced_expression(self, expr) -> list[str]:
|
||||
formatted = [""]
|
||||
for child in expr.children:
|
||||
lines = self.format(child)
|
||||
@@ -393,11 +512,13 @@ class Formatter:
|
||||
formatted.extend(lines[1:])
|
||||
|
||||
if expr.pos[0] == expr.end_pos[0]:
|
||||
return self._brace(formatted)
|
||||
space_before = expr.children[0].pos[1] - expr.pos[1] - 1
|
||||
space_after = expr.end_pos[1] - expr.children[-1].end_pos[1] - 1
|
||||
return self._brace(formatted, (space_before, space_after))
|
||||
|
||||
return ["{"] + self._indent(formatted, self.opts.indent) + ["}"]
|
||||
return ["{"] + self._indent(formatted, self.space("P")) + ["}"]
|
||||
|
||||
def format_paren_expression(self, expr) -> List[str]:
|
||||
def format_paren_expression(self, expr) -> list[str]:
|
||||
body = expr.body
|
||||
|
||||
formatted = ["("]
|
||||
@@ -408,7 +529,7 @@ class Formatter:
|
||||
formatted[-1] += lines[0]
|
||||
formatted.extend(lines[1:])
|
||||
|
||||
formatted = formatted[0:1] + self._indent(formatted[1:], self.opts.indent)
|
||||
formatted = formatted[0:1] + self._indent(formatted[1:], self.space("Q"))
|
||||
|
||||
if expr.end_pos[0] != body.end_pos[0]:
|
||||
formatted.append(")")
|
||||
@@ -425,7 +546,7 @@ class Formatter:
|
||||
lines[0] = op[0] + lines[0]
|
||||
return lines
|
||||
|
||||
def _format_op(self, expr) -> List[str]:
|
||||
def _format_op(self, expr) -> list[str]:
|
||||
nodes = expr.children
|
||||
formatted = self.format(nodes[0])
|
||||
|
||||
@@ -435,23 +556,23 @@ class Formatter:
|
||||
if last.end_pos[0] != next.pos[0]:
|
||||
formatted.extend(lines)
|
||||
else:
|
||||
formatted[-1] += " "
|
||||
formatted[-1] += self.space("R", " ")
|
||||
formatted[-1] += lines[0]
|
||||
formatted.extend(lines[1:])
|
||||
last = next
|
||||
|
||||
return formatted
|
||||
|
||||
def format_binary_op(self, expr) -> List[str]:
|
||||
def format_binary_op(self, expr) -> list[str]:
|
||||
return self._format_op(expr)
|
||||
|
||||
def format_ternary_op(self, expr) -> List[str]:
|
||||
def format_ternary_op(self, expr) -> list[str]:
|
||||
return self._format_op(expr)
|
||||
|
||||
def format_function(self, function):
|
||||
name = self.format(function.name)
|
||||
assert len(name) == 1
|
||||
name = name[0]
|
||||
name_parts = self.format(function.name)
|
||||
assert len(name_parts) == 1
|
||||
name = name_parts[0]
|
||||
|
||||
formatted = [f"{name}("]
|
||||
|
||||
@@ -464,13 +585,13 @@ class Formatter:
|
||||
formatted.extend(lines)
|
||||
else:
|
||||
if i > 0:
|
||||
formatted[-1] += " "
|
||||
formatted[-1] += self.space("S", " ")
|
||||
formatted[-1] += lines[0]
|
||||
formatted.extend(lines[1:])
|
||||
last = child
|
||||
|
||||
# indent any continuation lines, but we leave the closing paren dedented
|
||||
formatted = formatted[0:1] + self._indent(formatted[1:], self.opts.indent)
|
||||
formatted = formatted[0:1] + self._indent(formatted[1:], self.space("T"))
|
||||
|
||||
if last.end_pos[0] != function.end_pos[0]:
|
||||
formatted.append(")")
|
||||
|
||||
Reference in New Issue
Block a user