Skip to content

Commit 67616a2

Browse files
authored
Merge pull request #33 from DavidCEllis/improve_logic
Rename ducktools.pyz to ducktools-env.pyz, improve errors, remove .github folder from sdist.
2 parents a708649 + d6aca3a commit 67616a2

File tree

11 files changed

+300
-176
lines changed

11 files changed

+300
-176
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
prune .github

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,15 @@ The tool can be used in multiple ways:
7272
* This adds the `dtrun` shortcut for `ducktools-env run`
7373
* Executed from the zipapp
7474
* Download from: https://github.com/DavidCEllis/ducktools-env/releases/latest
75-
* Run with: `ducktools.pyz <command>`
75+
* Run with: `ducktools-env.pyz <command>`
76+
* The `dtrun.pyz` zipapp is available as a shortcut for `ducktools-env.pyz run`
7677
* Installed in an environment
7778
* Download with `pip` or `uv` in a virtual environment: `pip install ducktools-env`
7879
* Run with: `ducktools-env <command>`
80+
* The `dtrun` shortcut is also available
7981
* Accessed directly via `uvx` with uv
8082
* `uvx ducktools-env <command>`
83+
* No access to the `dtrun` shortcut this way
8184

8285
These examples will use the `ducktools-env` command as the base as if installed via `uv tool` or a similar tool.
8386

src/ducktools/env/__main__.py

Lines changed: 184 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ducktools.lazyimporter import LazyImporter, FromImport
3131

3232
from ducktools.env import __version__, PROJECT_NAME
33+
from ducktools.env.exceptions import EnvError
3334

