spin 0.12__tar.gz → 0.13__tar.gz

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.
Files changed (34) hide show
  1. {spin-0.12/spin.egg-info → spin-0.13}/PKG-INFO +48 -38
  2. {spin-0.12 → spin-0.13}/README.md +45 -36
  3. {spin-0.12 → spin-0.13}/pyproject.toml +2 -1
  4. {spin-0.12 → spin-0.13}/spin/__init__.py +1 -1
  5. {spin-0.12 → spin-0.13}/spin/__main__.py +13 -5
  6. {spin-0.12 → spin-0.13}/spin/cmds/meson.py +76 -18
  7. {spin-0.12 → spin-0.13}/spin/cmds/meta.py +5 -1
  8. spin-0.13/spin/cmds/util.py +179 -0
  9. {spin-0.12 → spin-0.13}/spin/color_format.py +1 -1
  10. spin-0.13/spin/tests/__init__.py +0 -0
  11. {spin-0.12 → spin-0.13}/spin/tests/conftest.py +13 -4
  12. {spin-0.12 → spin-0.13}/spin/tests/test_build_cmds.py +24 -17
  13. {spin-0.12 → spin-0.13}/spin/tests/test_cli.py +4 -4
  14. {spin-0.12 → spin-0.13}/spin/tests/test_editable.py +3 -3
  15. spin-0.13/spin/tests/test_extend_command.py +83 -0
  16. spin-0.13/spin/tests/test_test.py +51 -0
  17. spin-0.13/spin/tests/test_util.py +10 -0
  18. {spin-0.12 → spin-0.13}/spin/tests/testutil.py +5 -0
  19. {spin-0.12 → spin-0.13/spin.egg-info}/PKG-INFO +48 -38
  20. {spin-0.12 → spin-0.13}/spin.egg-info/SOURCES.txt +3 -0
  21. {spin-0.12 → spin-0.13}/spin.egg-info/requires.txt +1 -1
  22. spin-0.12/spin/cmds/util.py +0 -96
  23. spin-0.12/spin/tests/test_test.py +0 -29
  24. {spin-0.12 → spin-0.13}/LICENSE +0 -0
  25. {spin-0.12 → spin-0.13}/setup.cfg +0 -0
  26. {spin-0.12 → spin-0.13}/spin/cmds/__init__.py +0 -0
  27. {spin-0.12 → spin-0.13}/spin/cmds/build.py +0 -0
  28. {spin-0.12 → spin-0.13}/spin/cmds/pip.py +0 -0
  29. {spin-0.12 → spin-0.13}/spin/containers.py +0 -0
  30. {spin-0.12 → spin-0.13}/spin/sectioned_help.py +0 -0
  31. {spin-0.12 → spin-0.13}/spin/tests/test_meson.py +0 -0
  32. {spin-0.12 → spin-0.13}/spin.egg-info/dependency_links.txt +0 -0
  33. {spin-0.12 → spin-0.13}/spin.egg-info/entry_points.txt +0 -0
  34. {spin-0.12 → spin-0.13}/spin.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spin
3
- Version: 0.12
3
+ Version: 0.13
4
4
  Summary: Developer tool for scientific Python libraries
5
5
  Maintainer-email: Scientific Python <spin@discuss.scientific-python.org>
6
6
  License: BSD 3-Clause License
@@ -46,6 +46,7 @@ Classifier: Programming Language :: Python :: 3.9
46
46
  Classifier: Programming Language :: Python :: 3.10
47
47
  Classifier: Programming Language :: Python :: 3.11
48
48
  Classifier: Programming Language :: Python :: 3.12
49
+ Classifier: Programming Language :: Python :: 3.13
49
50
  Classifier: Programming Language :: Python :: 3 :: Only
50
51
  Requires-Python: >=3.8
51
52
  Description-Content-Type: text/markdown
@@ -55,7 +56,7 @@ Requires-Dist: tomli; python_version < "3.11"
55
56
  Requires-Dist: colorama; platform_system == "Windows"
56
57
  Requires-Dist: importlib_metadata>=7
57
58
  Provides-Extra: lint
58
- Requires-Dist: pre-commit==3.8.0; extra == "lint"
59
+ Requires-Dist: pre-commit==4.0.1; extra == "lint"
59
60
  Provides-Extra: dev
60
61
  Requires-Dist: changelist==0.5; extra == "dev"
61
62
 
@@ -75,6 +76,28 @@ When many of the build and installation commands changed, it made sense to abstr
75
76
  _Note:_ We now have experimental builds for editable installs.
