Skip to content

Commit

Permalink
Add project custom command setup
Browse files Browse the repository at this point in the history
  • Loading branch information
jeckel committed Oct 30, 2024
1 parent 0b4e14e commit bd0e52a
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 39 deletions.
10 changes: 8 additions & 2 deletions src/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@

from pydantic import BaseModel, Field, field_validator, model_validator

from models.composer import Composer

class ProjectAction(BaseModel):
label: str
command: str
help: Optional[str] = None
use_shell: bool = False


class Project(BaseModel):
Expand All @@ -14,7 +19,8 @@ class Project(BaseModel):
composer: Optional[bool] = Field(default=False)
composer_cmd: list[str] = ["composer"]
docker_composer_cmd: list[str] = ["docker", "compose"]
actions: dict[str, str] = {}
actions: list[ProjectAction] = []
# actions: dict[str, str] = {}

@classmethod
def from_json(cls, json_path: str):
Expand Down
2 changes: 1 addition & 1 deletion src/presentation/component/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .terminal_modal import TerminalModal
from .terminal import Terminal
from .terminal import Terminal, ShellCommand, NonShellCommand, CommandType
56 changes: 42 additions & 14 deletions src/presentation/component/terminal.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,75 @@
import subprocess
from time import sleep
from typing import Union, Optional

from dataclasses import dataclass
from textual.widgets import RichLog
from textual import on
from textual.message import Message
from textual.worker import Worker, WorkerState


@dataclass
class ShellCommand:
path: str
command: str
shell: bool = True

def __str__(self):
return self.command


@dataclass
class NonShellCommand:
path: str
command: list[str]
shell: bool = False

def __str__(self):
return " ".join(self.command)


CommandType = Union[ShellCommand, NonShellCommand]


class Terminal(RichLog):
DEFAULT_CSS = """
Terminal {
padding: 1 1;
}
"""
command: list[str] = []
command: Optional[CommandType] = None
current_worker: Worker | None = None

def __init__(self, **kwargs):
super().__init__(highlight=True, markup=True, **kwargs)

def execute(self, command: list[str], path: str) -> None:
self.command = command
def execute(self, command: CommandType) -> None:
# self.command = command
self.current_worker = self.run_worker(
self._execute(command, path), exclusive=True, thread=True
self._execute(command), exclusive=True, thread=True
)

def is_running(self) -> bool:
return self.current_worker is not None and self.current_worker.is_running

async def _execute(self, command: list[str], path: str) -> None:
async def _execute(self, command: CommandType) -> None:
self.command = command
self.post_message(self.TerminalStarted(self.command))
self.clear()
self.write(f"Path: [bold blue]{path}[/bold blue]")
self.write(f"Command: [bold blue]{" ".join(command)}[/bold blue]")
self.write(f"Path: [bold blue]{command.path}[/bold blue]")
self.write(f"Command: [bold blue]{command}[/bold blue]")
self.write(
"----------------------------------------------------------------",
shrink=True,
)
self.log(f"Running: {command} in {path}")
self.log(f"Running: {command.command} in {command.path}")
with subprocess.Popen(
command,
cwd=path,
command.command,
cwd=command.path,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=command.shell,
text=True,
) as process:
assert process.stdout is not None
Expand All @@ -63,8 +90,9 @@ async def _execute(self, command: list[str], path: str) -> None:

@on(Worker.StateChanged)
async def worker_state_changed(self, event: Worker.StateChanged) -> None:
if event.state == WorkerState.RUNNING:
self.post_message(self.TerminalStarted(self.command))
if event.state == WorkerState.PENDING or event.state == WorkerState.RUNNING:
return
assert self.command is not None
if event.state == WorkerState.SUCCESS:
self.post_message(self.TerminalCompleted(self.command))
if event.state == WorkerState.CANCELLED or event.state == WorkerState.ERROR:
Expand All @@ -75,7 +103,7 @@ class TerminalStarted(Message):
Message sent when terminal execution starts
"""

def __init__(self, command: list[str]) -> None:
def __init__(self, command: CommandType) -> None:
self.command = command
super().__init__()

Expand All @@ -84,7 +112,7 @@ class TerminalCompleted(Message):
Message sent when terminal execution completes
"""

