diff --git a/src/tmuxp/_internal/types.py b/src/tmuxp/_internal/types.py index a3521f5832..7565ebdce6 100644 --- a/src/tmuxp/_internal/types.py +++ b/src/tmuxp/_internal/types.py @@ -35,3 +35,66 @@ class PluginConfigSchema(TypedDict): tmuxp_min_version: NotRequired[str] tmuxp_max_version: NotRequired[str] tmuxp_version_incompatible: NotRequired[list[str]] + + +class ShellCommandConfig(TypedDict): + """Shell command configuration.""" + + cmd: str + enter: NotRequired[bool] + suppress_history: NotRequired[bool] + + +ShellCommandValue = t.Union[ + str, ShellCommandConfig, list[t.Union[str, ShellCommandConfig]] +] + + +class PaneConfig(TypedDict, total=False): + """Pane configuration.""" + + shell_command: NotRequired[ShellCommandValue] + shell_command_before: NotRequired[ShellCommandValue] + start_directory: NotRequired[str] + environment: NotRequired[dict[str, str]] + focus: NotRequired[str | bool] + suppress_history: NotRequired[bool] + target: NotRequired[str] + + +PaneValue = t.Union[str, PaneConfig, None] + + +class WindowConfig(TypedDict, total=False): + """Window configuration.""" + + window_name: str + start_directory: NotRequired[str] + shell_command_before: NotRequired[ShellCommandValue] + shell_command_after: NotRequired[ShellCommandValue] + layout: NotRequired[str] + clear: NotRequired[bool] + options: NotRequired[dict[str, t.Any]] + options_after: NotRequired[dict[str, t.Any]] + environment: NotRequired[dict[str, str]] + focus: NotRequired[str | bool] + suppress_history: NotRequired[bool] + panes: NotRequired[list[PaneValue]] + + +class WorkspaceConfig(TypedDict, total=False): + """Complete tmuxp workspace configuration.""" + + session_name: str | None # Can be None during import + start_directory: NotRequired[str] + before_script: NotRequired[str] + shell_command_before: NotRequired[ShellCommandValue] + shell_command: NotRequired[ShellCommandValue] # Used in import + environment: NotRequired[dict[str, str]] + global_options: NotRequired[dict[str, t.Any]] + options: NotRequired[dict[str, t.Any]] + config: NotRequired[str] # tmux config file path + socket_name: NotRequired[str] # tmux socket name + plugins: NotRequired[list[str | PluginConfigSchema]] + suppress_history: NotRequired[bool] + windows: list[WindowConfig] diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index a014e1558e..5e0210729c 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -17,6 +17,8 @@ if t.TYPE_CHECKING: import argparse + from tmuxp._internal.types import WorkspaceConfig + def get_tmuxinator_dir() -> pathlib.Path: """Return tmuxinator configuration directory. @@ -132,7 +134,7 @@ def create_import_subparser( class ImportConfigFn(t.Protocol): """Typing for import configuration callback function.""" - def __call__(self, workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: + def __call__(self, workspace_dict: dict[str, t.Any]) -> WorkspaceConfig: """Execute tmuxp import function.""" ... @@ -144,7 +146,9 @@ def import_config( ) -> None: """Import a configuration from a workspace_file.""" existing_workspace_file = ConfigReader._from_file(pathlib.Path(workspace_file)) - cfg_reader = ConfigReader(importfunc(existing_workspace_file)) + cfg_reader = ConfigReader( + t.cast(dict[t.Any, t.Any], importfunc(existing_workspace_file)) + ) workspace_file_format = prompt_choices( "Convert to", diff --git a/src/tmuxp/workspace/importers.py b/src/tmuxp/workspace/importers.py index fda361ca6f..967f7a44f9 100644 --- a/src/tmuxp/workspace/importers.py +++ b/src/tmuxp/workspace/importers.py @@ -4,8 +4,11 @@ import typing as t +if t.TYPE_CHECKING: + from tmuxp._internal.types import WindowConfig, WorkspaceConfig -def import_tmuxinator(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: + +def import_tmuxinator(workspace_dict: dict[str, t.Any]) -> WorkspaceConfig: """Return tmuxp workspace from a `tmuxinator`_ yaml workspace. .. _tmuxinator: https://github.com/aziz/tmuxinator @@ -18,8 +21,9 @@ def import_tmuxinator(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: Returns ------- dict + A dictionary conforming to WorkspaceConfig structure. """ - tmuxp_workspace: dict[str, t.Any] = {} + tmuxp_workspace: WorkspaceConfig = {"session_name": None, "windows": []} if "project_name" in workspace_dict: tmuxp_workspace["session_name"] = workspace_dict.pop("project_name") @@ -51,8 +55,6 @@ def import_tmuxinator(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: if "socket_name" in workspace_dict: tmuxp_workspace["socket_name"] = workspace_dict["socket_name"] - tmuxp_workspace["windows"] = [] - if "tabs" in workspace_dict: workspace_dict["windows"] = workspace_dict.pop("tabs") @@ -72,37 +74,46 @@ def import_tmuxinator(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: if "rbenv" in workspace_dict: if "shell_command_before" not in tmuxp_workspace: tmuxp_workspace["shell_command_before"] = [] + else: + # Ensure shell_command_before is a list + current = tmuxp_workspace["shell_command_before"] + if isinstance(current, str): + tmuxp_workspace["shell_command_before"] = [current] + elif isinstance(current, dict): + tmuxp_workspace["shell_command_before"] = [current] + # Now we can safely append + assert isinstance(tmuxp_workspace["shell_command_before"], list) tmuxp_workspace["shell_command_before"].append( "rbenv shell {}".format(workspace_dict["rbenv"]), ) - for window_dict in workspace_dict["windows"]: - for k, v in window_dict.items(): - window_dict = {"window_name": k} + for window_item in workspace_dict["windows"]: + for k, v in window_item.items(): + new_window: WindowConfig = {"window_name": k} if isinstance(v, str) or v is None: - window_dict["panes"] = [v] - tmuxp_workspace["windows"].append(window_dict) + new_window["panes"] = [v] + tmuxp_workspace["windows"].append(new_window) continue if isinstance(v, list): - window_dict["panes"] = v - tmuxp_workspace["windows"].append(window_dict) + new_window["panes"] = v + tmuxp_workspace["windows"].append(new_window) continue if "pre" in v: - window_dict["shell_command_before"] = v["pre"] + new_window["shell_command_before"] = v["pre"] if "panes" in v: - window_dict["panes"] = v["panes"] + new_window["panes"] = v["panes"] if "root" in v: - window_dict["start_directory"] = v["root"] + new_window["start_directory"] = v["root"] if "layout" in v: - window_dict["layout"] = v["layout"] - tmuxp_workspace["windows"].append(window_dict) + new_window["layout"] = v["layout"] + tmuxp_workspace["windows"].append(new_window) return tmuxp_workspace -def import_teamocil(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: +def import_teamocil(workspace_dict: dict[str, t.Any]) -> WorkspaceConfig: """Return tmuxp workspace from a `teamocil`_ yaml workspace. .. _teamocil: https://github.com/remiprev/teamocil @@ -112,6 +123,11 @@ def import_teamocil(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: workspace_dict : dict python dict for tmuxp workspace + Returns + ------- + dict + A dictionary conforming to WorkspaceConfig structure. + Notes ----- Todos: @@ -122,7 +138,7 @@ def import_teamocil(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: - clear - cmd_separator """ - tmuxp_workspace: dict[str, t.Any] = {} + tmuxp_workspace: WorkspaceConfig = {"session_name": None, "windows": []} if "session" in workspace_dict: workspace_dict = workspace_dict["session"] @@ -132,21 +148,17 @@ def import_teamocil(workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]: if "root" in workspace_dict: tmuxp_workspace["start_directory"] = workspace_dict.pop("root") - tmuxp_workspace["windows"] = [] - for w in workspace_dict["windows"]: - window_dict = {"window_name": w["name"]} + window_dict: WindowConfig = {"window_name": w["name"]} if "clear" in w: window_dict["clear"] = w["clear"] if "filters" in w: if "before" in w["filters"]: - for _b in w["filters"]["before"]: - window_dict["shell_command_before"] = w["filters"]["before"] + window_dict["shell_command_before"] = w["filters"]["before"] if "after" in w["filters"]: - for _b in w["filters"]["after"]: - window_dict["shell_command_after"] = w["filters"]["after"] + window_dict["shell_command_after"] = w["filters"]["after"] if "root" in w: window_dict["start_directory"] = w.pop("root")