Replace yapf with black
This commit is contained in:
@@ -1 +1 @@
|
||||
__version__ = '0.1.2'
|
||||
__version__ = "0.1.2"
|
||||
|
||||
@@ -12,10 +12,10 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def _tidy_doc(doc: str) -> str:
|
||||
doc = doc.strip()
|
||||
doc = re.sub(r':.+?:`(.+?)`', r'\1', doc)
|
||||
doc = re.sub(r'``([^`]+)``', r'`\1`', doc)
|
||||
doc = doc.replace('\n', ' ')
|
||||
doc = doc.replace('. ', '. ')
|
||||
doc = re.sub(r":.+?:`(.+?)`", r"\1", doc)
|
||||
doc = re.sub(r"``([^`]+)``", r"`\1`", doc)
|
||||
doc = doc.replace("\n", " ")
|
||||
doc = doc.replace(". ", ". ")
|
||||
return doc
|
||||
|
||||
|
||||
@@ -49,78 +49,82 @@ class API(object):
|
||||
return False
|
||||
|
||||
self.query_json.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self.query_json.open('w') as fp:
|
||||
fp.write('''\
|
||||
with self.query_json.open("w") as fp:
|
||||
fp.write(
|
||||
"""\
|
||||
{
|
||||
"requests": [
|
||||
{"kind": "codemodel", "version": 2},
|
||||
{"kind": "cache", "version": 2},
|
||||
{"kind": "cmakeFiles", "version": 1}
|
||||
]
|
||||
}''')
|
||||
}"""
|
||||
)
|
||||
|
||||
proc = subprocess.run([self._cmake, str(self._build)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
encoding='utf-8',
|
||||
universal_newlines=True)
|
||||
proc = subprocess.run(
|
||||
[self._cmake, str(self._build)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
encoding="utf-8",
|
||||
universal_newlines=True,
|
||||
)
|
||||
self.query_json.unlink()
|
||||
self.query_json.parent.rmdir()
|
||||
if proc.returncode != 0:
|
||||
logging.error(
|
||||
f'cmake exited with {proc.returncode}: {proc.stderr}')
|
||||
logging.error(f"cmake exited with {proc.returncode}: {proc.stderr}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def read_reply(self) -> bool:
|
||||
reply = self._build / '.cmake' / 'api' / 'v1' / 'reply'
|
||||
indices = sorted(reply.glob('index-*.json'))
|
||||
reply = self._build / ".cmake" / "api" / "v1" / "reply"
|
||||
indices = sorted(reply.glob("index-*.json"))
|
||||
if not indices:
|
||||
logger.error('no reply')
|
||||
logger.error("no reply")
|
||||
return False
|
||||
with indices[-1].open() as fp:
|
||||
index = json.load(fp)
|
||||
try:
|
||||
responses = index['reply'][f'client-{self._uuid}']['query.json'][
|
||||
'responses']
|
||||
responses = index["reply"][f"client-{self._uuid}"]["query.json"][
|
||||
"responses"
|
||||
]
|
||||
except KeyError:
|
||||
logger.error('no rensponse')
|
||||
logger.error("no rensponse")
|
||||
return False
|
||||
for response in responses:
|
||||
if response['kind'] == 'codemodel':
|
||||
self._read_codemodel(reply / response['jsonFile'])
|
||||
elif response['kind'] == 'cache':
|
||||
self._read_cache(reply / response['jsonFile'])
|
||||
elif response['kind'] == 'cmakeFiles':
|
||||
self._read_cmake_files(reply / response['jsonFile'])
|
||||
if response["kind"] == "codemodel":
|
||||
self._read_codemodel(reply / response["jsonFile"])
|
||||
elif response["kind"] == "cache":
|
||||
self._read_cache(reply / response["jsonFile"])
|
||||
elif response["kind"] == "cmakeFiles":
|
||||
self._read_cmake_files(reply / response["jsonFile"])
|
||||
|
||||
return True
|
||||
|
||||
def _read_codemodel(self, codemodelpath: Path):
|
||||
with (codemodelpath).open() as fp:
|
||||
codemodel = json.load(fp)
|
||||
config = codemodel['configurations'][0]
|
||||
self._targets[:] = [x['name'] for x in config['targets']]
|
||||
config = codemodel["configurations"][0]
|
||||
self._targets[:] = [x["name"] for x in config["targets"]]
|
||||
|
||||
def _read_cache(self, cachepath: Path):
|
||||
with cachepath.open() as fp:
|
||||
cache = json.load(fp)
|
||||
self._cached_variables.clear()
|
||||
for entry in cache['entries']:
|
||||
name = entry['name']
|
||||
value = self._truncate_variable(entry['value'])
|
||||
properties = {x['name']: x['value'] for x in entry['properties']}
|
||||
helpstring = properties.get('HELPSTRING', '')
|
||||
for entry in cache["entries"]:
|
||||
name = entry["name"]
|
||||
value = self._truncate_variable(entry["value"])
|
||||
properties = {x["name"]: x["value"] for x in entry["properties"]}
|
||||
helpstring = properties.get("HELPSTRING", "")
|
||||
doc = []
|
||||
if helpstring:
|
||||
doc.append(helpstring)
|
||||
if value:
|
||||
doc.append(f'`{value}`')
|
||||
self._cached_variables[name] = '\n\n'.join(doc)
|
||||
doc.append(f"`{value}`")
|
||||
self._cached_variables[name] = "\n\n".join(doc)
|
||||
|
||||
def _read_cmake_files(self, jsonpath: Path):
|
||||
'''inspect generated list files'''
|
||||
"""inspect generated list files"""
|
||||
|
||||
if not self._builtin_variables or self._generated_list_parsed:
|
||||
return
|
||||
@@ -130,44 +134,46 @@ class API(object):
|
||||
|
||||
# inspect generated list files
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
tmplist = Path(tmpdirname) / 'dump.cmake'
|
||||
with tmplist.open('w') as fp:
|
||||
for listfile in cmake_files['inputs']:
|
||||
if not listfile.get('isGenerated', False):
|
||||
tmplist = Path(tmpdirname) / "dump.cmake"
|
||||
with tmplist.open("w") as fp:
|
||||
for listfile in cmake_files["inputs"]:
|
||||
if not listfile.get("isGenerated", False):
|
||||
continue
|
||||
path = listfile['path']
|
||||
fp.write(f'include({path})\n')
|
||||
fp.write('''
|
||||
path = listfile["path"]
|
||||
fp.write(f"include({path})\n")
|
||||
fp.write(
|
||||
"""
|
||||
get_cmake_property(variables VARIABLES)
|
||||
foreach (variable ${variables})
|
||||
message("${variable}=${${variable}}")
|
||||
endforeach()
|
||||
''')
|
||||
"""
|
||||
)
|
||||
p = subprocess.run(
|
||||
[self._cmake, '-P', str(tmplist)],
|
||||
[self._cmake, "-P", str(tmplist)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=cmake_files['paths']['source'],
|
||||
encoding='utf-8',
|
||||
universal_newlines=True)
|
||||
cwd=cmake_files["paths"]["source"],
|
||||
encoding="utf-8",
|
||||
universal_newlines=True,
|
||||
)
|
||||
if p.returncode != 0:
|
||||
return
|
||||
|
||||
for line in p.stderr.split('\n'):
|
||||
for line in p.stderr.split("\n"):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
k, v = line.split('=', 1)
|
||||
if k.startswith('CMAKE_ARG'):
|
||||
k, v = line.split("=", 1)
|
||||
if k.startswith("CMAKE_ARG"):
|
||||
continue
|
||||
v = self._truncate_variable(v)
|
||||
if k in self._builtin_variables:
|
||||
self._builtin_variables[k] += f'\n\n`{v}`'
|
||||
self._builtin_variables[k] += f"\n\n`{v}`"
|
||||
else:
|
||||
for pattern, doc in self._builtin_variable_template.items(
|
||||
):
|
||||
for pattern, doc in self._builtin_variable_template.items():
|
||||
if pattern.fullmatch(k):
|
||||
self._builtin_variables[k] = f'{doc}\n\n`{v}`'
|
||||
self._builtin_variables[k] = f"{doc}\n\n`{v}`"
|
||||
break
|
||||
else:
|
||||
# ignore variable with no document
|
||||
@@ -177,12 +183,19 @@ endforeach()
|
||||
|
||||
@property
|
||||
def query_json(self) -> Path:
|
||||
return (self._build / '.cmake' / 'api' / 'v1' / 'query' /
|
||||
f'client-{self._uuid}' / 'query.json')
|
||||
return (
|
||||
self._build
|
||||
/ ".cmake"
|
||||
/ "api"
|
||||
/ "v1"
|
||||
/ "query"
|
||||
/ f"client-{self._uuid}"
|
||||
/ "query.json"
|
||||
)
|
||||
|
||||
@property
|
||||
def cmake_cache(self) -> Path:
|
||||
return self._build / 'CMakeCache.txt'
|
||||
return self._build / "CMakeCache.txt"
|
||||
|
||||
def parse_doc(self) -> None:
|
||||
self._parse_commands()
|
||||
@@ -190,81 +203,95 @@ endforeach()
|
||||
self._parse_modules()
|
||||
|
||||
def _parse_commands(self) -> None:
|
||||
p = subprocess.run([self._cmake, '--help-commands'],
|
||||
stdout=subprocess.PIPE,
|
||||
encoding='utf-8',
|
||||
universal_newlines=True)
|
||||
p = subprocess.run(
|
||||
[self._cmake, "--help-commands"],
|
||||
stdout=subprocess.PIPE,
|
||||
encoding="utf-8",
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
return
|
||||
|
||||
matches = re.finditer(
|
||||
r'''
|
||||
r"""
|
||||
(?P<command>.+)\n
|
||||
-+\n+?
|
||||
[\s\S]*?
|
||||
(?P<signature>(?P=command)\s*\([^)]*\))
|
||||
''', p.stdout, re.VERBOSE)
|
||||
""",
|
||||
p.stdout,
|
||||
re.VERBOSE,
|
||||
)
|
||||
self._builtin_commands.clear()
|
||||
for match in matches:
|
||||
command = match.group('command')
|
||||
signature = match.group('signature')
|
||||
signature = re.sub(r'^ ', r'', signature, flags=re.MULTILINE)
|
||||
self._builtin_commands[
|
||||
command] = '```cmake\n' + signature + '\n```'
|
||||
command = match.group("command")
|
||||
signature = match.group("signature")
|
||||
signature = re.sub(r"^ ", r"", signature, flags=re.MULTILINE)
|
||||
self._builtin_commands[command] = "```cmake\n" + signature + "\n```"
|
||||
|
||||
def _parse_variables(self) -> None:
|
||||
p = subprocess.run([self._cmake, '--help-variables'],
|
||||
stdout=subprocess.PIPE,
|
||||
encoding='utf-8',
|
||||
universal_newlines=True)
|
||||
p = subprocess.run(
|
||||
[self._cmake, "--help-variables"],
|
||||
stdout=subprocess.PIPE,
|
||||
encoding="utf-8",
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
return
|
||||
|
||||
matches = re.finditer(
|
||||
r'''
|
||||
r"""
|
||||
(?P<variable>.+)\n
|
||||
-+\n\n
|
||||
(?P<doc>[\s\S]+?)(?:\n\n|$)
|
||||
''', p.stdout, re.VERBOSE)
|
||||
""",
|
||||
p.stdout,
|
||||
re.VERBOSE,
|
||||
)
|
||||
self._builtin_variables.clear()
|
||||
for match in matches:
|
||||
variable = match.group('variable')
|
||||
doc = _tidy_doc(match.group('doc'))
|
||||
if variable == 'CMAKE_MATCH_<n>':
|
||||
variable = match.group("variable")
|
||||
doc = _tidy_doc(match.group("doc"))
|
||||
if variable == "CMAKE_MATCH_<n>":
|
||||
for i in range(10):
|
||||
self._builtin_variables[f'CMAKE_MATCH_{i}'] = doc
|
||||
elif '<' in variable:
|
||||
variable = re.sub(r'<[^>]+>', r'[^_]+', variable)
|
||||
self._builtin_variables[f"CMAKE_MATCH_{i}"] = doc
|
||||
elif "<" in variable:
|
||||
variable = re.sub(r"<[^>]+>", r"[^_]+", variable)
|
||||
pattern = re.compile(variable)
|
||||
self._builtin_variable_template[pattern] = doc
|
||||
else:
|
||||
self._builtin_variables[variable] = doc
|
||||
|
||||
def _parse_modules(self) -> None:
|
||||
p = subprocess.run([self._cmake, '--help-modules'],
|
||||
stdout=subprocess.PIPE,
|
||||
encoding='utf-8',
|
||||
universal_newlines=True)
|
||||
p = subprocess.run(
|
||||
[self._cmake, "--help-modules"],
|
||||
stdout=subprocess.PIPE,
|
||||
encoding="utf-8",
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
if p.returncode != 0:
|
||||
return
|
||||
|
||||
matches = re.finditer(
|
||||
r'''
|
||||
r"""
|
||||
(?P<module>.+)\n
|
||||
-+\n+?
|
||||
(?:(?P<header>\w[\w\s]+)\n\^+\n+?)?
|
||||
(?P<doc>.(?:.|\n)+?\n\n)
|
||||
''', p.stdout + '\n\n', re.VERBOSE)
|
||||
""",
|
||||
p.stdout + "\n\n",
|
||||
re.VERBOSE,
|
||||
)
|
||||
self._builtin_modules.clear()
|
||||
for match in matches:
|
||||
module = match.group('module')
|
||||
header = match.group('header')
|
||||
doc = _tidy_doc(match.group('doc'))
|
||||
if header is not None and header != 'Overview':
|
||||
doc = ''
|
||||
module = match.group("module")
|
||||
header = match.group("header")
|
||||
doc = _tidy_doc(match.group("doc"))
|
||||
if header is not None and header != "Overview":
|
||||
doc = ""
|
||||
self._builtin_modules[module] = doc
|
||||
|
||||
def get_command_doc(self, command: str) -> Optional[str]:
|
||||
@@ -281,28 +308,27 @@ endforeach()
|
||||
return self._builtin_variables.get(variable)
|
||||
|
||||
def search_variable(self, variable: str) -> List[str]:
|
||||
cached = frozenset(x for x in self._cached_variables
|
||||
if x.startswith(variable))
|
||||
builtin = frozenset(x for x in self._builtin_variables
|
||||
if x.startswith(variable))
|
||||
cached = frozenset(x for x in self._cached_variables if x.startswith(variable))
|
||||
builtin = frozenset(
|
||||
x for x in self._builtin_variables if x.startswith(variable)
|
||||
)
|
||||
return list(cached | builtin)
|
||||
|
||||
def get_module_doc(self, module: str, package: bool) -> Optional[str]:
|
||||
if package:
|
||||
return self._builtin_modules.get('Find' + module)
|
||||
return self._builtin_modules.get("Find" + module)
|
||||
|
||||
return self._builtin_modules.get(module)
|
||||
|
||||
def search_module(self, module: str, package: bool) -> List[str]:
|
||||
if package:
|
||||
module = 'Find' + module
|
||||
return [
|
||||
x[4:] for x in self._builtin_modules if x.startswith(module)
|
||||
]
|
||||
module = "Find" + module
|
||||
return [x[4:] for x in self._builtin_modules if x.startswith(module)]
|
||||
|
||||
return [
|
||||
x for x in self._builtin_modules
|
||||
if x.startswith(module) and not x.startswith('Find')
|
||||
x
|
||||
for x in self._builtin_modules
|
||||
if x.startswith(module) and not x.startswith("Find")
|
||||
]
|
||||
|
||||
def search_target(self, target: str) -> List[str]:
|
||||
@@ -310,4 +336,4 @@ endforeach()
|
||||
|
||||
def _truncate_variable(self, v: str) -> str:
|
||||
width = 70
|
||||
return v[:width] + (v[width:] and '...')
|
||||
return v[:width] + (v[width:] and "...")
|
||||
|
||||
@@ -7,39 +7,51 @@ class Formatter(object):
|
||||
indnt: str
|
||||
lower_identifier: bool
|
||||
|
||||
def __init__(self, indent=' ', lower_identifier=True):
|
||||
def __init__(self, indent=" ", lower_identifier=True):
|
||||
self.indent = indent
|
||||
self.lower_identifier = lower_identifier
|
||||
|
||||
def format(self, tokens: TokenList) -> str:
|
||||
cmds: List[str] = ['']
|
||||
cmds: List[str] = [""]
|
||||
indnet_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 identifier in (
|
||||
"elseif",
|
||||
"else",
|
||||
"endif",
|
||||
"endforeach",
|
||||
"endwhile",
|
||||
"endmacro",
|
||||
"endfunction",
|
||||
):
|
||||
if indnet_level > 0:
|
||||
indnet_level -= 1
|
||||
cmds[-1] = self.indent * indnet_level
|
||||
cmds[-1] += (identifier
|
||||
if self.lower_identifier else raw_identifier)
|
||||
cmds[-1] += identifier if self.lower_identifier else raw_identifier
|
||||
args = self._format_args(token[1])
|
||||
if len(args) < 2:
|
||||
cmds[-1] += '(' + ''.join(args) + ')'
|
||||
cmds[-1] += "(" + "".join(args) + ")"
|
||||
else:
|
||||
cmds[-1] += '(\n'
|
||||
cmds[-1] += "(\n"
|
||||
for arg in args:
|
||||
cmds[-1] += self.indent * (indnet_level +
|
||||
1) + arg + '\n'
|
||||
cmds[-1] += self.indent * indnet_level + ')'
|
||||
if identifier in ('if', 'elseif', 'else', 'foreach', 'while',
|
||||
'macro', 'function'):
|
||||
cmds[-1] += self.indent * (indnet_level + 1) + arg + "\n"
|
||||
cmds[-1] += self.indent * indnet_level + ")"
|
||||
if identifier in (
|
||||
"if",
|
||||
"elseif",
|
||||
"else",
|
||||
"foreach",
|
||||
"while",
|
||||
"macro",
|
||||
"function",
|
||||
):
|
||||
indnet_level += 1
|
||||
elif token == '\n':
|
||||
cmds.append('')
|
||||
elif token[0] == '#':
|
||||
elif token == "\n":
|
||||
cmds.append("")
|
||||
elif token[0] == "#":
|
||||
if cmds[-1]:
|
||||
cmds[-1] += token
|
||||
else:
|
||||
@@ -48,70 +60,67 @@ class Formatter(object):
|
||||
cmds[-1] += token
|
||||
|
||||
cmds = self._strip_line(cmds)
|
||||
return '\n'.join(cmds) + '\n'
|
||||
return "\n".join(cmds) + "\n"
|
||||
|
||||
def _format_args(self, args: List[str]) -> List[str]:
|
||||
lines = ['']
|
||||
lines = [""]
|
||||
for i in range(len(args)):
|
||||
arg = args[i]
|
||||
if arg[0] == '#':
|
||||
if arg[0] == "#":
|
||||
lines[-1] += arg
|
||||
elif arg[0] == '\n':
|
||||
lines.append('')
|
||||
elif arg[0] == "\n":
|
||||
lines.append("")
|
||||
elif arg.isspace():
|
||||
if lines[-1]:
|
||||
if i + 1 < len(args) and args[i + 1][0] == '#':
|
||||
if i + 1 < len(args) and args[i + 1][0] == "#":
|
||||
lines[-1] += arg
|
||||
else:
|
||||
lines[-1] += ' '
|
||||
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'''
|
||||
"""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:
|
||||
if line != "" or len(ret) > 0:
|
||||
ret.append(line)
|
||||
while ret and ret[-1] == '':
|
||||
while ret and ret[-1] == "":
|
||||
del ret[-1]
|
||||
return ret
|
||||
|
||||
|
||||
def main(args: List[str] = None):
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from difflib import unified_diff
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from . import __version__
|
||||
from .parser import ListParser
|
||||
|
||||
parser = ArgumentParser(
|
||||
description='Format CMake list files.',
|
||||
epilog='''
|
||||
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.''',
|
||||
standard input and writes the result to the standard output.""",
|
||||
)
|
||||
parser.add_argument('lists', type=Path, nargs='*', help='CMake list files')
|
||||
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__}')
|
||||
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(args)
|
||||
|
||||
if not args.lists and args.inplace:
|
||||
print('error: cannot use -i when no arguments are specified.',
|
||||
file=sys.stderr)
|
||||
print("error: cannot use -i when no arguments are specified.", file=sys.stderr)
|
||||
return
|
||||
if not args.lists:
|
||||
args.lists.append(None)
|
||||
@@ -120,7 +129,7 @@ def main(args: List[str] = None):
|
||||
formatter = Formatter()
|
||||
for listpath in args.lists:
|
||||
if listpath is None:
|
||||
listpath = '(stdin)'
|
||||
listpath = "(stdin)"
|
||||
content = sys.stdin.read()
|
||||
else:
|
||||
with listpath.open() as fp:
|
||||
@@ -130,14 +139,18 @@ def main(args: List[str] = None):
|
||||
|
||||
if args.inplace:
|
||||
if not remain:
|
||||
with listpath.open('w') as fp:
|
||||
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='')
|
||||
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='')
|
||||
print(formatted, end="")
|
||||
|
||||
@@ -11,8 +11,8 @@ class ListParser(object):
|
||||
_parser: pp.ParserElement
|
||||
|
||||
def __init__(self):
|
||||
newline = '\n'
|
||||
space_plus = pp.Regex('[ \t]+')
|
||||
newline = "\n"
|
||||
space_plus = pp.Regex("[ \t]+")
|
||||
space_star = pp.Optional(space_plus)
|
||||
|
||||
quoted_element = pp.Regex(r'[^\\"]|\\[^A-Za-z0-9]|\\[trn]')
|
||||
@@ -22,10 +22,10 @@ class ListParser(object):
|
||||
|
||||
def action_bracket_open(tokens: pp.ParseResults):
|
||||
nonlocal bracket_content
|
||||
marker = ']' + '=' * (len(tokens[0]) - 2) + ']'
|
||||
marker = "]" + "=" * (len(tokens[0]) - 2) + "]"
|
||||
bracket_content <<= pp.SkipTo(marker, include=True)
|
||||
|
||||
bracket_open = pp.Regex(r'\[=*\[').setParseAction(action_bracket_open)
|
||||
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]')
|
||||
@@ -33,25 +33,29 @@ class ListParser(object):
|
||||
|
||||
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))
|
||||
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 + '_')
|
||||
identifier = pp.Word(pp.alphas + "_", pp.alphanums + "_")
|
||||
arguments = pp.Forward()
|
||||
arguments << pp.ZeroOrMore(argument | line_ending | space_plus
|
||||
| '(' + arguments + ')').leaveWhitespace()
|
||||
arguments << pp.ZeroOrMore(
|
||||
argument | line_ending | space_plus | "(" + arguments + ")"
|
||||
).leaveWhitespace()
|
||||
arguments = pp.Group(arguments)
|
||||
PAREN_L, PAREN_R = map(pp.Suppress, '()')
|
||||
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()))
|
||||
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_element = (
|
||||
space_star + command_invocation + line_ending | line_ending
|
||||
).leaveWhitespace()
|
||||
file = pp.ZeroOrMore(file_element)
|
||||
|
||||
self._parser = file
|
||||
|
||||
@@ -3,14 +3,31 @@ import re
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from pygls.features import (COMPLETION, FORMATTING, HOVER, INITIALIZE,
|
||||
INITIALIZED, TEXT_DOCUMENT_DID_SAVE)
|
||||
from pygls.features import (
|
||||
COMPLETION,
|
||||
FORMATTING,
|
||||
HOVER,
|
||||
INITIALIZE,
|
||||
INITIALIZED,
|
||||
TEXT_DOCUMENT_DID_SAVE,
|
||||
)
|
||||
from pygls.server import LanguageServer
|
||||
from pygls.types import (CompletionItem, CompletionItemKind, CompletionList,
|
||||
CompletionParams, CompletionTriggerKind,
|
||||
DocumentFormattingParams, Hover, InitializeParams,
|
||||
MarkupContent, MarkupKind, Position, Range,
|
||||
TextDocumentPositionParams, TextEdit)
|
||||
from pygls.types import (
|
||||
CompletionItem,
|
||||
CompletionItemKind,
|
||||
CompletionList,
|
||||
CompletionParams,
|
||||
CompletionTriggerKind,
|
||||
DocumentFormattingParams,
|
||||
Hover,
|
||||
InitializeParams,
|
||||
MarkupContent,
|
||||
MarkupKind,
|
||||
Position,
|
||||
Range,
|
||||
TextDocumentPositionParams,
|
||||
TextEdit,
|
||||
)
|
||||
|
||||
from .api import API
|
||||
from .formatter import Formatter
|
||||
@@ -33,32 +50,34 @@ class CMakeLanguageServer(LanguageServer):
|
||||
def initialize(params: InitializeParams):
|
||||
opts = params.initializationOptions
|
||||
|
||||
cmake = getattr(opts, 'cmakeExecutable', 'cmake')
|
||||
builddir = getattr(opts, 'buildDirectory', '')
|
||||
logging.info(f'cmakeExecutable={cmake}, buildDirectory={builddir}')
|
||||
cmake = getattr(opts, "cmakeExecutable", "cmake")
|
||||
builddir = getattr(opts, "buildDirectory", "")
|
||||
logging.info(f"cmakeExecutable={cmake}, buildDirectory={builddir}")
|
||||
|
||||
self._api = API(cmake, Path(builddir))
|
||||
self._api.parse_doc()
|
||||
|
||||
trigger_characters = ['{', '(']
|
||||
trigger_characters = ["{", "("]
|
||||
|
||||
@self.feature(COMPLETION, trigger_characters=trigger_characters)
|
||||
def completions(params: CompletionParams):
|
||||
if (hasattr(params, 'context') and params.context.triggerKind ==
|
||||
CompletionTriggerKind.TriggerCharacter):
|
||||
token = ''
|
||||
if (
|
||||
hasattr(params, "context")
|
||||
and params.context.triggerKind == CompletionTriggerKind.TriggerCharacter
|
||||
):
|
||||
token = ""
|
||||
trigger = params.context.triggerCharacter
|
||||
else:
|
||||
line = self._cursor_line(params.textDocument.uri,
|
||||
params.position)
|
||||
line = self._cursor_line(params.textDocument.uri, params.position)
|
||||
idx = params.position.character - 1
|
||||
if 0 <= idx < len(line) and line[idx] in trigger_characters:
|
||||
token = ''
|
||||
token = ""
|
||||
trigger = line[idx]
|
||||
else:
|
||||
word = self._cursor_word(params.textDocument.uri,
|
||||
params.position, False)
|
||||
token = '' if word is None else word[0]
|
||||
word = self._cursor_word(
|
||||
params.textDocument.uri, params.position, False
|
||||
)
|
||||
token = "" if word is None else word[0]
|
||||
trigger = None
|
||||
|
||||
items: List[CompletionItem] = []
|
||||
@@ -66,46 +85,60 @@ class CMakeLanguageServer(LanguageServer):
|
||||
if trigger is None:
|
||||
commands = self._api.search_command(token)
|
||||
items.extend(
|
||||
CompletionItem(x,
|
||||
CompletionItemKind.Function,
|
||||
documentation=self._api.get_command_doc(x),
|
||||
insert_text=x) for x in commands)
|
||||
CompletionItem(
|
||||
x,
|
||||
CompletionItemKind.Function,
|
||||
documentation=self._api.get_command_doc(x),
|
||||
insert_text=x,
|
||||
)
|
||||
for x in commands
|
||||
)
|
||||
|
||||
if trigger is None or trigger == '{':
|
||||
if trigger is None or trigger == "{":
|
||||
variables = self._api.search_variable(token)
|
||||
items.extend(
|
||||
CompletionItem(x,
|
||||
CompletionItemKind.Variable,
|
||||
documentation=self._api.get_variable_doc(x),
|
||||
insert_text=x) for x in variables)
|
||||
CompletionItem(
|
||||
x,
|
||||
CompletionItemKind.Variable,
|
||||
documentation=self._api.get_variable_doc(x),
|
||||
insert_text=x,
|
||||
)
|
||||
for x in variables
|
||||
)
|
||||
|
||||
if trigger is None:
|
||||
targets = self._api.search_target(token)
|
||||
items.extend(
|
||||
CompletionItem(x, CompletionItemKind.Class, insert_text=x)
|
||||
for x in targets)
|
||||
for x in targets
|
||||
)
|
||||
|
||||
if trigger == '(':
|
||||
func = self._cursor_function(params.textDocument.uri,
|
||||
params.position)
|
||||
if trigger == "(":
|
||||
func = self._cursor_function(params.textDocument.uri, params.position)
|
||||
if func is not None:
|
||||
func = func.lower()
|
||||
if func == 'include':
|
||||
if func == "include":
|
||||
modules = self._api.search_module(token, False)
|
||||
items.extend(
|
||||
CompletionItem(x,
|
||||
CompletionItemKind.Module,
|
||||
documentation=self._api.
|
||||
get_module_doc(x, False),
|
||||
insert_text=x) for x in modules)
|
||||
elif func == 'find_package':
|
||||
CompletionItem(
|
||||
x,
|
||||
CompletionItemKind.Module,
|
||||
documentation=self._api.get_module_doc(x, False),
|
||||
insert_text=x,
|
||||
)
|
||||
for x in modules
|
||||
)
|
||||
elif func == "find_package":
|
||||
modules = self._api.search_module(token, True)
|
||||
items.extend(
|
||||
CompletionItem(x,
|
||||
CompletionItemKind.Module,
|
||||
documentation=self._api.
|
||||
get_module_doc(x, True),
|
||||
insert_text=x) for x in modules)
|
||||
CompletionItem(
|
||||
x,
|
||||
CompletionItemKind.Module,
|
||||
documentation=self._api.get_module_doc(x, True),
|
||||
insert_text=x,
|
||||
)
|
||||
for x in modules
|
||||
)
|
||||
|
||||
return CompletionList(False, items)
|
||||
|
||||
@@ -115,20 +148,16 @@ class CMakeLanguageServer(LanguageServer):
|
||||
content = doc.source
|
||||
tokens, remain = self._parser.parse(content)
|
||||
if remain:
|
||||
self.show_message('CMake parser failed')
|
||||
self.show_message("CMake parser failed")
|
||||
return None
|
||||
|
||||
formatted = Formatter().format(tokens)
|
||||
lines = content.count('\n')
|
||||
return [
|
||||
TextEdit(Range(Position(0, 0), Position(lines + 1, 0)),
|
||||
formatted)
|
||||
]
|
||||
lines = content.count("\n")
|
||||
return [TextEdit(Range(Position(0, 0), Position(lines + 1, 0)), formatted)]
|
||||
|
||||
@self.feature(HOVER)
|
||||
def hover(params: TextDocumentPositionParams):
|
||||
word = self._cursor_word(params.textDocument.uri, params.position,
|
||||
True)
|
||||
word = self._cursor_word(params.textDocument.uri, params.position, True)
|
||||
if not word:
|
||||
return None
|
||||
|
||||
@@ -154,43 +183,46 @@ class CMakeLanguageServer(LanguageServer):
|
||||
|
||||
def _cursor_function(self, uri: str, position: Position) -> Optional[str]:
|
||||
doc = self.workspace.get_document(uri)
|
||||
lines = doc.source.split('\n')[:position.line + 1]
|
||||
lines[-1] = lines[-1][:position.character - 1].strip()
|
||||
words = re.split(r'[\s\n()]+', '\n'.join(lines))
|
||||
lines = doc.source.split("\n")[: position.line + 1]
|
||||
lines[-1] = lines[-1][: position.character - 1].strip()
|
||||
words = re.split(r"[\s\n()]+", "\n".join(lines))
|
||||
return words[-1] if words else None
|
||||
|
||||
def _cursor_line(self, uri: str, position: Position) -> str:
|
||||
doc = self.workspace.get_document(uri)
|
||||
content = doc.source
|
||||
line = content.split('\n')[position.line]
|
||||
line = content.split("\n")[position.line]
|
||||
return line
|
||||
|
||||
def _cursor_word(self,
|
||||
uri: str,
|
||||
position: Position,
|
||||
include_all: bool = True) -> Optional[Tuple[str, Range]]:
|
||||
def _cursor_word(
|
||||
self, uri: str, position: Position, include_all: bool = True
|
||||
) -> Optional[Tuple[str, Range]]:
|
||||
line = self._cursor_line(uri, position)
|
||||
cursor = position.character
|
||||
for m in re.finditer(r'\w+', line):
|
||||
for m in re.finditer(r"\w+", line):
|
||||
end = m.end() if include_all else cursor
|
||||
if m.start() <= cursor <= m.end():
|
||||
word = (line[m.start():end],
|
||||
Range(Position(position.line, m.start()),
|
||||
Position(position.line, end)))
|
||||
word = (
|
||||
line[m.start() : end],
|
||||
Range(
|
||||
Position(position.line, m.start()), Position(position.line, end)
|
||||
),
|
||||
)
|
||||
return word
|
||||
return None
|
||||
|
||||
|
||||
def main(args=None):
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from . import __version__
|
||||
|
||||
parser = ArgumentParser(description='CMake Language Server')
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=f'%(prog)s {__version__}')
|
||||
parser = ArgumentParser(description="CMake Language Server")
|
||||
parser.add_argument(
|
||||
"--version", action="version", version=f"%(prog)s {__version__}"
|
||||
)
|
||||
args = parser.parse_args(args)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.getLogger('pygls').setLevel(logging.WARNING)
|
||||
logging.getLogger("pygls").setLevel(logging.WARNING)
|
||||
CMakeLanguageServer().start_io()
|
||||
|
||||
Reference in New Issue
Block a user