Use cmakelang formatter (#52)

This commit is contained in:
Regen
2022-05-01 23:43:22 +09:00
committed by GitHub
parent f0cfa8b13f
commit 1fefcb4cba
8 changed files with 66 additions and 512 deletions

View File

@@ -1,160 +0,0 @@
from typing import List, Optional
from .parser import TokenList
class Formatter(object):
indent: str
lower_identifier: bool
def __init__(self, indent: str = " ", lower_identifier: bool = True):
self.indent = indent
self.lower_identifier = lower_identifier
def format(self, tokens: TokenList) -> str:
cmds: List[str] = [""]
indent_level = 0
for token in tokens:
if isinstance(token, tuple):
raw_identifier = token[0]
identifier = raw_identifier.lower()
if identifier in (
"elseif",
"else",
"endif",
"endforeach",
"endwhile",
"endmacro",
"endfunction",
):
if indent_level > 0:
indent_level -= 1
cmds[-1] = self.indent * indent_level
cmds[-1] += identifier if self.lower_identifier else raw_identifier
args = self._format_args(token[1])
if len(args) < 2:
cmds[-1] += "(" + "".join(args) + ")"
else:
cmds[-1] += "(\n"
for arg in args:
cmds[-1] += self.indent * (indent_level + 1) + arg + "\n"
cmds[-1] += self.indent * indent_level + ")"
if identifier in (
"if",
"elseif",
"else",
"foreach",
"while",
"macro",
"function",
):
indent_level += 1
elif token == "\n":
cmds.append("")
elif token[0] == "#":
if cmds[-1]:
cmds[-1] += token
else:
cmds[-1] = self.indent * indent_level + token
elif cmds[-1]:
cmds[-1] += token
cmds = self._strip_line(cmds)
return "\n".join(cmds) + "\n"
def _format_args(self, args: List[str]) -> List[str]:
lines = [""]
for i in range(len(args)):
arg = args[i]
if arg[0] == "#":
lines[-1] += arg
elif arg[0] == "\n":
lines.append("")
elif arg.isspace():
if lines[-1]:
if i + 1 < len(args) and args[i + 1][0] == "#":
lines[-1] += arg
else:
lines[-1] += " "
else:
lines[-1] += arg
return self._strip_line(lines)
def _strip_line(self, lines: List[str]) -> List[str]:
"""Delete empty lines at the start/end of the input"""
ret: List[str] = []
for line in lines:
line = line.rstrip()
if line != "" or len(ret) > 0:
ret.append(line)
while ret and ret[-1] == "":
del ret[-1]
return ret
def main(argss: Optional[List[str]] = None) -> None:
import sys
from argparse import ArgumentParser
from difflib import unified_diff
from pathlib import Path
from . import __version__
from .parser import ListParser
parser = ArgumentParser(
description="Format CMake list files.",
epilog="""
If no arguments are specified, it formats the code from
standard input and writes the result to the standard output.""",
)
parser.add_argument("lists", type=Path, nargs="*", help="CMake list files")
group = parser.add_mutually_exclusive_group()
group.add_argument("-i", "--inplace", action="store_true", help="inplace edit")
group.add_argument("-d", "--diff", action="store_true", help="show diff")
parser.add_argument(
"--version", action="version", version=f"%(prog)s {__version__}"
)
args = parser.parse_args(argss)
if not args.lists and args.inplace:
print("error: cannot use -i when no arguments are specified.", file=sys.stderr)
return
if not args.lists:
args.lists.append(None)
list_parser = ListParser()
formatter = Formatter()
for listpath in args.lists:
if listpath is None:
listpath = "(stdin)"
content = sys.stdin.read()
else:
with listpath.open() as fp:
content = fp.read()
tokens, remain = list_parser.parse(content)
formatted = content if remain else formatter.format(tokens)
if args.inplace:
if not remain:
with listpath.open("w") as fp:
fp.write(formatted)
elif args.diff:
diff = unified_diff(
content.splitlines(True),
formatted.splitlines(True),
str(listpath),
str(listpath),
"(before formatting)",
"(after formatting)",
)
diffstr = "".join(diff)
print(diffstr, end="")
else:
print(formatted, end="")
if __name__ == "__main__":
main()

View File

@@ -1,70 +0,0 @@
from typing import List, Tuple, Union
import pyparsing as pp
CommandTokenType = Tuple[str, List[str]]
TokenType = Union[str, CommandTokenType]
TokenList = List[TokenType]
class ListParser(object):
_parser: pp.ParserElement
def __init__(self) -> None:
newline = "\n"
space_plus = pp.Regex("[ \t]+")
space_star = pp.Optional(space_plus)
quoted_element = pp.Regex(r'[^\\"]|\\[^A-Za-z0-9]|\\[trn]')
quoted_argument = pp.Combine('"' + pp.ZeroOrMore(quoted_element) + '"')
bracket_content = pp.Forward()
def action_bracket_open(tokens: pp.ParseResults) -> None:
nonlocal bracket_content
marker = "]" + "=" * (len(tokens[0]) - 2) + "]"
bracket_content <<= pp.SkipTo(marker, include=True)
bracket_open = pp.Regex(r"\[=*\[").setParseAction(action_bracket_open)
bracket_argument = pp.Combine(bracket_open + bracket_content)
unquoted_element = pp.Regex(r'[^\s()#"\\]|\\[^A-Za-z0-9]|\\[trn]')
unquoted_argument = pp.Combine(pp.OneOrMore(unquoted_element))
argument = bracket_argument | quoted_argument | unquoted_argument
line_comment = pp.Combine("#" + ~bracket_open + pp.SkipTo(pp.LineEnd()))
bracket_comment = pp.Combine("#" + bracket_argument)
line_ending = (
space_star
+ pp.ZeroOrMore(bracket_comment + space_star)
+ pp.Optional(line_comment)
+ (newline | pp.lineEnd)
)
identifier = pp.Word(pp.alphas + "_", pp.alphanums + "_")
arguments = pp.Forward()
(
arguments
<< pp.ZeroOrMore(
argument | line_ending | space_plus | "(" + arguments + ")"
).leaveWhitespace()
)
arguments = pp.Group(arguments)
PAREN_L, PAREN_R = map(pp.Suppress, "()")
command_invocation = (
identifier + space_star.suppress() + PAREN_L + arguments + PAREN_R
).setParseAction(lambda t: (t[0], t[1].asList()))
file_element = (
space_star + command_invocation + line_ending | line_ending
).leaveWhitespace()
file = pp.ZeroOrMore(file_element)
self._parser = file
def parse(self, liststr: str) -> Tuple[TokenList, str]:
for t, s, e in self._parser.scanString(liststr, maxMatches=1):
if s == 0:
return t.asList(), liststr[e:]
return [], liststr

View File

@@ -1,5 +1,7 @@
import logging
import re
import shutil
import subprocess
from pathlib import Path
from typing import Any, Callable, List, Optional, Tuple
@@ -32,20 +34,16 @@ from pygls.lsp.types import (
from pygls.server import LanguageServer
from .api import API
from .formatter import Formatter
from .parser import ListParser
logger = logging.getLogger(__name__)
class CMakeLanguageServer(LanguageServer):
_parser: ListParser
_api: Optional[API]
def __init__(self, *args: Any) -> None:
super().__init__(*args)
self._parser = ListParser()
self._api = None
@self.feature(INITIALIZE)
@@ -151,26 +149,30 @@ class CMakeLanguageServer(LanguageServer):
return CompletionList(is_incomplete=False, items=items)
@self.feature(FORMATTING)
def formatting(params: DocumentFormattingParams) -> Optional[List[TextEdit]]:
doc = self.workspace.get_document(params.text_document.uri)
content = doc.source
tokens, remain = self._parser.parse(content)
if remain:
self.show_message("CMake parser failed")
return None
if shutil.which("cmake-format") is not None:
formatted = Formatter().format(tokens)
lines = content.count("\n")
return [
TextEdit(
range=Range(
start=Position(line=0, character=0),
end=Position(line=lines + 1, character=0),
),
new_text=formatted,
@self.feature(FORMATTING)
def formatting(
params: DocumentFormattingParams,
) -> Optional[List[TextEdit]]:
doc = self.workspace.get_document(params.text_document.uri)
content = doc.source
formatted = subprocess.check_output(
["cmake-format", "-"],
cwd=str(Path(doc.path).parent),
input=content,
universal_newlines=True,
)
]
lines = content.count("\n")
return [
TextEdit(
range=Range(
start=Position(line=0, character=0),
end=Position(line=lines + 1, character=0),
),
new_text=formatted,
)
]
@self.feature(HOVER)
def hover(params: TextDocumentPositionParams) -> Optional[Hover]: