robotcode-plugin 2.8.0.dev677__py3-none-any.whl → 2.9.0.dev51__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.
@@ -41,6 +41,7 @@ __all__ = [
41
41
  "UnknownError",
42
42
  "hookimpl",
43
43
  "pass_application",
44
+ "show_error",
44
45
  ]
45
46
 
46
47
  F = TypeVar("F", bound=Callable[..., Any])
@@ -83,6 +84,8 @@ class CommonConfig:
83
84
  colored_output: ColoredOutput = ColoredOutput.AUTO
84
85
  default_paths: Optional[Sequence[str]] = None
85
86
  launcher_script: Optional[str] = None
87
+ wrapper: Optional[str] = None
88
+ no_wrapper: bool = False
86
89
  output_format: Optional[OutputFormat] = None
87
90
  pager: Optional[bool] = None
88
91
  log_enabled: bool = False
@@ -528,4 +531,27 @@ class Application:
528
531
  sys.path = self._syspath
529
532
 
530
533
 
534
+ def show_error(app: Application, error: click.ClickException, file: Optional[IO[Any]] = None) -> None:
535
+ """Write a Click error as robotcode writes the errors it reports, `[ ERROR ] <message>`
536
+ (`Application.error`), instead of Click's `Error: <message>`.
537
+
538
+ A usage error with a context gets Click's usage line and hint to `--help` first. An
539
+ error that writes itself in a form of its own, such as Click's help for a command
540
+ called without arguments, is written by its `show`.
541
+ """
542
+ if type(error).show not in (click.ClickException.show, click.UsageError.show):
543
+ error.show(file)
544
+ return
545
+
546
+ if isinstance(error, click.UsageError) and error.ctx is not None:
547
+ ctx = error.ctx
548
+ hint = ""
549
+ if ctx.command.get_help_option(ctx) is not None:
550
+ help_option = max(ctx.command.get_help_option_names(ctx), key=len)
551
+ hint = f"Try '{ctx.command_path} {help_option}' for help.\n"
552
+ click.echo(f"{ctx.get_usage()}\n{hint}", file=file, err=True, color=ctx.color)
553
+
554
+ app.error(error.format_message(), file=file)
555
+
556
+
531
557
  pass_application = click.make_pass_decorator(Application, ensure=True)
@@ -1 +1 @@
1
- __version__ = "2.8.0-dev.677"
1
+ __version__ = "2.9.0-dev.51"
@@ -1,5 +1,14 @@
1
+ import os
2
+ import shlex
3
+ import subprocess
4
+ import sys
5
+ from typing import TYPE_CHECKING, Any, Optional, Sequence
6
+
1
7
  import click
2
8
 
9
+ if TYPE_CHECKING:
10
+ from robotcode.plugin import Application
11
+
3
12
  # Set in the environment once a wrapper has been applied to the process tree.
4
13
  # Prevents infinite re-exec recursion and lets an outer layer (e.g. the VS Code
5
14
  # debug launcher applying its own wrapper) suppress the CLI/profile re-exec.
@@ -16,6 +25,12 @@ def wrappable(cmd: click.Command) -> click.Command:
16
25
  example, need to run inside a specific X11/Wayland session). Commands that
17
26
  do not execute Robot Framework - language server, discovery, libdoc, … -
18
27
  must NOT be marked.
