Skip to content

Commit bbf221e

Browse files
authored
New CLI Infrastructure (#2828)
* New CLI infrastructure and updated info command * formatting * typing.override didn't exist until 3.12
1 parent 26bf7ee commit bbf221e

10 files changed

Lines changed: 125 additions & 51 deletions

File tree

arcade/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from arcade.management import show_info
1+
from arcade.cli import run_arcade_cli
22

33
if __name__ == "__main__":
4-
show_info()
4+
run_arcade_cli()

arcade/cli/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .cli import run_arcade_cli

arcade/cli/cli.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import argparse
2+
import sys
3+
from typing import Type
4+
5+
from .commands import BaseCommand, InfoCommand
6+
7+
8+
class CLI:
9+
def __init__(self):
10+
self.commands: dict[str, BaseCommand] = {}
11+
self.prog: str = "arcade"
12+
self.description: str = "Arcade Game Library CLI"
13+
14+
def register_command(self, command_class: Type[BaseCommand]) -> None:
15+
command = command_class() # type: ignore BaseCommand has different constructor than it's implementations
16+
self.commands[command.name] = command
17+
18+
def create_parser(self) -> argparse.ArgumentParser:
19+
parser = argparse.ArgumentParser(
20+
prog=self.prog,
21+
description=self.description,
22+
formatter_class=argparse.RawDescriptionHelpFormatter,
23+
)
24+
25+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
26+
27+
for command_name, command in self.commands.items():
28+
command_parser = subparsers.add_parser(
29+
command_name,
30+
help=command.help,
31+
description=command.description,
32+
formatter_class=argparse.RawDescriptionHelpFormatter,
33+
)
34+
command.add_arguments(command_parser)
35+
36+
return parser
37+
38+
def run(self) -> int:
39+
parser = self.create_parser()
40+
args = parser.parse_args()
41+
42+
if args.command is None:
43+
parser.print_help()
44+
return 0
45+
46+
try:
47+
command = self.commands[args.command]
48+
return command.handle(args)
49+
except Exception as e:
50+
print(f"Error: {e}", file=sys.stderr)
51+
return 1
52+
53+
54+
def run_arcade_cli():
55+
cli = CLI()
56+
cli.register_command(InfoCommand)
57+
return cli.run()

arcade/cli/commands/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .base import BaseCommand
2+
from .info import InfoCommand
3+
4+
__all__ = ["BaseCommand", "InfoCommand"]

arcade/cli/commands/base.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import argparse
2+
from abc import ABC, abstractmethod
3+
4+
5+
class BaseCommand(ABC):
6+
name: str
7+
description: str
8+
help: str
9+
10+
def __init__(self, name: str, description: str, help: str) -> None:
11+
self.name = name
12+
self.description = description
13+
self.help = help
14+
15+
@abstractmethod
16+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
17+
pass
18+
19+
@abstractmethod
20+
def handle(self, args: argparse.Namespace) -> int:
21+
pass

arcade/cli/commands/info.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import argparse
2+
import sys
3+
4+
import PIL
5+
import pyglet
6+
7+
import arcade
8+
9+
from .base import BaseCommand
10+
11+
12+
class InfoCommand(BaseCommand):
13+
def __init__(self):
14+
super().__init__(
15+
name="info",
16+
description="Print Arcade and System Information",
17+
help="Print information about the installed Arcade version and system specifications",
18+
)
19+
20+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
21+
pass
22+
23+
def handle(self, args: argparse.Namespace) -> int:
24+
window = arcade.Window(visible=False)
25+
version_str = f"Arcade {arcade.__version__}"
26+
print()
27+
print(version_str)
28+
print("-" * len(version_str))
29+
print("vendor:", window.ctx.info.VENDOR)
30+
print("device:", window.ctx.info.RENDERER)
31+
print("renderer:", window.ctx.info.CTX_INFO) # type: ignore
32+
print("python:", sys.version)
33+
print("platform:", sys.platform)
34+
print("pyglet version:", pyglet.version)
35+
print("PIL version:", PIL.__version__)
36+
return 1

arcade/gl/backends/opengl/context.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,8 @@ def __init__(self, ctx):
455455
self.MAJOR_VERSION = self.get(gl.GL_MAJOR_VERSION)
456456
"""Major version number of the OpenGL API supported by the current context."""
457457

458+
self.CTX_INFO = f"opengl {self.MAJOR_VERSION}.{self.MINOR_VERSION}"
459+
458460
self.MAX_COLOR_TEXTURE_SAMPLES = self.get(gl.GL_MAX_COLOR_TEXTURE_SAMPLES)
459461
"""Maximum number of samples in a color multisample texture"""
460462

arcade/gl/backends/webgl/context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ class WebGLInfo(Info):
395395
def __init__(self, ctx: WebGLContext):
396396
super().__init__(ctx)
397397
self._ctx = ctx
398+
self.CTX_INFO = "webgl"
398399

399400
def get_int_tuple(self, enum, length: int):
400401
# TODO: this might not work

arcade/management/__init__.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ dev = [
7272
testing_libraries = ["pytest", "pytest-mock", "pytest-cov", "pyyaml==6.0.1"]
7373

7474
[project.scripts]
75-
arcade = "arcade.management:execute_from_command_line"
75+
arcade = "arcade.cli:run_arcade_cli"
7676

7777
[project.entry-points.pyinstaller40]
7878
hook-dirs = "arcade.__pyinstaller:get_hook_dirs"

0 commit comments

Comments
 (0)