Source code for gale.management.gale_admin

"""
This file contains gale-admin's click commands: create-project
(scaffolds a new project's main.py/settings.py/src/assets layout) and
create-state (scaffolds a new BaseState subclass inside one).

Author: Alejandro Mujica (aledrums@gmail.com)
"""

import os
import re
from pathlib import Path

import click


[docs] def touch(path: Path, text: str = None): with open(path, "w") as f: if text is not None: f.write(text)
[docs] def to_pascal_case(identifier: str) -> str: words = identifier.split("_") return "".join([w.capitalize() for w in words])
[docs] def to_spaced_name(identifier: str) -> str: words = identifier.split("_") return " ".join([w.capitalize() for w in words])
[docs] def to_snake_case(identifier: str) -> str: identifier = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", identifier) return re.sub("([a-z0-9])([A-Z])", r"\1_\2", identifier).lower()
@click.group def cli(): pass GAME_CLASS_TEMPLATE = '''""" This module was autogenerated by gale. """ import pygame from gale.game import Game from gale.input_handler import InputData, InputHandler, InputListener from gale.state import StateMachine class {game_class}(Game, InputListener): def init(self) -> None: self.state_machine = StateMachine() InputHandler.register_listener(self) def update(self, dt: float) -> None: self.state_machine.update(dt) def render(self, surface: pygame.Surface) -> None: self.state_machine.render(surface) def on_input(self, input_id: str, input_data: InputData) -> None: if (input_id == 'quit' and input_data.pressed): self.quit() ''' GAME_MAIN_TEMPLATE = '''""" This module was autogenerated by gale. """ import settings from src.{game_file} import {game_class} if __name__ == '__main__': game = {game_class}( "{game_title}", settings.WINDOW_WIDTH, settings.WINDOW_HEIGHT, settings.VIRTUAL_WIDTH, settings.VIRTUAL_HEIGHT ) game.exec() ''' SETTINGS_TEMPLATE = '''""" This module was autogenerated by gale. """ import pathlib import pygame from gale import frames from gale import input_handler input_handler.InputHandler.set_keyboard_action(input_handler.KEY_ESCAPE, 'quit') # Size we want to emulate VIRTUAL_WIDTH = 320 VIRTUAL_HEIGHT = 180 # Size of our actual window WINDOW_WIDTH = 1280 WINDOW_HEIGHT = 720 BASE_DIR = pathlib.Path(__file__).parent # Register your textures from the graphics folder, for instance: # TEXTURES = { # 'my_texture': pygame.image.load(BASE_DIR / "assets" / "graphics" / "my_texture.png") # } TEXTURES = {} # Register your frames, for instance: # FRAMES = { # 'my_frames': frames.generate_frames(TEXTURES['my_texture'], 16, 16) # } FRAMES = {} pygame.mixer.init() # Register your sound from the sounds folder, for instance: # SOUNDS = { # 'my_sound': pygame.mixer.Sound(BASE_DIR / "assets" / "sounds" / "my_sound.wav"), # } SOUNDS = {} pygame.font.init() # Register your fonts from the fonts folder, for instance: # FONTS = { # 'small': pygame.font.Font(BASE_DIR / "assets" / "fonts" / "font.ttf", 8) # } FONTS = {} ''' @click.command @click.argument("name") def create_project(name: str) -> None: if os.path.exists(name): click.echo(f"Project {name} already exists.") return os.mkdir(name) app_path = os.path.join(os.getcwd(), name) game_class = to_pascal_case(name) game_file = to_snake_case(game_class) game_title = to_spaced_name(name) touch( os.path.join(app_path, "main.py"), GAME_MAIN_TEMPLATE.format( game_class=game_class, game_title=game_title, game_file=game_file ), ) touch(os.path.join(app_path, "settings.py"), SETTINGS_TEMPLATE) touch(os.path.join(app_path, "README.md"), f"# {game_title}") os.mkdir(os.path.join(app_path, "src")) touch( os.path.join(app_path, "src", f"{game_file}.py"), GAME_CLASS_TEMPLATE.format(game_class=game_class), ) os.mkdir(os.path.join(app_path, "assets")) for directory in ["sounds", "graphics", "fonts"]: os.mkdir(os.path.join(app_path, "assets", directory)) click.echo(f"Project {name} created") cli.add_command(create_project) STATE_TEMPLATE = '''""" This module was autogenerated by gale. """ from typing import Any, Dict, Tuple import pygame from gale.input_handler import InputData from gale.state import BaseState class {state_class}(BaseState): def enter(self, *args: Tuple[Any], **kwargs: Dict[str, Any]) -> None: pass def exit(self) -> None: pass def on_input(self, input_id: str, input_data: InputData) -> None: pass def update(self, dt: float) -> None: pass def render(self, surface: pygame.Surface) -> None: pass ''' @click.command() @click.argument("name") def create_state(name: str) -> None: if not os.path.isdir("src"): click.echo( "No 'src' directory found. Run this from inside a project created " "with 'gale-admin create-project'." ) return normalized = to_snake_case(name) if not normalized.endswith("_state"): normalized += "_state" state_class = to_pascal_case(normalized) states_dir = os.path.join("src", "states") os.makedirs(states_dir, exist_ok=True) init_path = os.path.join(states_dir, "__init__.py") if not os.path.exists(init_path): touch(init_path) state_path = os.path.join(states_dir, f"{to_snake_case(state_class)}.py") if os.path.exists(state_path): click.echo(f"State {state_class} already exists.") return touch(state_path, STATE_TEMPLATE.format(state_class=state_class)) click.echo(f"State {state_class} created at {state_path}") cli.add_command(create_state)
[docs] def execute(): cli()