Use cmakelang formatter (#52)
This commit is contained in:
@@ -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()
|
||||
@@ -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
|
||||
@@ -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]:
|
||||
|
||||
Reference in New Issue
Block a user