Coverage for src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py: 26%
83 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
1import random
2import textwrap
3from argparse import BooleanOptionalAction
5from debputy.commands.debputy_cmd.context import ROOT_COMMAND, CommandContext, add_arg
6from debputy.lsp.lsp_reference_keyword import ALL_PUBLIC_NAMED_STYLES
7from debputy.util import _error
9_EDITOR_SNIPPETS = {
10 "emacs": "emacs+eglot",
11 "emacs+eglot": textwrap.dedent(
12 """\
13 ;; `deputy lsp server` glue for emacs eglot (eglot is built-in these days)
14 ;;
15 ;; Add to ~/.emacs or ~/.emacs.d/init.el and then activate via `M-x eglot`.
16 ;;
17 ;; Requires: apt install elpa-dpkg-dev-el elpa-yaml-mode
18 ;; Recommends: apt install elpa-markdown-mode
20 ;; Make emacs recognize debian/debputy.manifest as a YAML file
21 (add-to-list 'auto-mode-alist '("/debian/debputy.manifest\\'" . yaml-mode))
22 ;; Inform eglot about the debputy LSP
23 (with-eval-after-load 'eglot
24 (add-to-list 'eglot-server-programs
25 '(
26 (
27 ;; Requires elpa-dpkg-dev-el (>= 37.12)
28 (debian-autopkgtest-control-mode :language-id "debian/tests/control")
29 ;; Requires elpa-dpkg-dev-el
30 (debian-control-mode :language-id "debian/control")
31 (debian-changelog-mode :language-id "debian/changelog")
32 (debian-copyright-mode :language-id "debian/copyright")
33 ;; No language id for these atm.
34 makefile-gmake-mode
35 ;; Requires elpa-yaml-mode
36 yaml-mode
37 )
38 . ("debputy" "lsp" "server")
39 )))
41 ;; Auto-start eglot for the relevant modes.
42 (add-hook 'debian-control-mode-hook 'eglot-ensure)
43 ;; Requires elpa-dpkg-dev-el (>= 37.12)
44 ;; Technically, the `eglot-ensure` works before then, but it causes a
45 ;; visible and very annoying long delay on opening the first changelog.
46 ;; It still has a minor delay in 37.12, which may still be too long for
47 ;; for your preference. In that case, comment it out.
48 (add-hook 'debian-changelog-mode-hook 'eglot-ensure)
49 (add-hook 'debian-copyright-mode-hook 'eglot-ensure)
50 ;; Requires elpa-dpkg-dev-el (>= 37.12)
51 (add-hook 'debian-autopkgtest-control-mode-hook 'eglot-ensure)
52 (add-hook 'makefile-gmake-mode-hook 'eglot-ensure)
53 (add-hook 'yaml-mode-hook 'eglot-ensure)
54 """
55 ),
56 "vim": "vim+youcompleteme",
57 "vim+youcompleteme": textwrap.dedent(
58 """\
59 # debputy lsp server glue for vim with vim-youcompleteme. Add to ~/.vimrc
60 #
61 # Requires: apt install vim-youcompleteme
62 # - Your vim **MUST** be a provider of `vim-python3` (`vim-nox`/`vim-gtk`, etc.)
64 # Make vim recognize debputy.manifest as YAML file
65 au BufNewFile,BufRead debputy.manifest setf yaml
66 # Inform vim/ycm about the debputy LSP
67 # - NB: No known support for debian/tests/control that we can hook into.
68 # Feel free to provide one :)
69 let g:ycm_language_server = [
70 \\ { 'name': 'debputy',
71 \\ 'filetypes': [ 'debcontrol', 'debcopyright', 'debchangelog', 'make', 'yaml'],
72 \\ 'cmdline': [ 'debputy', 'lsp', 'server', '--ignore-language-ids' ]
73 \\ },
74 \\ ]
76 packadd! youcompleteme
77 # Add relevant ycm keybinding such as:
78 # nmap <leader>d <plug>(YCMHover)
79 """
80 ),
81 "vim+vim9lsp": textwrap.dedent(
82 """\
83 # debputy lsp server glue for vim with vim9 lsp. Add to ~/.vimrc
84 #
85 # Requires https://github.com/yegappan/lsp to be in your packages path
87 vim9script
89 # Make vim recognize debputy.manifest as YAML file
90 autocmd BufNewFile,BufRead debputy.manifest setfiletype yaml
92 packadd! lsp
94 final lspServers: list<dict<any>> = []
96 if executable('debputy')
97 lspServers->add({
98 filetype: ['debcontrol', 'debcopyright', 'debchangelog', 'make', 'yaml'],
99 path: 'debputy',
100 args: ['lsp', 'server', '--ignore-language-ids']
101 })
102 endif
104 autocmd User LspSetup g:LspOptionsSet({semanticHighlight: true})
105 autocmd User LspSetup g:LspAddServer(lspServers)
106 """
107 ),
108 "neovim": "neovim+nvim-lspconfig",
109 "neovim+nvim-lspconfig": textwrap.dedent(
110 """\
111 # debputy lsp server glue for neovim with nvim-lspconfig. Add to ~/.config/nvim/init.lua
112 #
113 # Requires https://github.com/neovim/nvim-lspconfig to be in your packages path
115 require("lspconfig").debputy.setup {capabilities = capabilities}
117 # Make vim recognize debputy.manifest as YAML file
118 vim.filetype.add({filename = {["debputy.manifest"] = "yaml"})
119 """
120 ),
121}
124lsp_command = ROOT_COMMAND.add_dispatching_subcommand(
125 "lsp",
126 dest="lsp_command",
127 help_description="Language server related subcommands",
128)
131@lsp_command.register_subcommand(
132 "server",
133 log_only_to_stderr=True,
134 help_description="Start the language server",
135 argparser=[
136 add_arg(
137 "--tcp",
138 action="store_true",
139 help="Use TCP server",
140 ),
141 add_arg(
142 "--ws",
143 action="store_true",
144 help="Use WebSocket server",
145 ),
146 add_arg(
147 "--host",
148 default="127.0.0.1",
149 help="Bind to this address (Use with --tcp / --ws)",
150 ),
151 add_arg(
152 "--port",
153 type=int,
154 default=2087,
155 help="Bind to this port (Use with --tcp / --ws)",
156 ),
157 add_arg(
158 "--ignore-language-ids",
159 dest="trust_language_ids",
160 default=True,
161 action="store_false",
162 help="Disregard language IDs from the editor (rely solely on filename instead)",
163 ),
164 add_arg(
165 "--force-locale",
166 dest="forced_locale",
167 default=None,
168 action="store",
169 help="Disregard locale from editor and always use provided locale code (translations)",
170 ),
171 ],
172)
173def lsp_server_cmd(context: CommandContext) -> None:
174 parsed_args = context.parsed_args
176 feature_set = context.load_plugins()
178 from debputy.lsp.lsp_self_check import assert_can_start_lsp
180 assert_can_start_lsp()
182 from debputy.lsp.lsp_features import (
183 ensure_lsp_features_are_loaded,
184 )
185 from debputy.lsp.lsp_dispatch import DEBPUTY_LANGUAGE_SERVER
187 ensure_lsp_features_are_loaded()
188 debputy_language_server = DEBPUTY_LANGUAGE_SERVER
189 debputy_language_server.plugin_feature_set = feature_set
190 debputy_language_server.dctrl_parser = context.dctrl_parser
191 debputy_language_server.trust_language_ids = parsed_args.trust_language_ids
192 debputy_language_server.forced_locale = parsed_args.forced_locale
194 debputy_language_server.finish_startup_initialization()
196 if parsed_args.tcp and parsed_args.ws:
197 _error("Sorry, --tcp and --ws are mutually exclusive")
199 if parsed_args.tcp:
200 debputy_language_server.start_tcp(parsed_args.host, parsed_args.port)
201 elif parsed_args.ws:
202 debputy_language_server.start_ws(parsed_args.host, parsed_args.port)
203 else:
204 debputy_language_server.start_io()
207@lsp_command.register_subcommand(
208 "editor-config",
209 help_description="Provide editor configuration snippets",
210 argparser=[
211 add_arg(
212 "editor_name",
213 metavar="editor",
214 choices=_EDITOR_SNIPPETS,
215 default=None,
216 nargs="?",
217 help="The editor to provide a snippet for",
218 ),
219 ],
220)
221def lsp_editor_glue(context: CommandContext) -> None:
222 editor_name = context.parsed_args.editor_name
224 if editor_name is None:
225 content = []
226 for editor_name, payload in _EDITOR_SNIPPETS.items():
227 alias_of = ""
228 if payload in _EDITOR_SNIPPETS:
229 alias_of = f" (short for: {payload})"
230 content.append((editor_name, alias_of))
231 max_name = max(len(c[0]) for c in content)
232 print(
233 "This version of debputy has instructions or editor config snippets for the following editors: "
234 )
235 print()
236 for editor_name, alias_of in content:
237 print(f" * {editor_name:<{max_name}}{alias_of}")
238 print()
239 choice = random.Random().choice(list(_EDITOR_SNIPPETS))
240 print(
241 f"Use `debputy editor-config {choice}` (as an example) to see the instructions for a concrete editor."
242 )
243 return
244 result = _EDITOR_SNIPPETS[editor_name]
245 while result in _EDITOR_SNIPPETS:
246 result = _EDITOR_SNIPPETS[result]
247 print(result)
250@lsp_command.register_subcommand(
251 "features",
252 help_description="Describe language ids and features",
253)
254def lsp_describe_features(context: CommandContext) -> None:
256 from debputy.lsp.lsp_self_check import assert_can_start_lsp
258 try:
259 from debputy.lsp.lsp_features import describe_lsp_features
260 except ImportError:
261 assert_can_start_lsp()
262 raise AssertionError(
263 "Cannot load the language server features but `assert_can_start_lsp` did not fail"
264 )
266 describe_lsp_features(context)
269@ROOT_COMMAND.register_subcommand(
270 "lint",
271 log_only_to_stderr=True,
272 help_description="Provide diagnostics for the packaging (like `lsp server` except no editor is needed)",
273 argparser=[
274 add_arg(
275 "--spellcheck",
276 dest="spellcheck",
277 action="store_true",
278 help="Enable spellchecking",
279 ),
280 add_arg(
281 "--auto-fix",
282 dest="auto_fix",
283 action="store_true",
284 help="Automatically fix problems with trivial or obvious corrections.",
285 ),
286 add_arg(
287 "--linter-exit-code",
288 dest="linter_exit_code",
289 default=True,
290 action=BooleanOptionalAction,
291 help='Enable or disable the "linter" convention of exiting with an error if severe issues were found',
292 ),
293 add_arg(
294 "--lint-report-format",
295 dest="lint_report_format",
296 default="term",
297 choices=["term", "junit4-xml"],
298 help="The report output format",
299 ),
300 add_arg(
301 "--report-output",
302 dest="report_output",
303 default=None,
304 action="store",
305 help="Where to place the report (for report formats that generate files/directory reports)",
306 ),
307 add_arg(
308 "--warn-about-check-manifest",
309 dest="warn_about_check_manifest",
310 default=True,
311 action=BooleanOptionalAction,
312 help="Warn about limitations that check-manifest would cover if d/debputy.manifest is present",
313 ),
314 ],
315)
316def lint_cmd(context: CommandContext) -> None:
317 try:
318 import lsprotocol
319 except ImportError:
320 _error("This feature requires lsprotocol (apt-get install python3-lsprotocol)")
322 from debputy.linting.lint_impl import perform_linting
324 context.must_be_called_in_source_root()
325 perform_linting(context)
328@ROOT_COMMAND.register_subcommand(
329 "reformat",
330 help_description="Reformat the packaging files based on the packaging/maintainer rules",
331 argparser=[
332 add_arg(
333 "--style",
334 dest="named_style",
335 choices=ALL_PUBLIC_NAMED_STYLES,
336 default=None,
337 help="The formatting style to use (overrides packaging style).",
338 ),
339 add_arg(
340 "--auto-fix",
341 dest="auto_fix",
342 default=True,
343 action=BooleanOptionalAction,
344 help="Whether to automatically apply any style changes.",
345 ),
346 add_arg(
347 "--linter-exit-code",
348 dest="linter_exit_code",
349 default=True,
350 action=BooleanOptionalAction,
351 help='Enable or disable the "linter" convention of exiting with an error if issues were found',
352 ),
353 add_arg(
354 "--supported-style-is-required",
355 dest="supported_style_required",
356 default=True,
357 action="store_true",
358 help="Fail with an error if a supported style cannot be identified.",
359 ),
360 add_arg(
361 "--unknown-or-unsupported-style-is-ok",
362 dest="supported_style_required",
363 action="store_false",
364 help="Do not exit with an error if no supported style can be identified. Useful for general"
365 ' pipelines to implement "reformat if possible"',
366 ),
367 add_arg(
368 "--missing-style-is-ok",
369 dest="supported_style_required",
370 action="store_false",
371 help="[Deprecated] Use --unknown-or-unsupported-style-is-ok instead",
372 ),
373 ],
374)
375def reformat_cmd(context: CommandContext) -> None:
376 try:
377 import lsprotocol
378 except ImportError:
379 _error("This feature requires lsprotocol (apt-get install python3-lsprotocol)")
381 from debputy.linting.lint_impl import perform_reformat
383 context.must_be_called_in_source_root()
384 perform_reformat(context, named_style=context.parsed_args.named_style)
387def ensure_lint_and_lsp_commands_are_loaded() -> None:
388 # Loading the module does the heavy lifting
389 # However, having this function means that we do not have an "unused" import that some tool
390 # gets tempted to remove
391 assert ROOT_COMMAND.has_command("lsp")
392 assert ROOT_COMMAND.has_command("lint")
393 assert ROOT_COMMAND.has_command("reformat")