28
+
29
+ The re-execution happens when the command loads its configuration with
30
+ `load_robot_profile` or `handle_robot_options` of `robotcode.runner.cli.robot`,
31
+ so a marked command must do that first, before it starts a server, an
32
+ interpreter or a debugger. A marked command that loads its configuration in
33
+ another way runs without the wrapper.
19
34
  """
20
35
  setattr(cmd, _WRAPPABLE_ATTR, True)
21
36
  return cmd
@@ -24,3 +39,72 @@ def wrappable(cmd: click.Command) -> click.Command:
24
39
  def is_wrappable(cmd: click.Command) -> bool:
25
40
  """Return whether `cmd` was marked with :func:`wrappable`."""
26
41
  return bool(getattr(cmd, _WRAPPABLE_ATTR, False))
42
+
43
+
44
+ def invoked_command_is_wrappable(ctx: click.Context) -> bool:
45
+ """Return whether the command that the root group invokes is marked with :func:`wrappable`.
46
+
47
+ A command that such a command runs itself, such as `robot` in `debug`, belongs to it.
48
+ """
49
+ root = ctx.find_root()
50
+ sub_name = root.invoked_subcommand
51
+ if not sub_name or not isinstance(root.command, click.Group):
52
+ return False
53
+ # Aliases resolve to the same command object, so this is alias-safe and needs
54
+ # no central name list.
55
+ sub_cmd = root.command.get_command(root, sub_name)
56
+ return sub_cmd is not None and is_wrappable(sub_cmd)
57
+
58
+
59
+ def run_under_wrapper(app: "Application", profile_wrapper: Optional[Sequence[Any]]) -> None:
60
+ """Re-execute this process through the configured ``wrapper`` command.
61
+
62
+ A wrappable command calls this once it has loaded its configuration, before it
63
+ starts anything. ``--wrapper`` overrides the ``wrapper`` of the profile
64
+ (``profile_wrapper``); ``--no-wrapper`` disables wrapping for this run. The
65
+ guard env var prevents infinite recursion and lets an outer layer suppress the
66
+ re-exec by setting it itself. Returns when no wrapper applies or for a dry run.
67
+ """
68
+ if app.config.no_wrapper or os.environ.get(WRAPPER_APPLIED_ENV):
69
+ return
70
+
71
+ # `--wrapper` overrides the profile's `wrapper`. The evaluated profile turns
72
+ # StringExpression entries into plain strings.
73
+ if app.config.wrapper:
74
+ wrapper = shlex.split(app.config.wrapper)
75
+ elif profile_wrapper:
76
+ wrapper = [str(w) for w in profile_wrapper]
77
+ else:
78
+ return
79
+
80
+ # A dry run must not start the wrapper: it would set up and tear down the
81
+ # real environment. Show it instead; the command itself then prints its own
82
+ # dry-run output.
83
+ if app.config.dry:
84
+ app.echo(f"Dry run, not executing the wrapper. Would run robotcode under: {' '.join(wrapper)}")
85
+ return
86
+
87
+ # Rebuild the invocation the way this process was actually started, so the
88
+ # re-exec finds robotcode even when it runs from a bundled path (VS Code) and
89
+ # is not installed in the interpreter. `launcher_script` is set only when we
90
+ # were started through a bundled entry (its `__main__` sets it) or an explicit
91
+ # `--launcher-script`; otherwise robotcode is importable directly and
92
+ # `-m robotcode.cli` is correct. We deliberately do NOT consult the
93
+ # ROBOTCODE_BUNDLED_ROBOTCODE_MAIN env var: VS Code sets it in every terminal,
94
+ # so a directly started robotcode would be diverted to the bundled copy.
95
+ # Mirrors the debug launcher's `debugger_script` handling.
96
+ entry = [str(app.config.launcher_script)] if app.config.launcher_script else ["-m", "robotcode.cli"]
97
+ command = [*wrapper, sys.executable, *entry, *sys.argv[1:]]
98
+ app.verbose(lambda: "Running under wrapper: " + " ".join(command))
99
+
100
+ # Run robotcode again under the wrapper; the guard env stops the spawned
101
+ # robotcode from wrapping a second time. On POSIX we replace this process with
102
+ # execvp (no extra process); Windows has no such exec, so there we spawn the
103
+ # command, wait, and forward its exit code.
104
+ os.environ[WRAPPER_APPLIED_ENV] = "1"
105
+ try:
106
+ if sys.platform == "win32":
107
+ sys.exit(subprocess.run(command).returncode)
108
+ os.execvp(command[0], command)
109
+ except OSError as e:
110
+ raise click.ClickException(f"Failed to execute wrapper command {wrapper!r}: {e}") from e
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: robotcode-plugin
3
- Version: 2.8.0-dev.677
3
+ Version: 2.9.0-dev.51
4
4
  Summary: Some classes for RobotCode plugin management
5
5
  Project-URL: Donate, https://opencollective.com/robotcode
6
6
  Project-URL: Documentation, https://github.com/robotcodedev/robotcode#readme
@@ -1,5 +1,5 @@
1
- robotcode/plugin/__init__.py,sha256=GZc3LUHnoIcvYlSZjS1zA1SV0p1WY4GWwkN89QVIKgw,17381
2
- robotcode/plugin/__version__.py,sha256=0xyLlet74gxp40f05fA-fjXhAr2XdtEik3UuIJs6yAM,30
1
+ robotcode/plugin/__init__.py,sha256=h6lXt0W8_c72vw6M4GY_0pFEZuBbFSvf6ZPAoCK5n00,18546
2
+ robotcode/plugin/__version__.py,sha256=68lI1Ud2IVxiSKwJTXlHsfwV7s6ASsCia4CPMEdJ8Ls,29
3
3
  robotcode/plugin/_agent_detection.py,sha256=PVsBAxy_Xn_uefTBpBAGE-q3Z5CUxbhwp_z4Dcb_RL4,3606
4
4
  robotcode/plugin/manager.py,sha256=ARNXtSbQTRktsbNQGLeU1fwtsK7GdstHa6OJaXuadrU,1098
5
5
  robotcode/plugin/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
@@ -8,7 +8,7 @@ robotcode/plugin/click_helper/aliases.py,sha256=NJZKyF6GOro52fjp728OZfqZ9EptNhiJ
8
8
  robotcode/plugin/click_helper/exit_codes.py,sha256=k5ih_20gKkuEWpOhyig2_g11ZywrjPoiZHzH9pV2obs,854
9
9
  robotcode/plugin/click_helper/options.py,sha256=eAU05TfOq93wVWkiYqw7BV6RrXAp1xw9jT6DsPRdZ_Y,11430
10
10
  robotcode/plugin/click_helper/types.py,sha256=UAno_G-0uNdPfapGCvECwd0yghdEZXMMv4DIlUAKKsw,3679
11
- robotcode/plugin/click_helper/wrappable.py,sha256=28FzkR4OO5lSi3_e35AKKZHQD2fD7Dr1DM4xkI_PrPg,1009
12
- robotcode_plugin-2.8.0.dev677.dist-info/METADATA,sha256=68GL39gjhjYVYcExbiGgFHJLNDkzBxBr10bDMJwJ-Tc,2844
13
- robotcode_plugin-2.8.0.dev677.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
14
- robotcode_plugin-2.8.0.dev677.dist-info/RECORD,,
11
+ robotcode/plugin/click_helper/wrappable.py,sha256=dYC6hYvtPRnjDzC-k7fiIsgoLRnwS-ACLWf0COnMPl4,5009
12
+ robotcode_plugin-2.9.0.dev51.dist-info/METADATA,sha256=SqmX5dBAKwhQ44AEiSm2HTcFdglm_RWBTaZgKtjir1c,2843
13
+ robotcode_plugin-2.9.0.dev51.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
14
+ robotcode_plugin-2.9.0.dev51.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.4
2
+ Generator: hatchling 1.33.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any