76
77
  Most of the Meson commands listed below should work "out of the box" for those.
77
78
 
79
+ <!--TOC-->
80
+
81
+ - [Installation](#installation)
82
+ - [Configuration](#configuration)
83
+ - [Command sections](#command-sections)
84
+ - [Running](#running)
85
+ - [Built-in commands](#built-in-commands)
86
+ - [Meson](#meson)
87
+ - [Build (PEP 517 builder)](#build-pep-517-builder)
88
+ - [pip (Package Installer for Python)](#pip-package-installer-for-python)
89
+ - [Meta (commands that operate on commands)](#meta-commands-that-operate-on-commands)
90
+ - [🧪 Custom commands](#-custom-commands)
91
+ - [Configuration](#configuration-1)
92
+ - [Argument overrides](#argument-overrides)
93
+ - [Advanced: adding arguments to built-in commands](#advanced-adding-arguments-to-built-in-commands)
94
+ - [Advanced: override Meson CLI](#advanced-override-meson-cli)
95
+ - [FAQ](#faq)
96
+ - [For contributors](#for-contributors)
97
+ - [History](#history)
98
+
99
+ <!--TOC-->
100
+
78
101
  ## Installation
79
102
 
80
103
  ```
@@ -240,52 +263,39 @@ click options or function keywords:
240
263
 
241
264
  ### Advanced: adding arguments to built-in commands
242
265
 
243
- Instead of rewriting a command from scratch, a project may want to add a flag to a built-in `spin` command, or perhaps do some pre- or post-processing.
244
- For this, we have to use an internal Click concept called a [context](https://click.palletsprojects.com/en/8.1.x/complex/#contexts).
245
- Fortunately, we don't need to know anything about contexts other than that they allow us to execute commands within commands.
266
+ Instead of rewriting a command from scratch, a project may simply want to add a flag to an existing `spin` command, or perhaps do some pre- or post-processing.
267
+ For this purpose, we provide the `spin.util.extend_cmd` decorator.
246
268
 
247
- We proceed by duplicating the function header of the existing command, and adding our own flag:
269
+ Here, we show how to add a `--extra` flag to the existing `build` function:
248
270
 
249
271
  ```python
250
- from spin.cmds import meson
251
-
252
- # Take this from the built-in implementation, in `spin.cmds.meson.build`:
253
-
272
+ import spin
254
273
 
255
- @click.command()
256
- @click.argument("meson_args", nargs=-1)
257
- @click.option("-j", "--jobs", help="Number of parallel tasks to launch", type=int)
258
- @click.option("--clean", is_flag=True, help="Clean build directory before build")
259
- @click.option(
260
- "-v", "--verbose", is_flag=True, help="Print all build output, even installation"
261
- )
262
-
263
- # This is our new option
264
- @click.option("--custom-arg/--no-custom-arg")
265
274
 
266
- # This tells spin that we will need a context, which we
267
- # can use to invoke the built-in command
268
- @click.pass_context
275
+ @click.option("-e", "--extra", help="Extra test flag")
276
+ @spin.util.extend_command(spin.cmds.meson.build)
277
+ def build_extend(*, parent_callback, extra=None, **kwargs):
278
+ """
279
+ This version of build also provides the EXTRA flag, that can be used
280
+ to specify an extra integer argument.
281
+ """
282
+ print(f"Preparing for build with {extra=}")
283
+ parent_callback(**kwargs)
284
+ print("Finalizing build...")
285
+ ```
269
286
 
270
- # This is the original function signature, plus our new flag
271
- def build(ctx, meson_args, jobs=None, clean=False, verbose=False, custom_arg=False):
272
- """Docstring goes here. You may want to copy and customize the original."""
287
+ Note that `build_extend` receives the parent command callback (the function the `build` command would have executed) as its first argument.
273
288
 
274
- # Do something with the new option
275
- print("The value of custom arg is:", custom_arg)
289
+ The matching entry in `pyproject.toml` is:
276
290
 
277
- # The spin `build` command doesn't know anything about `custom_arg`,
278
- # so don't send it on.
279
- del ctx.params["custom_arg"]
291
+ ```
292
+ "Build" = [".spin/cmds.py:build_extend"]
293
+ ```
280
294
 
281
- # Call the built-in `build` command, passing along
282
- # all arguments and options.
283
- ctx.forward(meson.build)
295
+ The `extend_cmd` decorator also accepts a `doc` argument, for setting the new command's `--help` description.
296
+ The function documentation ("This version of build...") is also appended.
284
297
 
285
- # Also see:
286
- # - https://click.palletsprojects.com/en/8.1.x/api/#click.Context.forward
287
- # - https://click.palletsprojects.com/en/8.1.x/api/#click.Context.invoke
288
- ```
298
+ Finally, `remove_args` is a tuple of arguments that are not inherited from the original command.
289
299
 
290
300
  ### Advanced: override Meson CLI
291
301
 
@@ -14,6 +14,28 @@ When many of the build and installation commands changed, it made sense to abstr
14
14
  _Note:_ We now have experimental builds for editable installs.
15
15
  Most of the Meson commands listed below should work "out of the box" for those.
16
16
 
17
+ <!--TOC-->
18
+
19
+ - [Installation](#installation)
20
+ - [Configuration](#configuration)
21
+ - [Command sections](#command-sections)
22
+ - [Running](#running)
23
+ - [Built-in commands](#built-in-commands)
24
+ - [Meson](#meson)
25
+ - [Build (PEP 517 builder)](#build-pep-517-builder)
26
+ - [pip (Package Installer for Python)](#pip-package-installer-for-python)
27
+ - [Meta (commands that operate on commands)](#meta-commands-that-operate-on-commands)
28
+ - [🧪 Custom commands](#-custom-commands)
29
+ - [Configuration](#configuration-1)
30
+ - [Argument overrides](#argument-overrides)
31
+ - [Advanced: adding arguments to built-in commands](#advanced-adding-arguments-to-built-in-commands)
32
+ - [Advanced: override Meson CLI](#advanced-override-meson-cli)
33
+ - [FAQ](#faq)
34
+ - [For contributors](#for-contributors)
35
+ - [History](#history)
36
+
37
+ <!--TOC-->
38
+
17
39
  ## Installation
18
40
 
19
41
  ```
@@ -179,52 +201,39 @@ click options or function keywords:
179
201
 
180
202
  ### Advanced: adding arguments to built-in commands
181
203
 
182
- Instead of rewriting a command from scratch, a project may want to add a flag to a built-in `spin` command, or perhaps do some pre- or post-processing.
183
- For this, we have to use an internal Click concept called a [context](https://click.palletsprojects.com/en/8.1.x/complex/#contexts).
184
- Fortunately, we don't need to know anything about contexts other than that they allow us to execute commands within commands.
204
+ Instead of rewriting a command from scratch, a project may simply want to add a flag to an existing `spin` command, or perhaps do some pre- or post-processing.
205
+ For this purpose, we provide the `spin.util.extend_cmd` decorator.
185
206
 
186
- We proceed by duplicating the function header of the existing command, and adding our own flag:
207
+ Here, we show how to add a `--extra` flag to the existing `build` function:
187
208
 
188
209
  ```python
189
- from spin.cmds import meson
190
-
191
- # Take this from the built-in implementation, in `spin.cmds.meson.build`:
192
-
210
+ import spin
193
211
 
194
- @click.command()
195
- @click.argument("meson_args", nargs=-1)
196
- @click.option("-j", "--jobs", help="Number of parallel tasks to launch", type=int)
197
- @click.option("--clean", is_flag=True, help="Clean build directory before build")
198
- @click.option(
199
- "-v", "--verbose", is_flag=True, help="Print all build output, even installation"
200
- )
201
-
202
- # This is our new option
203
- @click.option("--custom-arg/--no-custom-arg")
204
212
 
205
- # This tells spin that we will need a context, which we
206
- # can use to invoke the built-in command
207
- @click.pass_context
213
+ @click.option("-e", "--extra", help="Extra test flag")
214
+ @spin.util.extend_command(spin.cmds.meson.build)
215
+ def build_extend(*, parent_callback, extra=None, **kwargs):
216
+ """
217
+ This version of build also provides the EXTRA flag, that can be used
218
+ to specify an extra integer argument.
219
+ """
220
+ print(f"Preparing for build with {extra=}")
221
+ parent_callback(**kwargs)
222
+ print("Finalizing build...")
223
+ ```
208
224
 
209
- # This is the original function signature, plus our new flag
210
- def build(ctx, meson_args, jobs=None, clean=False, verbose=False, custom_arg=False):
211
- """Docstring goes here. You may want to copy and customize the original."""
225
+ Note that `build_extend` receives the parent command callback (the function the `build` command would have executed) as its first argument.
212
226
 
213
- # Do something with the new option
214
- print("The value of custom arg is:", custom_arg)
227
+ The matching entry in `pyproject.toml` is:
215
228
 
216
- # The spin `build` command doesn't know anything about `custom_arg`,
217
- # so don't send it on.
218
- del ctx.params["custom_arg"]
229
+ ```
230
+ "Build" = [".spin/cmds.py:build_extend"]
231
+ ```
219
232
 
220
- # Call the built-in `build` command, passing along
221
- # all arguments and options.
222
- ctx.forward(meson.build)
233
+ The `extend_cmd` decorator also accepts a `doc` argument, for setting the new command's `--help` description.
234
+ The function documentation ("This version of build...") is also appended.
223
235
 
224
- # Also see:
225
- # - https://click.palletsprojects.com/en/8.1.x/api/#click.Context.forward
226
- # - https://click.palletsprojects.com/en/8.1.x/api/#click.Context.invoke
227
- ```
236
+ Finally, `remove_args` is a tuple of arguments that are not inherited from the original command.
228
237
 
229
238
  ### Advanced: override Meson CLI
230
239
 
@@ -20,6 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.10",
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
23
24
  "Programming Language :: Python :: 3 :: Only",
24
25
  ]
25
26
  dependencies = [
@@ -34,7 +35,7 @@ dynamic = ['version']
34
35
  spin = "spin.__main__:main"
35
36
 
36
37
  [project.optional-dependencies]
37
- lint = ["pre-commit == 3.8.0"]
38
+ lint = ["pre-commit == 4.0.1"]
38
39
  dev = ["changelist == 0.5"]
39
40
 
40
41
  [project.urls]
@@ -6,7 +6,7 @@ For usage information see `python -m spin`.
6
6
  Read more at https://github.com/scientific-python/spin
7
7
  """
8
8
 
9
- __version__ = "0.12"
9
+ __version__ = "0.13"
10
10
 
11
11
  from .cmds import util
12
12
 
@@ -131,6 +131,8 @@ def main():
131
131
  }
132
132
  cmd_default_kwargs = toml_config.get("tool.spin.kwargs", {})
133
133
 
134
+ custom_module_cache = {}
135
+
134
136
  for section, cmds in config_cmds.items():
135
137
  for cmd in cmds:
136
138
  if cmd not in commands:
@@ -147,11 +149,17 @@ def main():
147
149
  else:
148
150
  try:
149
151
  path, func = cmd.split(":")
150
- spec = importlib.util.spec_from_file_location(
151
- "custom_mod", path
152
- )
153
- mod = importlib.util.module_from_spec(spec)
154
- spec.loader.exec_module(mod)
152
+
153
+ if path not in custom_module_cache:
154
+ spec = importlib.util.spec_from_file_location(
155
+ "custom_mod", path
156
+ )
157
+ mod = importlib.util.module_from_spec(spec)
158
+ spec.loader.exec_module(mod)
159
+ custom_module_cache[path] = mod
160
+ else:
161
+ mod = custom_module_cache[path]
162
+
155
163
  except FileNotFoundError:
156
164
  print(
157
165
  f"!! Could not find file `{path}` to load custom command `{cmd}`.\n"
@@ -4,6 +4,7 @@ import json
4
4
  import os
5
5
  import re
6
6
  import shutil
7
+ import signal
7
8
  import sys
8
9
  from enum import Enum
9
10
  from pathlib import Path
@@ -230,18 +231,30 @@ def _check_coverage_tool_installation(coverage_type: GcovReportFormat, build_dir
230
231
  )
231
232
 
232
233
 
234
+ if sys.platform.startswith("win"):
235
+ DEFAULT_PREFIX = "C:/"
236
+ else:
237
+ DEFAULT_PREFIX = "/usr"
238
+
233
239
  build_dir_option = click.option(
234
240
  "-C",
235
241
  "--build-dir",
236
242
  default="build",
237
243
  show_envvar=True,
244
+ metavar="BUILD_DIR",
238
245
  envvar="SPIN_BUILD_DIR",
239
246
  help="Meson build directory; package is installed into './{build-dir}-install'.",
240
247
  )
241
248
 
242
249
 
243
250
  @click.command()
244
- @click.option("-j", "--jobs", help="Number of parallel tasks to launch", type=int)
251
+ @click.option(
252
+ "-j",
253
+ "--jobs",
254
+ metavar="N_JOBS",
255
+ help="Number of parallel tasks to launch",
256
+ type=int,
257
+ )
245
258
  @click.option("--clean", is_flag=True, help="Clean build directory before build")
246
259
  @click.option(
247
260
  "-v", "--verbose", is_flag=True, help="Print detailed build and installation output"
@@ -251,6 +264,13 @@ build_dir_option = click.option(
251
264
  is_flag=True,
252
265
  help="Enable C code coverage using `gcov`. Use `spin test --gcov` to generate reports.",
253
266
  )
267
+ @click.option(
268
+ "--prefix",
269
+ help="The build prefix, passed directly to meson.",
270
+ type=str,
271
+ metavar="PREFIX",
272
+ default=DEFAULT_PREFIX,
273
+ )
254
274
  @click.argument("meson_args", nargs=-1)
255
275
  @build_dir_option
256
276
  def build(
@@ -262,6 +282,7 @@ def build(
262
282
  gcov=False,
263
283
  quiet=False,
264
284
  build_dir=None,
285
+ prefix=None,
265
286
  ):
266
287
  """🔧 Build package with Meson/ninja
267
288
 
@@ -312,7 +333,7 @@ def build(
312
333
  if gcov:
313
334
  meson_args = meson_args + ["-Db_coverage=true"]
314
335
 
315
- setup_cmd = _meson_cli() + ["setup", build_dir, "--prefix=/usr"] + meson_args
336
+ setup_cmd = _meson_cli() + ["setup", build_dir, f"--prefix={prefix}"] + meson_args
316
337
 
317
338
  if clean:
318
339
  print(f"Removing `{build_dir}`")
@@ -480,18 +501,23 @@ def test(
480
501
 
481
502
  package = cfg.get("tool.spin.package", None)
482
503
  if package is None:
483
- print(
484
- "Please specify `package = packagename` under `tool.spin` section of `pyproject.toml`"
504
+ click.secho(
505
+ "Please specify `package = packagename` under `tool.spin` section of `pyproject.toml`",
506
+ fg="bright_red",
485
507
  )
486
508
  raise SystemExit(1)
487
509
 
488
510
  # User did not specify what to test, so we test
489
- # the full package
511
+ # the full package, or the tests directory if that is present
490
512
  if not (pytest_args or tests):
491
- pytest_args = ("--pyargs", package)
513
+ if os.path.isdir("./tests"):
514
+ # tests dir exists, presuming you are not shipping tests
515
+ # with your package, and prefer to run those instead
516
+ pytest_args = (os.path.abspath("./tests"),)
517
+ else:
518
+ pytest_args = ("--pyargs", package)
492
519
  elif tests:
493
520
  if (os.path.sep in tests) or ("/" in tests):
494
- # Tests specified as file path
495
521
  pytest_args = pytest_args + (tests,)
496
522
  else:
497
523
  # Otherwise tests given as modules
@@ -503,7 +529,7 @@ def test(
503
529
  "Error: cannot generate coverage report for editable installs",
504
530
  fg="bright_red",
505
531
  )
506
- raise SystemExit(-1)
532
+ raise SystemExit(1)
507
533
 
508
534
  build_cmd = _get_configured_command("build")
509
535
  if build_cmd:
@@ -536,9 +562,6 @@ def test(
536
562
  if (n_jobs != "1") and ("-n" not in pytest_args):
537
563
  pytest_args = ("-n", str(n_jobs)) + pytest_args
538
564
 
539
- if not any("--import-mode" in arg for arg in pytest_args):
540
- pytest_args = ("--import-mode=importlib",) + pytest_args
541
-
542
565
  if verbose:
543
566
  pytest_args = ("-v",) + pytest_args
544
567
 
@@ -564,8 +587,11 @@ def test(
564
587
  if not os.path.exists(install_dir):
565
588
  os.mkdir(install_dir)
566
589
 
590
+ # Unless we have a src layout, we need to switch away from the current directory into build install to avoid importing ./package instead of the built package.
591
+ test_path = site_path if not os.path.isdir("./src") else None
592
+
567
593
  cwd = os.getcwd()
568
- pytest_p = _run(cmd + list(pytest_args), cwd=site_path)
594
+ pytest_p = _run(cmd + list(pytest_args), cwd=test_path)
569
595
  os.chdir(cwd)
570
596
 
571
597
  if gcov:
@@ -607,7 +633,9 @@ def test(
607
633
 
608
634
 
609
635
  @click.command()
610
- @click.option("--code", "-c", help="Python program passed in as a string")
636
+ @click.option(
637
+ "--code", "-c", metavar="CODE", help="Python program passed in as a string"
638
+ )
611
639
  @click.argument("gdb_args", nargs=-1)
612
640
  @build_dir_option
613
641
  @click.pass_context
@@ -664,7 +692,7 @@ def gdb(ctx, *, code, gdb_args, build_dir):
664
692
  @click.argument("ipython_args", nargs=-1)
665
693
  @build_dir_option
666
694
  @click.pass_context
667
- def ipython(ctx, *, ipython_args, build_dir):
695
+ def ipython(ctx, *, ipython_args, build_dir, pre_import=""):
668
696
  """💻 Launch IPython shell with PYTHONPATH set
669
697
 
670
698
  IPYTHON_ARGS are passed through directly to IPython, e.g.:
@@ -674,13 +702,15 @@ def ipython(ctx, *, ipython_args, build_dir):
674
702
  build_cmd = _get_configured_command("build")
675
703
  if build_cmd:
676
704
  click.secho(
677
- "Invoking `build` prior to invoking ipython:", bold=True, fg="bright_green"
705
+ "Invoking `build` prior to launching ipython:", bold=True, fg="bright_green"
678
706
  )
679
707
  ctx.invoke(build_cmd, build_dir=build_dir)
680
708
 
681
709
  p = _set_pythonpath(build_dir)
682
710
  if p:
683
711
  print(f'💻 Launching IPython with PYTHONPATH="{p}"')
712
+ if pre_import:
713
+ ipython_args = (f"--TerminalIPythonApp.exec_lines={pre_import}",) + ipython_args
684
714
  _run(["ipython", "--ignore-cwd"] + list(ipython_args), replace=True)
685
715
 
686
716
 
@@ -798,7 +828,27 @@ def run(ctx, *, args, build_dir=None):
798
828
  cmd_args = ["bash", "-c", cmd_args]
799
829
 
800
830
  _set_pythonpath(build_dir, quiet=True)
801
- p = _run(cmd_args, echo=False, shell=shell, sys_exit=False)
831
+
832
+ is_windows = sys.platform == "win32"
833
+
834
+ if not is_windows:
835
+ # Let the subprocess handle its own signals
836
+ # Except on Windows, where it already seems to work as intended,
837
+ # and `preexec_fn` is not supported
838
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
839
+
840
+ def attach_sigint():
841
+ # Reset SIGINT handler to default
842
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
843
+
844
+ # --- launch subprocess ---
845
+ p = _run(
846
+ cmd_args,
847
+ echo=False,
848
+ shell=shell,
849
+ sys_exit=False,
850
+ preexec_fn=None if is_windows else attach_sigint,
851
+ )
802
852
 
803
853
  # Is the user trying to run a Python script, without calling the Python interpreter?
804
854
  executable = args[0]
@@ -834,7 +884,13 @@ def run(ctx, *, args, build_dir=None):
834
884
  default=True,
835
885
  help="Sphinx gallery: enable/disable plots",
836
886
  )
837
- @click.option("--jobs", "-j", default="auto", help="Number of parallel build jobs")
887
+ @click.option(
888
+ "--jobs",
889
+ "-j",
890
+ default="auto",
891
+ metavar="N_JOBS",
892
+ help="Number of parallel build jobs",
893
+ )
838
894
  @build_dir_option
839
895
  @click.pass_context
840
896
  def docs(
@@ -937,7 +993,9 @@ def docs(
937
993
 
938
994
 
939
995
  @click.command()
940
- @click.option("--code", "-c", help="Python program passed in as a string")
996
+ @click.option(
997
+ "--code", "-c", metavar="CODE", help="Python program passed in as a string"
998
+ )
941
999
  @click.argument("lldb_args", nargs=-1)
942
1000
  @build_dir_option
943
1001
  @click.pass_context
@@ -29,7 +29,11 @@ def introspect(*, cmd):
29
29
 
30
30
  cmd_func = cmds[cmd]
31
31
  try:
32
- code = inspect.getsource(cmd_func.callback)
32
+ if hasattr(cmd_func.callback, "_parent"):
33
+ # `util.extend_command` was used
34
+ code = inspect.getsource(cmd_func.callback._parent)
35
+ else:
36
+ code = inspect.getsource(cmd_func.callback)
33
37
  except TypeError:
34
38
  # Perhaps a partial, try again
35
39
  code = inspect.getsource(cmd_func.callback.func)