Skip to content

Commit 122d728

Browse files
committed
[cli] Add -e/--exec flag for invoking python scripts
Allows for passing filepaths or urls to python scripts and running with the same python interpreter.
1 parent 6133b50 commit 122d728

File tree

2 files changed

+89
-5
lines changed

2 files changed

+89
-5
lines changed

ocebuild_cli/__main__.py

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""Entry point for the CLI."""
88

99
from os import _exit as os_exit
10+
from urllib.parse import urlparse
1011

1112
from typing import Optional
1213

@@ -20,12 +21,93 @@
2021
from ocebuild_cli.logging import _format_url, abort
2122

2223

23-
@click.group(context_settings=CONTEXT_SETTINGS)
24+
class PassthroughCommand(click.Group):
25+
"""A custom command group that handles the exec option specially."""
26+
27+
@staticmethod
28+
def is_url(s):
29+
try:
30+
result = urlparse(s)
31+
return all([result.scheme, result.netloc])
32+
except ValueError:
33+
return False
34+
35+
def parse_args(self, ctx, args):
36+
ctx.obj = CLIEnv()
37+
38+
# Check if -e or --exec is in args
39+
if '-e' in args or '--exec' in args:
40+
# Find the position of -e or --exec
41+
try:
42+
idx = args.index('-e')
43+
except ValueError:
44+
idx = args.index('--exec')
45+
46+
# Get the script path (next argument after -e/--exec)
47+
if idx + 1 < len(args):
48+
script_path = args[idx + 1]
49+
50+
# If the script is a URL, we should download it to a tmp dir
51+
if self.is_url(script_path):
52+
from ocebuild.sources import request
53+
from tempfile import TemporaryDirectory
54+
with request(script_path) as response:
55+
# Create a temporary dir and store the script with the same name
56+
# as the URL's last part (instead of a random name).
57+
with TemporaryDirectory(delete = False) as tmpdir:
58+
from os.path import join, basename
59+
ctx.obj.tmpdir = tmpdir
60+
script_path = join(tmpdir, basename(urlparse(script_path).path))
61+
with open(script_path, 'wb') as f:
62+
f.write(response.read())
63+
64+
# Remove -e/--exec and script_path from args
65+
new_args = args[:idx] + args[idx+2:]
66+
67+
# Store the script path and remaining args in the context
68+
ctx.params['exec_file'] = script_path
69+
ctx.params['args'] = new_args
70+
71+
return [] # No more arguments to process
72+
73+
# Default processing for other cases
74+
return super().parse_args(ctx, args)
75+
76+
@click.group(cls=PassthroughCommand, invoke_without_command=True,
77+
context_settings=CONTEXT_SETTINGS)
78+
@click.option('-e', '--exec', 'exec_file',
79+
type=click.Path(exists=True, dir_okay=False),
80+
help='Run the specified Python file with the CLI environment.')
81+
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
2482
@click.version_option(message='ocebuild-cli %(version)s', version=__version__)
2583
@click.pass_context
26-
def cli(ctx):
84+
def cli(ctx, exec_file=None, args=None):
2785
"""Main runner for the CLI."""
28-
ctx.obj = CLIEnv()
86+
if exec_file and ctx.invoked_subcommand is None:
87+
try:
88+
# Run python script in a controlled namespace (inherits pyinstaller env)
89+
import runpy, sys
90+
91+
sys.argv = [exec_file] + list(args) if args else [exec_file]
92+
ctx.obj.__dict__['argv'] = list(args) if args else []
93+
94+
runpy.run_path(exec_file, run_name="__main__", init_globals=ctx.obj.__dict__)
95+
return
96+
# If an error occurs, abort with a message and traceback
97+
except Exception as e:
98+
abort(msg=f"Failed to execute {exec_file}: {e}", traceback=True)
99+
finally:
100+
tmpdir = ctx.obj.tmpdir
101+
if tmpdir:
102+
# Remove the temporary directory if it was created
103+
import tempfile
104+
from os.path import isdir
105+
from shutil import rmtree
106+
if isdir(tmpdir) and tmpdir.startswith(tempfile.gettempdir()):
107+
try:
108+
rmtree(tmpdir)
109+
except OSError as e:
110+
abort(msg=f"Failed to remove temporary directory {tmpdir}: {e}")
29111

30112
def cli_exit(env: Optional[CLIEnv]=None, status: int=0):
31113
"""Cleanup the CLI environment on exit."""
@@ -39,7 +121,7 @@ def cli_exit(env: Optional[CLIEnv]=None, status: int=0):
39121

40122
@cli.result_callback(replace=True)
41123
@click.make_pass_decorator(CLIEnv)
42-
def cli_exit_hook(env, res, status: int=0):
124+
def cli_exit_hook(env, res, status: int=0, **_):
43125
"""Exit hook for CLI commands."""
44126
cli_exit(env, status)
45127

@@ -52,7 +134,7 @@ def _main():
52134
cli() #pylint: disable=no-value-for-parameter
53135
# Cleanup the CLI environment on exit.
54136
except SystemExit as e:
55-
cli_exit(status=e.code or 0)
137+
cli_exit(status=int(e.code) if e.code is not None else 0)
56138
# Catch any unhandled exceptions.
57139
except Exception: #pylint: disable=broad-exception-caught
58140
issues_url = _format_url("https://github.com/Qonfused/OCE-Build/issues")

ocebuild_cli/_lib.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
class CLIEnv:
2828
"""Shared CLI environment."""
2929

30+
tmpdir: Optional[str] = None
31+
3032
global VERBOSE, DEBUG
3133
def __init__(self,
3234
verbose_flag: bool=VERBOSE,

0 commit comments

Comments
 (0)