summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:21:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:21:34 +0000
commitfe1438b06234f8e5ecd4caa7eedfeec585b6f77c (patch)
tree5c2a9ff683189a61e0855ca3f24df319e7e03b7f /scripts
parentInitial commit. (diff)
downloadpygls-upstream.tar.xz
pygls-upstream.zip
Adding upstream version 1.3.0.upstream/1.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'scripts')
-rw-r--r--scripts/check_client_is_uptodate.py20
-rw-r--r--scripts/generate_client.py207
-rw-r--r--scripts/generate_contributors_md.py48
3 files changed, 275 insertions, 0 deletions
diff --git a/scripts/check_client_is_uptodate.py b/scripts/check_client_is_uptodate.py
new file mode 100644
index 0000000..1c5b1be
--- /dev/null
+++ b/scripts/check_client_is_uptodate.py
@@ -0,0 +1,20 @@
+import sys
+import subprocess
+
+AUTOGENERATED_CLIENT_FILE = "pygls/lsp/client.py"
+
+subprocess.run(["poe", "generate_client"])
+
+result = subprocess.run(
+ ["git", "diff", "--exit-code", AUTOGENERATED_CLIENT_FILE], stdout=subprocess.DEVNULL
+)
+
+if result.returncode == 0:
+ print("✅ Pygls client is up to date")
+else:
+ print(
+ "🔴 Pygls client not uptodate\n"
+ "1. Generate with: `poetry run poe generate_client`\n"
+ "2. Commit"
+ )
+ sys.exit(result.returncode)
diff --git a/scripts/generate_client.py b/scripts/generate_client.py
new file mode 100644
index 0000000..6aab8f2
--- /dev/null
+++ b/scripts/generate_client.py
@@ -0,0 +1,207 @@
+"""Script to automatically generate a lanaguge client from `lsprotocol` type definitons
+"""
+import argparse
+import inspect
+import pathlib
+import re
+import sys
+import textwrap
+from typing import Optional
+from typing import Set
+from typing import Tuple
+from typing import Type
+
+from lsprotocol._hooks import _resolve_forward_references
+from lsprotocol.types import METHOD_TO_TYPES
+from lsprotocol.types import message_direction
+
+cli = argparse.ArgumentParser(
+ description="generate language client from lsprotocol types."
+)
+cli.add_argument("-o", "--output", default=None)
+
+
+def write_imports(imports: Set[Tuple[str, str]]) -> str:
+ lines = []
+
+ for import_ in sorted(list(imports), key=lambda i: (i[0], i[1])):
+ if isinstance(import_, tuple):
+ mod, name = import_
+ lines.append(f"from {mod} import {name}")
+ continue
+
+ lines.append(f"import {import_}")
+
+ return "\n".join(lines)
+
+
+def to_snake_case(string: str) -> str:
+ return "".join(f"_{c.lower()}" if c.isupper() else c for c in string)
+
+
+def write_notification(
+ method: str,
+ request: Type,
+ params: Optional[Type],
+ imports: Set[Tuple[str, str]],
+) -> str:
+ python_name = to_snake_case(method).replace("/", "_").replace("$_", "")
+
+ if params is None:
+ param_name = "None"
+ param_mod = ""
+ else:
+ param_mod, param_name = params.__module__, params.__name__
+ param_mod = param_mod.replace("lsprotocol.types", "types") + "."
+
+ return "\n".join(
+ [
+ f"def {python_name}(self, params: {param_mod}{param_name}) -> None:",
+ f' """Send a :lsp:`{method}` notification.',
+ "",
+ textwrap.indent(inspect.getdoc(request) or "", " "),
+ ' """',
+ " if self.stopped:",
+ ' raise RuntimeError("Client has been stopped.")',
+ "",
+ f' self.protocol.notify("{method}", params)',
+ "",
+ ]
+ )
+
+
+def get_response_type(response: Type, imports: Set[Tuple[str, str]]) -> str:
+ # Find the response type.
+ result_field = [f for f in response.__attrs_attrs__ if f.name == "result"][0]
+ result = re.sub(r"<class '([\w.]+)'>", r"\1", str(result_field.type))
+ result = re.sub(r"ForwardRef\('([\w.]+)'\)", r"lsprotocol.types.\1", result)
+ result = result.replace("NoneType", "None")
+
+ # Replace any typing imports with their short name.
+ for match in re.finditer(r"typing.([\w]+)", result):
+ imports.add(("typing", match.group(1)))
+
+ result = result.replace("lsprotocol.types.", "types.")
+ result = result.replace("typing.", "")
+
+ return result
+
+
+def write_method(
+ method: str,
+ request: Type,
+ params: Optional[Type],
+ response: Type,
+ imports: Set[Tuple[str, str]],
+) -> str:
+ python_name = to_snake_case(method).replace("/", "_").replace("$_", "")
+
+ if params is None:
+ param_name = "None"
+ param_mod = ""
+ else:
+ param_mod, param_name = params.__module__, params.__name__
+ param_mod = param_mod.replace("lsprotocol.types", "types") + "."
+
+ result_type = get_response_type(response, imports)
+
+ return "\n".join(
+ [
+ f"def {python_name}(",
+ " self,",
+ f" params: {param_mod}{param_name},",
+ f" callback: Optional[Callable[[{result_type}], None]] = None,",
+ ") -> Future:",
+ f' """Make a :lsp:`{method}` request.',
+ "",
+ textwrap.indent(inspect.getdoc(request) or "", " "),
+ ' """',
+ " if self.stopped:",
+ ' raise RuntimeError("Client has been stopped.")',
+ "",
+ f' return self.protocol.send_request("{method}", params, callback)',
+ "",
+ f"async def {python_name}_async(",
+ " self,",
+ f" params: {param_mod}{param_name},",
+ f") -> {result_type}:",
+ f' """Make a :lsp:`{method}` request.',
+ "",
+ textwrap.indent(inspect.getdoc(request) or "", " "),
+ ' """',
+ " if self.stopped:",
+ ' raise RuntimeError("Client has been stopped.")',
+ "",
+ f' return await self.protocol.send_request_async("{method}", params)',
+ "",
+ ]
+ )
+
+
+def generate_client() -> str:
+ methods = []
+ imports = {
+ ("concurrent.futures", "Future"),
+ ("lsprotocol", "types"),
+ ("pygls.protocol", "LanguageServerProtocol"),
+ ("pygls.protocol", "default_converter"),
+ ("pygls.client", "JsonRPCClient"),
+ ("typing", "Callable"),
+ ("typing", "Optional"),
+ }
+
+ for method_name, types in METHOD_TO_TYPES.items():
+ # Skip any requests that come from the server.
+ if message_direction(method_name) == "serverToClient":
+ continue
+
+ request, response, params, _ = types
+
+ if response is None:
+ method = write_notification(method_name, request, params, imports)
+ else:
+ method = write_method(method_name, request, params, response, imports)
+
+ methods.append(textwrap.indent(method, " "))
+
+ code = [
+ "# GENERATED FROM scripts/gen-client.py -- DO NOT EDIT",
+ "# flake8: noqa",
+ write_imports(imports),
+ "",
+ "",
+ "class BaseLanguageClient(JsonRPCClient):",
+ "",
+ " def __init__(",
+ " self,",
+ " name: str,",
+ " version: str,",
+ " protocol_cls=LanguageServerProtocol,",
+ " converter_factory=default_converter,",
+ " **kwargs,",
+ " ):",
+ " self.name = name",
+ " self.version = version",
+ " super().__init__(protocol_cls, converter_factory, **kwargs)",
+ "",
+ *methods,
+ ]
+ return "\n".join(code)
+
+
+def main():
+ args = cli.parse_args()
+
+ # Make sure all the type annotations in lsprotocol are resolved correctly.
+ _resolve_forward_references()
+ client = generate_client()
+
+ if args.output is None:
+ sys.stdout.write(client)
+ else:
+ output = pathlib.Path(args.output)
+ output.write_text(client)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/generate_contributors_md.py b/scripts/generate_contributors_md.py
new file mode 100644
index 0000000..655181a
--- /dev/null
+++ b/scripts/generate_contributors_md.py
@@ -0,0 +1,48 @@
+"""
+Example JSON object:
+{
+ "login": "danixeee",
+ "id": 16227576,
+ "node_id": "MDQ6VXNlcjE2MjI3NTc2",
+ "avatar_url": "https://avatars.githubusercontent.com/u/16227576?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/danixeee",
+ "html_url": "https://github.com/danixeee",
+ "followers_url": "https://api.github.com/users/danixeee/followers",
+ "following_url": "https://api.github.com/users/danixeee/following{/other_user}",
+ "gists_url": "https://api.github.com/users/danixeee/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/danixeee/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/danixeee/subscriptions",
+ "organizations_url": "https://api.github.com/users/danixeee/orgs",
+ "repos_url": "https://api.github.com/users/danixeee/repos",
+ "events_url": "https://api.github.com/users/danixeee/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/danixeee/received_events",
+ "type": "User",
+ "site_admin": false,
+ "contributions": 321
+}
+"""
+
+import requests
+
+PYGLS_CONTRIBUTORS_JSON_URL = (
+ "https://api.github.com/repos/openlawlibrary/pygls/contributors"
+)
+CONTRIBUTORS_FILE = "CONTRIBUTORS.md"
+
+response = requests.get(PYGLS_CONTRIBUTORS_JSON_URL)
+contributors = sorted(response.json(), key=lambda d: d["login"].lower())
+
+contents = "# Contributors (contributions)\n"
+
+for contributor in contributors:
+ name = contributor["login"]
+ contributions = contributor["contributions"]
+ url = contributor["html_url"]
+ contents += f"* [{name}]({url}) ({contributions})\n"
+
+file = open(CONTRIBUTORS_FILE, "w")
+n = file.write(contents)
+file.close()
+
+print("✅ CONTRIBUTORS.md updated")