sphinx-lua-ls 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sphinx_lua_ls/__init__.py +166 -0
- sphinx_lua_ls/_version.py +21 -0
- sphinx_lua_ls/autodoc.py +632 -0
- sphinx_lua_ls/doctree.py +477 -0
- sphinx_lua_ls/domain.py +1156 -0
- sphinx_lua_ls/intersphinx.py +204 -0
- sphinx_lua_ls/lua_ls.py +694 -0
- sphinx_lua_ls/py.typed +0 -0
- sphinx_lua_ls-0.0.4.dist-info/LICENSE +21 -0
- sphinx_lua_ls-0.0.4.dist-info/METADATA +92 -0
- sphinx_lua_ls-0.0.4.dist-info/RECORD +13 -0
- sphinx_lua_ls-0.0.4.dist-info/WHEEL +5 -0
- sphinx_lua_ls-0.0.4.dist-info/top_level.txt +1 -0
sphinx_lua_ls/lua_ls.py
ADDED
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wrapper around the Lua-LS executable; able to download lua-ls if it's not installed.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import pathlib
|
|
10
|
+
import platform
|
|
11
|
+
import re
|
|
12
|
+
import shutil
|
|
13
|
+
import signal
|
|
14
|
+
import stat
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
import typing as _t
|
|
19
|
+
|
|
20
|
+
import github
|
|
21
|
+
import requests
|
|
22
|
+
import requests.adapters
|
|
23
|
+
import urllib3
|
|
24
|
+
from sphinx.util import logging
|
|
25
|
+
from sphinx.util.console import bold # type: ignore
|
|
26
|
+
|
|
27
|
+
_PathLike: _t.TypeAlias = str | os.PathLike[str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_logger = logging.getLogger("sphinx_lua_ls")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LuaLsError(Exception):
|
|
34
|
+
"""
|
|
35
|
+
Raised when LuaLS is unavailable, or when installation fails.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LuaLsRunError(LuaLsError, subprocess.CalledProcessError):
|
|
41
|
+
"""
|
|
42
|
+
Raised when LuaLS process fails.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __str__(self):
|
|
47
|
+
if self.returncode and self.returncode < 0:
|
|
48
|
+
try:
|
|
49
|
+
returncode = f"signal {signal.Signals(-self.returncode)}"
|
|
50
|
+
except ValueError:
|
|
51
|
+
returncode = f"unknown signal {self.returncode}"
|
|
52
|
+
else:
|
|
53
|
+
returncode = f"code {self.returncode}"
|
|
54
|
+
|
|
55
|
+
msg = f"LuaLS run failed with {returncode}"
|
|
56
|
+
stderr = self.stderr
|
|
57
|
+
if self.stderr:
|
|
58
|
+
if isinstance(stderr, bytes):
|
|
59
|
+
stderr = stderr.decode("utf-8", errors="replace")
|
|
60
|
+
msg += f"\n\nStderr:\n{stderr}"
|
|
61
|
+
stdout = self.stdout
|
|
62
|
+
if self.stdout:
|
|
63
|
+
if isinstance(stdout, bytes):
|
|
64
|
+
stdout = stdout.decode("utf-8", errors="replace")
|
|
65
|
+
msg += f"\n\nStdout:\n{stdout}"
|
|
66
|
+
return msg
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@_t.final
|
|
70
|
+
class LuaLs:
|
|
71
|
+
"""
|
|
72
|
+
Interface for a lua-language-server installation.
|
|
73
|
+
|
|
74
|
+
Do not create directly, use :func:`resolve` instead.
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
*,
|
|
81
|
+
_lua_ls_path: pathlib.Path,
|
|
82
|
+
_path: str,
|
|
83
|
+
_quiet: bool = True,
|
|
84
|
+
_env: dict[str, str] | None = None,
|
|
85
|
+
_cwd: _PathLike | None = None,
|
|
86
|
+
):
|
|
87
|
+
self._lua_ls_path = _lua_ls_path
|
|
88
|
+
self._path = _path
|
|
89
|
+
self._quiet = _quiet
|
|
90
|
+
self._env = _env
|
|
91
|
+
self._cwd = _cwd
|
|
92
|
+
|
|
93
|
+
def run(
|
|
94
|
+
self,
|
|
95
|
+
input_path: _PathLike,
|
|
96
|
+
*,
|
|
97
|
+
quiet: bool | None = None,
|
|
98
|
+
env: dict[str, str] | None = None,
|
|
99
|
+
cwd: _PathLike | None = None,
|
|
100
|
+
) -> _t.Any:
|
|
101
|
+
"""
|
|
102
|
+
Renter the given VHS file.
|
|
103
|
+
|
|
104
|
+
:param input_path:
|
|
105
|
+
path to the directory/file that needs documentation.
|
|
106
|
+
:param quiet:
|
|
107
|
+
redefine `quiet` for this invocation. (see :func:`resolve`).
|
|
108
|
+
:param env:
|
|
109
|
+
redefine `env` for this invocation. (see :func:`resolve`).
|
|
110
|
+
:param cwd:
|
|
111
|
+
redefine `cmd` for this invocation. (see :func:`resolve`).
|
|
112
|
+
:return:
|
|
113
|
+
parsed documentation.
|
|
114
|
+
|
|
115
|
+
:raises LuaLsRunError: VHS process failed with non-zero return code.
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
if quiet is None:
|
|
120
|
+
quiet = self._quiet
|
|
121
|
+
|
|
122
|
+
if env is None:
|
|
123
|
+
env = self._env
|
|
124
|
+
if env is None:
|
|
125
|
+
env = os.environ.copy()
|
|
126
|
+
else:
|
|
127
|
+
env = env.copy()
|
|
128
|
+
env["PATH"] = self._path
|
|
129
|
+
|
|
130
|
+
if cwd is None:
|
|
131
|
+
cwd = self._cwd
|
|
132
|
+
|
|
133
|
+
with tempfile.TemporaryDirectory() as output_path:
|
|
134
|
+
args: list[str | _PathLike] = [
|
|
135
|
+
self._lua_ls_path,
|
|
136
|
+
"--doc",
|
|
137
|
+
input_path,
|
|
138
|
+
"--doc_out_path",
|
|
139
|
+
output_path,
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
_logger.debug(
|
|
144
|
+
"running lua-language-server with args %r", args, type="lua-ls"
|
|
145
|
+
)
|
|
146
|
+
subprocess.run(
|
|
147
|
+
args,
|
|
148
|
+
capture_output=quiet,
|
|
149
|
+
env=env,
|
|
150
|
+
cwd=cwd,
|
|
151
|
+
check=True,
|
|
152
|
+
)
|
|
153
|
+
except subprocess.CalledProcessError as e:
|
|
154
|
+
raise LuaLsRunError(
|
|
155
|
+
e.returncode,
|
|
156
|
+
e.cmd,
|
|
157
|
+
e.output,
|
|
158
|
+
e.stderr,
|
|
159
|
+
) from None
|
|
160
|
+
|
|
161
|
+
return json.loads(pathlib.Path(output_path, "doc.json").read_text())
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class ProgressReporter:
|
|
165
|
+
"""
|
|
166
|
+
Interface for reporting installation progress.
|
|
167
|
+
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def start(self):
|
|
171
|
+
"""
|
|
172
|
+
Called when installation starts.
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def progress(self, desc: str, dl_size: int, total_size: int, speed: float, /):
|
|
177
|
+
"""
|
|
178
|
+
Called to update current progress.
|
|
179
|
+
|
|
180
|
+
:param desc:
|
|
181
|
+
description of the currently performed operation.
|
|
182
|
+
:param dl_size:
|
|
183
|
+
when the installer downloads files, this number indicates
|
|
184
|
+
number of bytes downloaded so far. Otherwise, it is set to zero.
|
|
185
|
+
:param total_size:
|
|
186
|
+
when the installer downloads files, this number indicates
|
|
187
|
+
total number of bytes to download. Otherwise, it is set to zero.
|
|
188
|
+
:param speed:
|
|
189
|
+
when the installer downloads files, this number indicates
|
|
190
|
+
current downloading speed, in bytes per second. Otherwise,
|
|
191
|
+
it is set to zero.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
def finish(self, exc_type, exc_val, exc_tb):
|
|
196
|
+
"""
|
|
197
|
+
Called when installation finishes.
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class DefaultProgressReporter(ProgressReporter):
|
|
203
|
+
"""
|
|
204
|
+
Default reporter that prints progress to stderr.
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
_prev_len = 0
|
|
209
|
+
|
|
210
|
+
def __init__(self, stream: _t.TextIO | None = None):
|
|
211
|
+
self.stream = stream or sys.stderr
|
|
212
|
+
|
|
213
|
+
def progress(self, desc: str, dl_size: int, total_size: int, speed: float, /):
|
|
214
|
+
desc = self.format_desc(desc)
|
|
215
|
+
|
|
216
|
+
if total_size:
|
|
217
|
+
desc += self.format_progress(dl_size, total_size, speed)
|
|
218
|
+
|
|
219
|
+
self.write(desc.ljust(self._prev_len) + "\r")
|
|
220
|
+
|
|
221
|
+
self._prev_len = len(desc)
|
|
222
|
+
|
|
223
|
+
def finish(self, exc_type, exc_val, exc_tb):
|
|
224
|
+
if exc_val:
|
|
225
|
+
self.progress(f"lua_ls installation failed: {exc_val}", 0, 0, 0)
|
|
226
|
+
self.write("\n")
|
|
227
|
+
elif self._prev_len > 0:
|
|
228
|
+
self.progress(f"lua_ls installed", 0, 0, 0)
|
|
229
|
+
self.write("\n")
|
|
230
|
+
|
|
231
|
+
def format_desc(self, desc: str) -> str:
|
|
232
|
+
return desc
|
|
233
|
+
|
|
234
|
+
def format_progress(self, dl_size: int, total_size: int, speed: float) -> str:
|
|
235
|
+
dl_size_mb = dl_size / 1024**2
|
|
236
|
+
total_size_mb = total_size / 1024**2
|
|
237
|
+
speed_mb = speed / 1024**2
|
|
238
|
+
|
|
239
|
+
return f": {dl_size_mb:.1f}/{total_size_mb:.1f}MB - {speed_mb:.2f}MB/s"
|
|
240
|
+
|
|
241
|
+
def write(self, msg: str):
|
|
242
|
+
self.stream.write(msg)
|
|
243
|
+
self.stream.flush()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class SphinxProgressReporter(DefaultProgressReporter):
|
|
247
|
+
_prev_desc = None
|
|
248
|
+
_prev_len = 0
|
|
249
|
+
|
|
250
|
+
def __init__(self, verbosity: int):
|
|
251
|
+
super().__init__()
|
|
252
|
+
|
|
253
|
+
self._verbosity = verbosity
|
|
254
|
+
|
|
255
|
+
def progress(self, desc: str, dl_size: int, total_size: int, speed: float, /):
|
|
256
|
+
if self._verbosity:
|
|
257
|
+
if desc != self._prev_desc:
|
|
258
|
+
_logger.info("%s", desc, type="lua-ls")
|
|
259
|
+
else:
|
|
260
|
+
super().progress(desc, dl_size, total_size, speed)
|
|
261
|
+
|
|
262
|
+
self._prev_desc = desc
|
|
263
|
+
|
|
264
|
+
def format_desc(self, desc: str) -> str:
|
|
265
|
+
return bold(desc + "...")
|
|
266
|
+
|
|
267
|
+
def format_progress(self, dl_size: int, total_size: int, speed: float) -> str:
|
|
268
|
+
dl_size_mb = dl_size / 1024**2
|
|
269
|
+
total_size_mb = total_size / 1024**2
|
|
270
|
+
speed_mb = speed / 1024**2
|
|
271
|
+
progress = dl_size / total_size
|
|
272
|
+
|
|
273
|
+
return f" [{progress: >3.0%}] {dl_size_mb:.1f}/{total_size_mb:.1f}MB ({speed_mb:.1f}MB/s)"
|
|
274
|
+
|
|
275
|
+
def write(self, msg: str):
|
|
276
|
+
_logger.info(msg, nonl=True, type="lua-ls")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def resolve(
|
|
280
|
+
*,
|
|
281
|
+
cache_path: _PathLike | None = None,
|
|
282
|
+
min_version: str = "3.0.0",
|
|
283
|
+
quiet: bool = True,
|
|
284
|
+
env: dict[str, str] | None = None,
|
|
285
|
+
cwd: _PathLike | None = None,
|
|
286
|
+
install: bool = True,
|
|
287
|
+
reporter: ProgressReporter = ProgressReporter(),
|
|
288
|
+
timeout: int = 15,
|
|
289
|
+
retry: _t.Optional[urllib3.Retry] = None,
|
|
290
|
+
):
|
|
291
|
+
"""
|
|
292
|
+
Find a system LuaLS installation or download LuaLS from GitHub.
|
|
293
|
+
|
|
294
|
+
If LuaLS is not installed, or it's outdated, try to download it
|
|
295
|
+
and install it into `cache_path`.
|
|
296
|
+
|
|
297
|
+
Automatic download only works on 64-bit Linux.
|
|
298
|
+
MacOS users will be presented with an instruction to use `brew`,
|
|
299
|
+
and other systems users will get a link to LuaLS installation guide.
|
|
300
|
+
|
|
301
|
+
:param cache_path:
|
|
302
|
+
path where LuaLS binaries should be downloaded to.
|
|
303
|
+
:param min_version:
|
|
304
|
+
minimal LuaLS version required.
|
|
305
|
+
:param quiet:
|
|
306
|
+
if true (default), any output from the LuaLS binary is hidden.
|
|
307
|
+
:param env:
|
|
308
|
+
overrides environment variables for the LuaLS process.
|
|
309
|
+
:param cwd:
|
|
310
|
+
overrides current working directory for the LuaLS process.
|
|
311
|
+
:param install:
|
|
312
|
+
if false, disables installing LuaLS from GitHub.
|
|
313
|
+
:param reporter:
|
|
314
|
+
a hook that will be called to inform user about installation
|
|
315
|
+
progress. See :class:`ProgressReporter` for API documentation,
|
|
316
|
+
and :class:`DefaultProgressReporter` for an example.
|
|
317
|
+
:param timeout:
|
|
318
|
+
timeout in seconds for connecting to GitHub APIs.
|
|
319
|
+
:param retry:
|
|
320
|
+
retry policy for reading from GitHub and downloading releases.
|
|
321
|
+
The default retry polity uses exponential backoff
|
|
322
|
+
to avoid rate limiting.
|
|
323
|
+
:return:
|
|
324
|
+
resolved LuaLS installation.
|
|
325
|
+
:raises LuaLsError:
|
|
326
|
+
LuaLS not available or installation failed.
|
|
327
|
+
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
if cache_path is None:
|
|
331
|
+
cache_path = default_cache_path()
|
|
332
|
+
else:
|
|
333
|
+
cache_path = pathlib.Path(cache_path)
|
|
334
|
+
cache_path = cache_path.expanduser().resolve()
|
|
335
|
+
|
|
336
|
+
_logger.debug("using lua_ls cache path: %s", cache_path, type="lua-ls")
|
|
337
|
+
|
|
338
|
+
if retry is None:
|
|
339
|
+
retry = urllib3.Retry(10, backoff_factor=0.1)
|
|
340
|
+
|
|
341
|
+
reporter.start()
|
|
342
|
+
try:
|
|
343
|
+
lua_ls_path, path = _check_and_install(
|
|
344
|
+
min_version, cache_path, _get_path(env), install, reporter, timeout, retry
|
|
345
|
+
)
|
|
346
|
+
finally:
|
|
347
|
+
reporter.finish(*sys.exc_info())
|
|
348
|
+
|
|
349
|
+
return LuaLs(
|
|
350
|
+
_lua_ls_path=lua_ls_path,
|
|
351
|
+
_path=path,
|
|
352
|
+
_quiet=quiet,
|
|
353
|
+
_env=env,
|
|
354
|
+
_cwd=cwd,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def default_cache_path() -> pathlib.Path:
|
|
359
|
+
"""
|
|
360
|
+
Return default path where LuaLS binaries should be downloaded to.
|
|
361
|
+
|
|
362
|
+
Currently it is equal to ``pathlib.Path(tempfile.gettempdir()) / "python_lua_ls_cache"``.
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
if path := os.environ.get("LUA_LS_CACHE_PATH", None):
|
|
367
|
+
return pathlib.Path(path)
|
|
368
|
+
else:
|
|
369
|
+
return pathlib.Path(tempfile.gettempdir()) / "python_lua_ls_cache"
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _get_path(env: dict[str, str] | None) -> str:
|
|
373
|
+
path = (env or {}).get("PATH", None)
|
|
374
|
+
if path is None:
|
|
375
|
+
path = os.environ.get("PATH", None)
|
|
376
|
+
if path is None:
|
|
377
|
+
try:
|
|
378
|
+
path = os.confstr("CS_PATH")
|
|
379
|
+
except (AttributeError, ValueError):
|
|
380
|
+
pass
|
|
381
|
+
if path is None:
|
|
382
|
+
path = os.defpath or ""
|
|
383
|
+
return path
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _check_version(
|
|
387
|
+
version: str, lua_ls_path: _PathLike
|
|
388
|
+
) -> _t.Tuple[bool, _t.Optional[str]]:
|
|
389
|
+
version_tuple = tuple(int(c) for c in version.split("."))
|
|
390
|
+
try:
|
|
391
|
+
_logger.debug("checking version of %a", lua_ls_path, type="lua-ls")
|
|
392
|
+
system_version_text_b = subprocess.check_output([lua_ls_path, "--version"])
|
|
393
|
+
system_version_text = system_version_text_b.decode().strip()
|
|
394
|
+
if match := re.search(r"(\d+\.\d+\.\d+)", system_version_text):
|
|
395
|
+
system_version = match.group(1)
|
|
396
|
+
system_version_tuple = tuple(int(c) for c in system_version.split("."))
|
|
397
|
+
if system_version_tuple >= version_tuple:
|
|
398
|
+
return True, system_version
|
|
399
|
+
else:
|
|
400
|
+
_logger.debug(
|
|
401
|
+
"%s is outdated (got %s, required %s)",
|
|
402
|
+
lua_ls_path,
|
|
403
|
+
system_version,
|
|
404
|
+
version,
|
|
405
|
+
type="lua-ls",
|
|
406
|
+
)
|
|
407
|
+
return False, system_version
|
|
408
|
+
else:
|
|
409
|
+
_logger.debug(
|
|
410
|
+
"%s printed invalid version %r",
|
|
411
|
+
lua_ls_path,
|
|
412
|
+
system_version_text,
|
|
413
|
+
type="lua-ls",
|
|
414
|
+
)
|
|
415
|
+
except (subprocess.SubprocessError, OSError, UnicodeDecodeError):
|
|
416
|
+
_logger.debug(
|
|
417
|
+
"%s failed to print its version", lua_ls_path, exc_info=True, type="lua-ls"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return False, None
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _check_and_install(
|
|
424
|
+
version: str,
|
|
425
|
+
cache_path: pathlib.Path,
|
|
426
|
+
path: str,
|
|
427
|
+
install: bool,
|
|
428
|
+
reporter: ProgressReporter,
|
|
429
|
+
timeout: int,
|
|
430
|
+
retry: urllib3.Retry,
|
|
431
|
+
) -> _t.Tuple[pathlib.Path, str]:
|
|
432
|
+
if version.startswith("v"):
|
|
433
|
+
version = version[1:]
|
|
434
|
+
|
|
435
|
+
# Check system lua_ls
|
|
436
|
+
|
|
437
|
+
system_lua_ls_path = shutil.which("lua-language-server", path=path)
|
|
438
|
+
system_version = None
|
|
439
|
+
if system_lua_ls_path:
|
|
440
|
+
can_use_system_lua_ls, system_version = _check_version(
|
|
441
|
+
version, system_lua_ls_path
|
|
442
|
+
)
|
|
443
|
+
if can_use_system_lua_ls:
|
|
444
|
+
_logger.debug(
|
|
445
|
+
"using pre-installed lua-language-server at %s",
|
|
446
|
+
system_lua_ls_path,
|
|
447
|
+
type="lua-ls",
|
|
448
|
+
)
|
|
449
|
+
return pathlib.Path(system_lua_ls_path).expanduser().resolve(), path
|
|
450
|
+
else:
|
|
451
|
+
_logger.debug("pre-installed lua-language-server not found", type="lua-ls")
|
|
452
|
+
|
|
453
|
+
machine = platform.machine().lower()
|
|
454
|
+
if "arm" in machine:
|
|
455
|
+
machine = "arm"
|
|
456
|
+
|
|
457
|
+
return _install(
|
|
458
|
+
version,
|
|
459
|
+
cache_path,
|
|
460
|
+
path,
|
|
461
|
+
install,
|
|
462
|
+
reporter,
|
|
463
|
+
timeout,
|
|
464
|
+
retry,
|
|
465
|
+
machine,
|
|
466
|
+
sys.platform,
|
|
467
|
+
system_lua_ls_path,
|
|
468
|
+
system_version,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _install(
|
|
473
|
+
version: str,
|
|
474
|
+
cache_path: pathlib.Path,
|
|
475
|
+
path: str,
|
|
476
|
+
install: bool,
|
|
477
|
+
reporter: ProgressReporter,
|
|
478
|
+
timeout: int,
|
|
479
|
+
retry: urllib3.Retry,
|
|
480
|
+
machine: str,
|
|
481
|
+
platform: str,
|
|
482
|
+
system_lua_ls_path: str | None,
|
|
483
|
+
system_version: str | None,
|
|
484
|
+
verify: bool = False,
|
|
485
|
+
):
|
|
486
|
+
# Check system compatibility.
|
|
487
|
+
|
|
488
|
+
release_names = {
|
|
489
|
+
("darwin", "arm"): "-darwin-arm64.tar.gz",
|
|
490
|
+
("darwin", "x86_64"): "-darwin-x64.tar.gz",
|
|
491
|
+
("linux", "arm"): "-linux-arm64.tar.gz",
|
|
492
|
+
("linux", "x86_64"): "-linux-x64.tar.gz",
|
|
493
|
+
("win32", "amd64"): "-win32-x64.zip",
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
release_name = release_names.get((platform, machine), None)
|
|
497
|
+
if not install or not release_name:
|
|
498
|
+
if system_lua_ls_path:
|
|
499
|
+
raise LuaLsError(
|
|
500
|
+
f"you have lua-language-server {system_version}, "
|
|
501
|
+
f"but version {version} or newer is required; "
|
|
502
|
+
f"see upgrade instructions "
|
|
503
|
+
f"at https://lua_ls.github.io/#other-install"
|
|
504
|
+
)
|
|
505
|
+
else:
|
|
506
|
+
raise LuaLsError(
|
|
507
|
+
f"lua-language-server is not installed on your system; "
|
|
508
|
+
f"see installation instructions "
|
|
509
|
+
f"at https://lua_ls.github.io/#other-install"
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
# Check cached lua-ls
|
|
513
|
+
|
|
514
|
+
cache_path.mkdir(parents=True, exist_ok=True)
|
|
515
|
+
|
|
516
|
+
if platform == "win32":
|
|
517
|
+
bin_path = cache_path / "bin/lua-language-server.exe"
|
|
518
|
+
else:
|
|
519
|
+
bin_path = cache_path / "bin/lua-language-server"
|
|
520
|
+
if bin_path.exists():
|
|
521
|
+
bin_path.chmod(bin_path.stat().st_mode | stat.S_IEXEC)
|
|
522
|
+
can_use_cached_lua_ls, _ = _check_version(version, bin_path)
|
|
523
|
+
if can_use_cached_lua_ls:
|
|
524
|
+
_logger.debug("using cached lua-language-server", type="lua-ls")
|
|
525
|
+
return bin_path, path
|
|
526
|
+
|
|
527
|
+
# Download binary release.
|
|
528
|
+
|
|
529
|
+
api = github.Github(retry=retry, timeout=timeout)
|
|
530
|
+
|
|
531
|
+
_install_lua_ls(api, timeout, retry, cache_path, reporter, release_name, platform)
|
|
532
|
+
|
|
533
|
+
if verify:
|
|
534
|
+
can_use_cached_lua_ls, _ = _check_version(version, bin_path)
|
|
535
|
+
if not can_use_cached_lua_ls:
|
|
536
|
+
raise LuaLsError(
|
|
537
|
+
"downloaded latest lua-language-server is outdated; "
|
|
538
|
+
"are you sure min_lua_ls_version is correct?",
|
|
539
|
+
)
|
|
540
|
+
elif not bin_path.exists():
|
|
541
|
+
raise LuaLsError(
|
|
542
|
+
f"downloaded latest lua-language-server is broken: "
|
|
543
|
+
f"can't find {bin_path}",
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
return bin_path, path
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _install_lua_ls(
|
|
550
|
+
api: github.Github,
|
|
551
|
+
timeout: int,
|
|
552
|
+
retry: urllib3.Retry,
|
|
553
|
+
cache_path: pathlib.Path,
|
|
554
|
+
reporter: ProgressReporter,
|
|
555
|
+
release_name: str,
|
|
556
|
+
platform: str,
|
|
557
|
+
):
|
|
558
|
+
filter = lambda name: name.endswith(release_name)
|
|
559
|
+
|
|
560
|
+
with tempfile.TemporaryDirectory() as tmp_dir_s:
|
|
561
|
+
tmp_dir = pathlib.Path(tmp_dir_s)
|
|
562
|
+
|
|
563
|
+
try:
|
|
564
|
+
tmp_file = _download_latest_release(
|
|
565
|
+
api,
|
|
566
|
+
timeout,
|
|
567
|
+
retry,
|
|
568
|
+
"lua-language-server",
|
|
569
|
+
"LuaLS/lua-language-server",
|
|
570
|
+
tmp_dir,
|
|
571
|
+
filter,
|
|
572
|
+
reporter,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
reporter.progress(f"processing lua-language-server", 0, 0, 0)
|
|
576
|
+
|
|
577
|
+
_logger.debug("unpacking lua-language-server", type="lua-ls")
|
|
578
|
+
|
|
579
|
+
shutil.unpack_archive(tmp_file, cache_path)
|
|
580
|
+
|
|
581
|
+
if platform == "win32":
|
|
582
|
+
bin_path = cache_path / "bin/lua-language-server.exe"
|
|
583
|
+
else:
|
|
584
|
+
bin_path = cache_path / "bin/lua-language-server"
|
|
585
|
+
bin_path.chmod(bin_path.stat().st_mode | stat.S_IEXEC)
|
|
586
|
+
except Exception as e:
|
|
587
|
+
raise LuaLsError(f"lua-language-server install failed: {e}")
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _download_latest_release(
|
|
591
|
+
api: github.Github,
|
|
592
|
+
timeout: int,
|
|
593
|
+
retry: urllib3.Retry,
|
|
594
|
+
name: str,
|
|
595
|
+
repo_name: str,
|
|
596
|
+
dest: pathlib.Path,
|
|
597
|
+
filter: _t.Callable[[str], bool],
|
|
598
|
+
reporter: ProgressReporter,
|
|
599
|
+
):
|
|
600
|
+
reporter.progress(f"resolving {name}", 0, 0, 0)
|
|
601
|
+
|
|
602
|
+
repo = api.get_repo(repo_name)
|
|
603
|
+
|
|
604
|
+
for release in repo.get_releases():
|
|
605
|
+
if release.draft or release.prerelease:
|
|
606
|
+
continue
|
|
607
|
+
|
|
608
|
+
_logger.debug("found %s release %s", name, release.tag_name, type="lua-ls")
|
|
609
|
+
|
|
610
|
+
for asset in release.assets:
|
|
611
|
+
_logger.debug("trying %s asset %s", name, asset.name, type="lua-ls")
|
|
612
|
+
if filter(asset.name):
|
|
613
|
+
_logger.debug("found %s asset %s", name, asset.name, type="lua-ls")
|
|
614
|
+
basename = asset.name
|
|
615
|
+
browser_download_url = asset.browser_download_url
|
|
616
|
+
break
|
|
617
|
+
else:
|
|
618
|
+
raise LuaLsError(
|
|
619
|
+
f"unable to find {name} release for platform {sys.platform}"
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
break
|
|
623
|
+
else:
|
|
624
|
+
raise LuaLsError(f"unable to find latest {name} release")
|
|
625
|
+
|
|
626
|
+
_logger.debug("downloading %s from %s", name, browser_download_url, type="lua-ls")
|
|
627
|
+
|
|
628
|
+
with requests.Session() as session:
|
|
629
|
+
adapter = requests.adapters.HTTPAdapter(max_retries=retry)
|
|
630
|
+
session.mount("https://", adapter)
|
|
631
|
+
session.mount("http://", adapter)
|
|
632
|
+
|
|
633
|
+
with requests.get(browser_download_url, stream=True, timeout=timeout) as stream:
|
|
634
|
+
stream.raise_for_status()
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
size = int(stream.headers["content-length"])
|
|
638
|
+
except (KeyError, ValueError):
|
|
639
|
+
size = -1
|
|
640
|
+
downloaded = 0
|
|
641
|
+
|
|
642
|
+
reporter.progress(f"downloading {name}", downloaded, size, 0)
|
|
643
|
+
|
|
644
|
+
start = datetime.datetime.now()
|
|
645
|
+
|
|
646
|
+
with open(dest / basename, "wb") as dest_file:
|
|
647
|
+
for chunk in stream.iter_content(64 * 1024):
|
|
648
|
+
dest_file.write(chunk)
|
|
649
|
+
if size:
|
|
650
|
+
# note: this does not take content-encoding into account.
|
|
651
|
+
# our contents are not encoded, though, so this is fine.
|
|
652
|
+
time = (datetime.datetime.now() - start).total_seconds()
|
|
653
|
+
downloaded += len(chunk)
|
|
654
|
+
speed = downloaded / time if time else 0
|
|
655
|
+
reporter.progress(
|
|
656
|
+
f"downloading {name}", downloaded, size, speed
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
return dest / basename
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
if __name__ == "__main__":
|
|
663
|
+
|
|
664
|
+
def main():
|
|
665
|
+
import argparse
|
|
666
|
+
|
|
667
|
+
parser = argparse.ArgumentParser()
|
|
668
|
+
parser.add_argument("platform")
|
|
669
|
+
parser.add_argument("machine")
|
|
670
|
+
parser.add_argument("path", type=pathlib.Path)
|
|
671
|
+
|
|
672
|
+
_logger.setLevel("DEBUG")
|
|
673
|
+
_logger.logger.addHandler(
|
|
674
|
+
logging.NewLineStreamHandler(logging.SafeEncodingWriter(sys.stderr))
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
args = parser.parse_args()
|
|
678
|
+
|
|
679
|
+
_install(
|
|
680
|
+
"3.0.0",
|
|
681
|
+
args.path,
|
|
682
|
+
_get_path(None),
|
|
683
|
+
True,
|
|
684
|
+
DefaultProgressReporter(),
|
|
685
|
+
15,
|
|
686
|
+
urllib3.Retry(10, backoff_factor=0.1),
|
|
687
|
+
args.machine,
|
|
688
|
+
args.platform,
|
|
689
|
+
None,
|
|
690
|
+
None,
|
|
691
|
+
False,
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
main()
|
sphinx_lua_ls/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Tamika Nomara
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|