3435
_laz = LazyImporter(
3536
[
@@ -191,7 +192,7 @@ def get_parser(prog, exit_on_error=True) -> FixedArgumentParser:
191192
create_zipapp_parser.add_argument(
192193
"--zipapp",
193194
action="store_true",
194-
help="Also create the portable ducktools.pyz zipapp",
195+
help="Also create the portable ducktools-env.pyz zipapp",
195196
)
196197

197198
list_parser = subparsers.add_parser(
@@ -284,9 +285,159 @@ def get_columns(
284285
)
285286

286287

287-
def main():
288-
if __name__ == "__main__":
289-
command = f"{os.path.basename(sys.executable)} -m ducktools.env"
288+
def run_command(manager, args):
289+
# Split on existence of the command as a file, if the file exists run it
290+
# Otherwise look for it in the registered scripts database
291+
292+
if os.path.isfile(args.script_filename):
293+
returncode = manager.run_script(
294+
script_path=args.script_filename,
295+
script_args=args.script_args,
296+
generate_lock=args.generate_lock,
297+
lock_path=args.with_lock,
298+
)
299+
else:
300+
returncode = manager.run_registered_script(
301+
script_name=args.script_filename,
302+
script_args=args.script_args,
303+
generate_lock=args.generate_lock,
304+
lock_path=args.with_lock,
305+
)
306+
307+
return returncode
308+
309+
310+
def bundle_command(manager, args):
311+
manager.create_bundle(
312+
script_path=args.script_filename,
313+
with_lock=args.with_lock,
314+
generate_lock=args.generate_lock,
315+
output_file=args.output,
316+
compressed=args.compress,
317+
)
318+
319+
return 0
320+
321+
322+
def register_command(manager, args):
323+
if args.remove:
324+
# filename should just be the script name, but it's awkward to change this
325+
manager.remove_registered_script(
326+
script_name=args.script_filename,
327+
)
328+
else:
329+
manager.register_script(
330+
script_path=args.script_filename,
331+
script_name=args.name,
332+
)
333+
334+
return 0
335+
336+
337+
def generate_lock_command(manager, args):
338+
lock_path = manager.generate_lockfile(
339+
script_path=args.script_filename,
340+
lockfile_path=args.output,
341+
)
342+
print(f"Lockfile generated at '{lock_path}'")
343+
344+
return 0
345+
346+
347+
def clear_cache_command(manager, args):
348+
if args.full:
349+
manager.clear_project_folder()
350+
else:
351+
manager.clear_temporary_cache()
352+
353+
return 0
354+
355+
356+
def rebuild_env_command(manager, args):
357+
manager.build_env_folder()
358+
if args.zipapp:
359+
manager.build_zipapp()
360+
361+
return 0
362+
363+
364+
def list_command(manager, args):
365+
has_data = False
366+
show_temp = args.temp or not (args.app or args.scripts)
367+
show_app = args.app or not (args.scripts or args.temp)
368+
show_scripts = args.scripts or not (args.app or args.temp)
369+
370+
if (envs := manager.temp_catalogue.environments) and show_temp:
371+
has_data = True
372+
print("Temporary Environments")
373+
print("======================")
374+
formatted = get_columns(
375+
data=envs.values(),
376+
headings=["Name", "Last Used"],
377+
attributes=["name", "last_used_simple"],
378+
)
379+
for line in formatted:
380+
print(line)
381+
if not args.temp:
382+
# newline if not exclusive
383+
print()
384+
385+
if (envs := manager.app_catalogue.environments) and show_app:
386+
has_data = True
387+
print("Application Environments")
388+
print("========================")
389+
formatted = get_columns(
390+
data=envs.values(),
391+
headings=["Owner / Name", "Last Used"],
392+
attributes=["name", "last_used_simple"],
393+
)
394+
for line in formatted:
395+
print(line)
396+
if not args.app:
397+
# newline if not exclusive
398+
print()
399+
400+
if (scripts := manager.script_registry.list_registered_scripts()) and show_scripts:
401+
has_data = True
402+
print("Registered Scripts")
403+
print("==================")
404+
405+
formatted = get_columns(
406+
data=scripts,
407+
headings=["Script Name", "Path"],
408+
attributes=["name", "path"],
409+
)
410+
for line in formatted:
411+
print(line)
412+
print()
413+
414+
if has_data is False:
415+
print("No environments or scripts managed by ducktools-env")
416+
417+
return 0
418+
419+
420+
def delete_env_command(manager, args):
421+
envname = args.environment_name
422+
if envname in manager.temp_catalogue.environments:
423+
manager.temp_catalogue.delete_env(envname)
424+
print(f"Temporary environment {envname!r} deleted")
425+
elif envname in manager.app_catalogue.environments:
426+
manager.app_catalogue.delete_env(envname)
427+
print(f"Application environment {envname!r} deleted")
428+
else:
429+
print(f"Environment {envname!r} not found")
430+
431+
return 0
432+
433+
434+
def main_command() -> int:
435+
executable_name = os.path.splitext(os.path.basename(sys.executable))[0]
436+
437+
if zipapp_path := globals().get("zipapp_path"):
438+
command = f"{executable_name} {zipapp_path}"
439+
elif __name__ == "__main__":
440+
command = f"{executable_name} -m ducktools.env"
290441
else:
291442
command = os.path.basename(sys.argv[0])
292443

@@ -313,138 +464,37 @@ def main():
313464
command=command,
314465
)
315466

316-
if args.command == "run":
317-
# Split on existence of the command as a file, if the file exists run it
318-
# Otherwise look for it in the registered scripts database
319-
if os.path.isfile(args.script_filename):
320-
manager.run_script(
321-
script_path=args.script_filename,
322-
script_args=args.script_args,
323-
generate_lock=args.generate_lock,
324-
lock_path=args.with_lock,
325-
)
326-
else:
327-
manager.run_registered_script(
328-
script_name=args.script_filename,
329-
script_args=args.script_args,
330-
generate_lock=args.generate_lock,
331-
lock_path=args.with_lock,
332-
)
333-
334-
elif args.command == "bundle":
335-
manager.create_bundle(
336-
script_path=args.script_filename,
337-
with_lock=args.with_lock,
338-
generate_lock=args.generate_lock,
339-
output_file=args.output,
340-
compressed=args.compress,
341-
)
342-
343-
elif args.command == "register":
344-
if args.remove:
345-
# filename should just be the script name, but it's awkward to change this
346-
manager.remove_registered_script(
347-
script_name=args.script_filename,
348-
)
349-
else:
350-
manager.register_script(
351-
script_path=args.script_filename,
352-
script_name=args.name,
353-
)
354-
355-
elif args.command == "generate_lock":
356-
lock_path = manager.generate_lockfile(
357-
script_path=args.script_filename,
358-
lockfile_path=args.output,
359-
)
360-
print(f"Lockfile generated at '{lock_path}'")
361-
362-
elif args.command == "clear_cache":
363-
if args.full:
364-
manager.clear_project_folder()
365-
else:
366-
manager.clear_temporary_cache()
367-
368-
elif args.command == "rebuild_env":
369-
manager.build_env_folder()
370-
if args.zipapp:
371-
manager.build_zipapp()
372-
373-
elif args.command == "list":
374-
has_data = False
375-
show_temp = args.temp or not (args.app or args.scripts)
376-
show_app = args.app or not (args.scripts or args.temp)
377-
show_scripts = args.scripts or not (args.app or args.temp)
378-
379-
if (envs := manager.temp_catalogue.environments) and show_temp:
380-
has_data = True
381-
print("Temporary Environments")
382-
print("======================")
383-
formatted = get_columns(
384-
data=envs.values(),
385-
headings=["Name", "Last Used"],
386-
attributes=["name", "last_used_simple"],
387-
)
388-
for line in formatted:
389-
print(line)
390-
if not args.temp:
391-
# newline if not exclusive
392-
print()
393-
394-
if (envs := manager.app_catalogue.environments) and show_app:
395-
has_data = True
396-
print("Application Environments")
397-
print("========================")
398-
formatted = get_columns(
399-
data=envs.values(),
400-
headings=["Owner / Name", "Last Used"],
401-
attributes=["name", "last_used_simple"],
402-
)
403-
for line in formatted:
404-
print(line)
405-
if not args.app:
406-
# newline if not exclusive
407-
print()
408-
409-
if (scripts := manager.script_registry.list_registered_scripts()) and show_scripts:
410-
has_data = True
411-
print("Registered Scripts")
412-
print("==================")
413-
414-
formatted = get_columns(
415-
data=scripts,
416-
headings=["Script Name", "Path"],
417-
attributes=["name", "path"],
418-
)
419-
for line in formatted:
420-
print(line)
421-
print()
422-
423-
if has_data is False:
424-
print("No environments or scripts managed by ducktools-env")
425-
426-
elif args.command == "delete_env":
427-
envname = args.environment_name
428-
if envname in manager.temp_catalogue.environments:
429-
manager.temp_catalogue.delete_env(envname)
430-
print(f"Temporary environment {envname!r} deleted")
431-
elif envname in manager.app_catalogue.environments:
432-
manager.app_catalogue.delete_env(envname)
433-
print(f"Application environment {envname!r} deleted")
434-
else:
435-
print(f"Environment {envname!r} not found")
436-
else:
437-
# Should be unreachable
438-
raise ValueError("Invalid command")
439-
440-
441-
if __name__ == "__main__":
467+
match args.command:
468+
case "run":
469+
return run_command(manager, args)
470+
case "bundle":
471+
return bundle_command(manager, args)
472+
case "register":
473+
return register_command(manager, args)
474+
case "generate_lock":
475+
return generate_lock_command(manager, args)
476+
case "clear_cache":
477+
return clear_cache_command(manager, args)
478+
case "rebuild_env":
479+
return rebuild_env_command(manager, args)
480+
case "list":
481+
return list_command(manager, args)
482+
case "delete_env":
483+
return delete_env_command(manager, args)
484+
case _:
485+
raise RuntimeError(f"Invalid Command {args.command!r}")
486+
487+
488+
def main() -> int:
442489
try:
443-
main()
444-
except RuntimeError as e:
490+
result = main_command()
491+
except (RuntimeError, EnvError) as e:
445492
errors = "\n".join(e.args) + "\n"
446493
if sys.stderr:
447494
sys.stderr.write(errors)
448-
sys.exit(1)
495+
return 1
496+
return 0
449497

450-
sys.exit(0)
498+
499+
if __name__ == "__main__":
500+
sys.exit(main())

src/ducktools/env/_run.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
from . import PROJECT_NAME
3737
from .manager import Manager
38+
from .exceptions import EnvError
3839

3940

4041
def run():
@@ -60,10 +61,10 @@ def run():
6061
script_name=app,
6162
script_args=args,
6263
)
63-
except RuntimeError as e:
64+
except (RuntimeError, EnvError) as e:
6465
msg = "\n".join(e.args) + "\n"
6566
if sys.stderr:
6667
sys.stderr.write(msg)
67-
sys.exit(1)
68+
return 1
6869

69-
sys.exit(0)
70+
return 0

0 commit comments

Comments
 (0)