def __init__(self, command: list[str], success: bool = True) -> None:
def __init__(self, command: CommandType, success: bool = True) -> None:
self.command = command
self.success = success
super().__init__()
14 changes: 7 additions & 7 deletions src/presentation/component/terminal_modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from textual.screen import ModalScreen
from textual.widgets import Button, Static

from .terminal import Terminal
from .terminal import Terminal, CommandType


class TerminalModal(ModalScreen[bool]):
Expand Down Expand Up @@ -39,16 +39,16 @@ class TerminalModal(ModalScreen[bool]):

def __init__(
self,
command: list[str],
path: str,
command: CommandType,
allow_rerun: bool = False,
**kwargs,
):
super().__init__(**kwargs)
self.command = command
self.path = path
self.modal_title = f"Running: {" ".join(self.command)}"
# self.path = path
self.modal_title = f"Running: {self.command}"
self.allow_rerun = allow_rerun
# self.shell = shell
self.terminal = Terminal(
id="terminal_command",
classes="modal_container",
Expand All @@ -66,15 +66,15 @@ def compose(self) -> ComposeResult:
yield Button.success(" Rerun", id="modal_rerun")

def on_mount(self) -> None:
self.terminal.execute(command=self.command, path=self.path)
self.terminal.execute(command=self.command)

@on(Button.Pressed, "#modal_close")
def on_close(self, event: Button.Pressed) -> None:
self.dismiss(self._result)

@on(Button.Pressed, "#modal_rerun")
def on_rerun(self, event: Button.Pressed) -> None:
self.terminal.execute(command=self.command, path=self.path)
self.terminal.execute(command=self.command)

@on(Terminal.TerminalCompleted)
def on_terminal_completed(self, event: Terminal.TerminalCompleted) -> None:
Expand Down
16 changes: 10 additions & 6 deletions src/presentation/composer/composer_container.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from click import command
from textual import on, work
from textual.app import ComposeResult
from textual.containers import Container, Horizontal
Expand All @@ -6,7 +7,7 @@

from models import Project
from models.composer import Composer
from presentation.component import TerminalModal
from presentation.component import TerminalModal, NonShellCommand
from service_locator import Container as ServiceContainer

from .composer_packages_table import ComposerPackagesTable
Expand Down Expand Up @@ -98,8 +99,10 @@ def on_pressed(self, event: Button.Pressed) -> None:
if isinstance(event.button, ComposerScriptButton):
self.app.push_screen(
TerminalModal(
command=["composer", "--no-ansi", event.button.script_name],
path=self.project.path,
command=NonShellCommand(
path=self.project.path,
command=["composer", "--no-ansi", event.button.script_name],
),
allow_rerun=True,
)
)
Expand All @@ -110,9 +113,10 @@ def on_update_package_clicked(
) -> None:
self.app.push_screen(
TerminalModal(
command=["composer", "--no-ansi", "update", event.package],
path=self.project.path,
use_stderr=True,
command=NonShellCommand(
self.project.path,
["composer", "--no-ansi", "update", event.package],
)
),
self.terminal_modal_callback,
)
Expand Down
40 changes: 31 additions & 9 deletions src/presentation/summary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
from textual import on

from models import Project
from presentation.component import TerminalModal
from models.project import ProjectAction
from presentation.component import (
TerminalModal,
Terminal,
ShellCommand,
NonShellCommand,
)


# service: Service = Provide[Container.service]
Expand Down Expand Up @@ -36,15 +42,31 @@ def compose(self):
)
if len(self.project.actions) > 0:
with Horizontal(id="summary-actions"):
for label in self.project.actions.keys():
yield Button(label, name=label)
for action in self.project.actions:
yield ProjectActionButton(action)

@on(Button.Pressed)
def on_pressed(self, event: Button.Pressed) -> None:
self.app.push_screen(
TerminalModal(
command=self.project.actions[event.button.name].split(" "),
path=self.project.path,
allow_rerun=True,
if not isinstance(event.button, ProjectActionButton):
return
action = event.button.action
if action.use_shell:
self.app.push_screen(
TerminalModal(
command=ShellCommand(path=self.project.path, command=action.command)
)
)
)
else:
self.app.push_screen(
TerminalModal(
command=NonShellCommand(
self.project.path, action.command.split(" ")
)
)
)


class ProjectActionButton(Button):
def __init__(self, action: ProjectAction, **kwargs):
self.action = action
super().__init__(label=action.label, name=action.label, **kwargs)

0 comments on commit bd0e52a

Please sign in to comment.