diff --git a/CHANGES b/CHANGES index d5816714ec9..139caf082da 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,34 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force +### Internal refactoring (#840) + +#### Rename config to workspace + +Reference to "config" are now "workspace" + +#### Organize files + +- `cli/utils.py` functions moved to `workspace/finders.py` +- `config.py` split between: + + - `workspace/finders.py` + - `workspace/freezer.py` + - `workspace/importers.py` + - `workspace/validation.py` + +- `workspacebuilder.py` split into: + + - `workspace/builder.py` + - `workspace/freezer.py` + + `config.inline` moved to freezer + +#### Tests + +- `tests/fixtures/{workspacebuilder,workspacefreezer}` -> `tests/fixtures/workspace/{builder,freezer}` +- `tests/test_import_{teamocil,tmuxinator}.py` -> `tests/workspace/test_import_{teamocil,tmuxinator}.py` + ## tmuxp 1.17.3 (2022-10-30) **Maintenance release, no features or fixes** diff --git a/conftest.py b/conftest.py index a9c51dffb9f..0b8ae523d7d 100644 --- a/conftest.py +++ b/conftest.py @@ -15,7 +15,7 @@ from libtmux.test import namer from tests.fixtures import utils as test_utils -from tmuxp.cli.utils import get_config_dir +from tmuxp.workspace.finders import get_workspace_dir if t.TYPE_CHECKING: from libtmux.session import Session @@ -56,7 +56,7 @@ def tmuxp_configdir_default( monkeypatch: pytest.MonkeyPatch, tmuxp_configdir: pathlib.Path ) -> None: monkeypatch.setenv("TMUXP_CONFIGDIR", str(tmuxp_configdir)) - assert get_config_dir() == str(tmuxp_configdir) + assert get_workspace_dir() == str(tmuxp_configdir) @pytest.fixture(scope="function") diff --git a/docs/about.md b/docs/about.md index 225a862f3a8..f5833dde217 100644 --- a/docs/about.md +++ b/docs/about.md @@ -94,7 +94,7 @@ from JSON and YAML. - `$ tmuxp -2`, `$ tmuxp -8` for forcing term colors a la {term}`tmux(1)`. - `$ tmuxp -L`, `$ tmuxp -S` for sockets and - `$ tmuxp -f` for config file. + `$ tmuxp -f ` for config file. [attempt at 1.7 test]: https://travis-ci.org/tmux-python/tmuxp/jobs/12348263 [mit-licensed]: http://opensource.org/licenses/MIT diff --git a/docs/api.md b/docs/api.md index b416dcb22cd..7897b05bc80 100644 --- a/docs/api.md +++ b/docs/api.md @@ -45,10 +45,6 @@ If you need an internal API stabilized please [file an issue](https://github.com ## CLI -```{eval-rst} -.. automethod:: tmuxp.cli.utils.get_config_dir -``` - ```{eval-rst} .. automethod:: tmuxp.cli.import_config.get_teamocil_dir ``` @@ -65,50 +61,54 @@ If you need an internal API stabilized please [file an issue](https://github.com .. automethod:: tmuxp.cli.load._reattach ``` -## Configuration +## Workspace files ### Finding ```{eval-rst} -.. automethod:: tmuxp.config.is_config_file +.. automethod:: tmuxp.workspace.finders.is_workspace_file ``` ```{eval-rst} -.. automethod:: tmuxp.config.in_dir +.. automethod:: tmuxp.workspace.finders.in_dir ``` ```{eval-rst} -.. automethod:: tmuxp.config.in_cwd +.. automethod:: tmuxp.workspace.finders.in_cwd ``` -### Import and export - ```{eval-rst} -.. automethod:: tmuxp.config.validate_schema +.. automethod:: tmuxp.workspace.finders.get_workspace_dir ``` +### Validation + ```{eval-rst} -.. automethod:: tmuxp.config.expandshell +.. autofunction:: tmuxp.workspace.validation.validate_schema ``` +### Processing + ```{eval-rst} -.. automethod:: tmuxp.config.expand +.. automethod:: tmuxp.workspace.loader.expandshell ``` ```{eval-rst} -.. automethod:: tmuxp.config.inline +.. automethod:: tmuxp.workspace.loader.expand ``` ```{eval-rst} -.. automethod:: tmuxp.config.trickle +.. automethod:: tmuxp.workspace.loader.trickle ``` +## Workspace importers + ```{eval-rst} -.. automethod:: tmuxp.config.import_teamocil +.. automethod:: tmuxp.workspace.importers.import_teamocil ``` ```{eval-rst} -.. automethod:: tmuxp.config.import_tmuxinator +.. automethod:: tmuxp.workspace.importers.import_tmuxinator ``` ## Configuration reader @@ -120,22 +120,28 @@ If you need an internal API stabilized please [file an issue](https://github.com ## Workspace Builder ```{eval-rst} -.. autoclass:: tmuxp.workspacebuilder.WorkspaceBuilder +.. autoclass:: tmuxp.workspace.builder.WorkspaceBuilder :members: ``` +## Workspace Freezer + +```{eval-rst} +.. automethod:: tmuxp.workspace.freezer.freeze +``` + ```{eval-rst} -.. automethod:: tmuxp.workspacebuilder.freeze +.. automethod:: tmuxp.workspace.freezer.inline ``` ## Exceptions ```{eval-rst} -.. autoexception:: tmuxp.exc.EmptyConfigException +.. autoexception:: tmuxp.exc.EmptyWorkspaceException ``` ```{eval-rst} -.. autoexception:: tmuxp.exc.ConfigError +.. autoexception:: tmuxp.exc.WorkspaceError ``` ```{eval-rst} diff --git a/docs/cli/freeze.md b/docs/cli/freeze.md index 63d0dc0625b..fa4e2ad7593 100644 --- a/docs/cli/freeze.md +++ b/docs/cli/freeze.md @@ -34,4 +34,4 @@ Tmuxp will offer to save your session state to `.json` or `.yaml`. If no session is specified, it will default to the attached session. -If the `--force` argument is passed, it will overwrite any existing config file with the same name. +If the `--force` argument is passed, it will overwrite any existing workspace file with the same name. diff --git a/docs/cli/load.md b/docs/cli/load.md index f12e6a7177c..1be15adcf08 100644 --- a/docs/cli/load.md +++ b/docs/cli/load.md @@ -84,9 +84,9 @@ $ tmuxp load [filename] ## Inside sessions -If you try to load a config file from within a tmux session, it will ask you +If you try to load a workspace file from within a tmux session, it will ask you if you want to load and attach to the new session, or just load detached. -You can also load a config file and append the windows to the current active session. +You can also load a workspace file and append the windows to the current active session. ``` Already inside TMUX, switch to session? yes/no diff --git a/docs/configuration/examples.md b/docs/configuration/examples.md index 8d9a8da09bd..2537a86e955 100644 --- a/docs/configuration/examples.md +++ b/docs/configuration/examples.md @@ -699,7 +699,7 @@ Edit this page. - You can use `start_directory: ./` to make the directories relative to -the config file / project root. +the workspace file / project root. ## Bonus: pipenv auto-bootstrapping @@ -761,8 +761,8 @@ windows: tmuxp sessions can be scripted in python. The first way is to use the ORM in the {ref}`API`. The second is to pass a {py:obj}`dict` into -{class}`~tmuxp.workspacebuilder.WorkspaceBuilder` with a correct schema. -See: {meth}`tmuxp.config.validate_schema`. +{class}`~tmuxp.workspace.builder.WorkspaceBuilder` with a correct schema. +See: {meth}`tmuxp.validation.validate_schema`. ::: diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 3fbc09dcde8..b12abdaaccc 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -4,7 +4,7 @@ # Configuration -tmuxp loads your terminal workspace into tmux using config files. +tmuxp loads your terminal workspace into tmux using workspace files. The configuration file can be JSON or YAML. It's declarative style resembles tmux's object hierarchy: session, window and wanes. @@ -24,7 +24,7 @@ tmuxp will offers to assist when: - _When inside a tmux client_, `tmuxp` will let you create a new session and switch to it, or append the windows to your existing session. -## What's in a config file? +## What's in a workspace file? 1. A session name 2. A list of _windows_ @@ -98,14 +98,14 @@ Breaking down the basic configuration into sections: # command options ``` -## Where do I store config files? +## Where do I store workspace files? ### Direct You can create a configuration and load it from anywhere in your file system. ```console -$ tmuxp load [config_file] +$ tmuxp load [workspace-file] ``` ````{tab} Relative diff --git a/docs/configuration/top-level.md b/docs/configuration/top-level.md index 7976c6605c0..72eb24f32fb 100644 --- a/docs/configuration/top-level.md +++ b/docs/configuration/top-level.md @@ -12,7 +12,7 @@ Used for: Notes: -- Session names may differ from config filename. +- Session names may differ from workspace filename. e.g. _apple.yaml_: diff --git a/docs/developing.md b/docs/developing.md index 42bf06c5006..1579c979df7 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -159,19 +159,19 @@ $ env PYTEST_ADDOPTS="-verbose" make start Pick a file: ```console -$ env PYTEST_ADDOPTS="tests/test_workspacebuilder.py" poetry run make start +$ env PYTEST_ADDOPTS="tests/workspace/test_builder.py" poetry run make start ``` -Drop into `test_automatic_rename_option()` in `tests/test_workspacebuilder.py`: +Drop into `test_automatic_rename_option()` in `tests/workspace/test_builder.py`: ```console -$ env PYTEST_ADDOPTS="-s -x -vv tests/test_workspacebuilder.py" poetry run make start +$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py" poetry run make start ``` -Drop into `test_automatic_rename_option()` in `tests/test_workspacebuilder.py` and stop on first error: +Drop into `test_automatic_rename_option()` in `tests/workspace/test_builder.py` and stop on first error: ```console -$ env PYTEST_ADDOPTS="-s -x -vv tests/test_workspacebuilder.py::test_automatic_rename_option" poetry run make start +$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py::test_automatic_rename_option" poetry run make start ``` Drop into `pdb` on first error: @@ -237,7 +237,7 @@ Create two terminals: version of tmuxp above. Then: ```console - $ py.test tests/test_workspacebuilder.py + $ py.test tests/workspace/test_builder.py ``` Terminal 1 should have flickered and built the session before your eyes. diff --git a/examples/start-directory.json b/examples/start-directory.json index 68bb55b53c0..2bc00033d51 100644 --- a/examples/start-directory.json +++ b/examples/start-directory.json @@ -51,9 +51,9 @@ { "shell_command": [ "echo '\\033c", - "./ is relative to config file location", - "../ will be parent of config file", - "./test will be \\\"test\\\" dir inside dir of config file'" + "./ is relative to workspace file location", + "../ will be parent of workspace file", + "./test will be \\\"test\\\" dir inside dir of workspace file'" ] }, { diff --git a/examples/start-directory.yaml b/examples/start-directory.yaml index 15534f25ce2..dcecf67b879 100644 --- a/examples/start-directory.yaml +++ b/examples/start-directory.yaml @@ -26,14 +26,15 @@ windows: panes: - echo '\033c absolute paths also have precedence.' - echo hello - - window_name: should be config's dir + - window_name: should be workspace file's dir + start_directory: ./ panes: - shell_command: - echo '\033c - - ./ is relative to config file location - - ../ will be parent of config file - - ./test will be \"test\" dir inside dir of config file' + - ./ is relative to workspace file location + - ../ will be parent of workspace file + - ./test will be \"test\" dir inside dir of workspace file' - shell_command: - echo '\033c - This way you can load up workspaces from projects and maintain diff --git a/src/tmuxp/__init__.py b/src/tmuxp/__init__.py index 145a640bc74..1601772c9ed 100644 --- a/src/tmuxp/__init__.py +++ b/src/tmuxp/__init__.py @@ -8,7 +8,7 @@ :license: MIT, see LICENSE for details """ -from . import cli, config, util +from . import cli, util from .__about__ import ( __author__, __copyright__, diff --git a/src/tmuxp/cli/__init__.py b/src/tmuxp/cli/__init__.py index e676bed51d9..a925feece20 100644 --- a/src/tmuxp/cli/__init__.py +++ b/src/tmuxp/cli/__init__.py @@ -68,12 +68,12 @@ def create_parser() -> argparse.ArgumentParser: ) create_shell_subparser(shell_parser) import_parser = subparsers.add_parser( - "import", help="import configurations from teamocil and tmuxinator." + "import", help="import workspaces from teamocil and tmuxinator." ) create_import_subparser(import_parser) convert_parser = subparsers.add_parser( - "convert", help="convert configs between yaml and json." + "convert", help="convert workspace files between yaml and json." ) create_convert_subparser(convert_parser) @@ -82,14 +82,14 @@ def create_parser() -> argparse.ArgumentParser: ) create_debug_info_subparser(debug_info_parser) - ls_parser = subparsers.add_parser("ls", help="list sessions in config directory") + ls_parser = subparsers.add_parser("ls", help="list workspaces in tmuxp directory") create_ls_subparser(ls_parser) - edit_parser = subparsers.add_parser("edit", help="run $EDITOR on config") + edit_parser = subparsers.add_parser("edit", help="run $EDITOR on workspace file") create_edit_subparser(edit_parser) freeze_parser = subparsers.add_parser( - "freeze", help="freeze a live tmux session to a tmuxp config" + "freeze", help="freeze a live tmux session to a tmuxp workspace file" ) create_freeze_subparser(freeze_parser) @@ -147,17 +147,17 @@ def cli(_args: t.Optional[t.List[str]] = None) -> None: return elif import_subparser_name == "teamocil": command_import_teamocil( - config_file=args.config_file, + workspace_file=args.workspace_file, parser=parser, ) elif import_subparser_name == "tmuxinator": command_import_tmuxinator( - config_file=args.config_file, + workspace_file=args.workspace_file, parser=parser, ) elif args.subparser_name == "convert": command_convert( - config_file=args.config_file, + workspace_file=args.workspace_file, answer_yes=args.answer_yes, parser=parser, ) @@ -166,7 +166,7 @@ def cli(_args: t.Optional[t.List[str]] = None) -> None: elif args.subparser_name == "edit": command_edit( - config_file=args.config_file, + workspace_file=args.workspace_file, parser=parser, ) elif args.subparser_name == "freeze": @@ -184,7 +184,7 @@ def startup(config_dir: pathlib.Path) -> None: Parameters ---------- - str : get_config_dir(): Config directory to search + str : get_workspace_dir(): Config directory to search """ if not os.path.exists(config_dir): diff --git a/src/tmuxp/cli/constants.py b/src/tmuxp/cli/constants.py deleted file mode 100644 index c5abebb7b10..00000000000 --- a/src/tmuxp/cli/constants.py +++ /dev/null @@ -1 +0,0 @@ -VALID_CONFIG_DIR_FILE_EXTENSIONS = [".yaml", ".yml", ".json"] diff --git a/src/tmuxp/cli/convert.py b/src/tmuxp/cli/convert.py index be9548004f5..6236b58551a 100644 --- a/src/tmuxp/cli/convert.py +++ b/src/tmuxp/cli/convert.py @@ -4,23 +4,24 @@ import typing as t from tmuxp.config_reader import ConfigReader +from tmuxp.workspace.finders import find_workspace_file, get_workspace_dir -from .utils import get_config_dir, prompt_yes_no, scan_config +from .utils import prompt_yes_no def create_convert_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: - config_file = parser.add_argument( - dest="config_file", + workspace_file = parser.add_argument( + dest="workspace_file", type=str, - metavar="config-file", - help="checks tmuxp and current directory for config files.", + metavar="workspace-file", + help="checks tmuxp and current directory for workspace files.", ) try: import shtab - config_file.complete = shtab.FILE # type: ignore + workspace_file.complete = shtab.FILE # type: ignore except ImportError: pass @@ -35,17 +36,19 @@ def create_convert_subparser( def command_convert( - config_file: t.Union[str, pathlib.Path], + workspace_file: t.Union[str, pathlib.Path], answer_yes: bool, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: """Convert a tmuxp config between JSON and YAML.""" - config_file = scan_config(config_file, config_dir=get_config_dir()) + workspace_file = find_workspace_file( + workspace_file, workspace_dir=get_workspace_dir() + ) - if isinstance(config_file, str): - config_file = pathlib.Path(config_file) + if isinstance(workspace_file, str): + workspace_file = pathlib.Path(workspace_file) - _, ext = os.path.splitext(config_file) + _, ext = os.path.splitext(workspace_file) ext = ext.lower() if ext == ".json": to_filetype = "yaml" @@ -54,19 +57,19 @@ def command_convert( else: raise Exception(f"Unknown filetype: {ext} (valid: [.json, .yaml, .yml])") - configparser = ConfigReader.from_file(config_file) - newfile = config_file.parent / (str(config_file.stem) + f".{to_filetype}") + configparser = ConfigReader.from_file(workspace_file) + newfile = workspace_file.parent / (str(workspace_file.stem) + f".{to_filetype}") export_kwargs = {"default_flow_style": False} if to_filetype == "yaml" else {} - new_config = configparser.dump(format=to_filetype, indent=2, **export_kwargs) + new_workspace = configparser.dump(format=to_filetype, indent=2, **export_kwargs) if not answer_yes: - if prompt_yes_no(f"Convert to <{config_file}> to {to_filetype}?"): - if prompt_yes_no("Save config to %s?" % newfile): + if prompt_yes_no(f"Convert to <{workspace_file}> to {to_filetype}?"): + if prompt_yes_no("Save workspace to %s?" % newfile): answer_yes = True if answer_yes: buf = open(newfile, "w") - buf.write(new_config) + buf.write(new_workspace) buf.close() - print(f"New config saved to <{newfile}>.") + print(f"New workspace file saved to <{newfile}>.") diff --git a/src/tmuxp/cli/edit.py b/src/tmuxp/cli/edit.py index bc0864ce2c1..184077c3cc7 100644 --- a/src/tmuxp/cli/edit.py +++ b/src/tmuxp/cli/edit.py @@ -4,26 +4,26 @@ import subprocess import typing as t -from .utils import scan_config +from tmuxp.workspace.finders import find_workspace_file def create_edit_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: parser.add_argument( - dest="config_file", - metavar="config-file", + dest="workspace_file", + metavar="workspace-file", type=str, - help="checks current tmuxp and current directory for config files.", + help="checks current tmuxp and current directory for workspace files.", ) return parser def command_edit( - config_file: t.Union[str, pathlib.Path], + workspace_file: t.Union[str, pathlib.Path], parser: t.Optional[argparse.ArgumentParser] = None, ): - config_file = scan_config(config_file) + workspace_file = find_workspace_file(workspace_file) sys_editor = os.environ.get("EDITOR", "vim") - subprocess.call([sys_editor, config_file]) + subprocess.call([sys_editor, workspace_file]) diff --git a/src/tmuxp/cli/freeze.py b/src/tmuxp/cli/freeze.py index d8551bf5dda..62422c3f141 100644 --- a/src/tmuxp/cli/freeze.py +++ b/src/tmuxp/cli/freeze.py @@ -7,10 +7,11 @@ from libtmux.server import Server from tmuxp.config_reader import ConfigReader from tmuxp.exc import TmuxpException +from tmuxp.workspace.finders import get_workspace_dir -from .. import config, util -from ..workspacebuilder import freeze -from .utils import get_config_dir, prompt, prompt_choices, prompt_yes_no +from .. import util +from ..workspace import freezer +from .utils import prompt, prompt_choices, prompt_yes_no if t.TYPE_CHECKING: from typing_extensions import Literal, TypeAlias, TypeGuard @@ -22,7 +23,7 @@ class CLIFreezeNamespace(argparse.Namespace): session_name: str socket_name: t.Optional[str] socket_path: t.Optional[str] - config_format: t.Optional["CLIOutputFormatLiteral"] + workspace_format: t.Optional["CLIOutputFormatLiteral"] save_to: t.Optional[str] answer_yes: t.Optional[bool] quiet: t.Optional[bool] @@ -52,7 +53,7 @@ def create_freeze_subparser( ) parser.add_argument( "-f", - "--config-format", + "--workspace-format", choices=["yaml", "json"], help="format to save in", ) @@ -78,7 +79,10 @@ def create_freeze_subparser( help="don't prompt for confirmation", ) parser.add_argument( - "--force", dest="force", action="store_true", help="overwrite the config file" + "--force", + dest="force", + action="store_true", + help="overwrite the workspace file", ) return parser @@ -88,7 +92,7 @@ def command_freeze( args: CLIFreezeNamespace, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Snapshot a session into a config. + """Snapshot a tmux session into a tmuxp workspace. If SESSION_NAME is provided, snapshot that session. Otherwise, use the current session.""" @@ -106,9 +110,9 @@ def command_freeze( print(e) return - sconf = freeze(session) - newconfig = config.inline(sconf) - configparser = ConfigReader(newconfig) + frozen_workspace = freezer.freeze(session) + workspace = freezer.inline(frozen_workspace) + configparser = ConfigReader(workspace) if not args.quiet: print( @@ -119,7 +123,7 @@ def command_freeze( if not ( args.answer_yes or prompt_yes_no( - "The new config *WILL* require adjusting afterwards. Save config?" + "The new workspace will require adjusting afterwards. Save workspace file?" ) ): if not args.quiet: @@ -134,8 +138,11 @@ def command_freeze( while not dest: save_to = os.path.abspath( os.path.join( - get_config_dir(), - "{}.{}".format(sconf.get("session_name"), args.config_format or "yaml"), + get_workspace_dir(), + "{}.{}".format( + frozen_workspace.get("session_name"), + args.workspace_format or "yaml", + ), ) ) dest_prompt = prompt( @@ -148,16 +155,18 @@ def command_freeze( dest = dest_prompt dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest))) - config_format = args.config_format + workspace_format = args.workspace_format - valid_config_formats: t.List["CLIOutputFormatLiteral"] = ["json", "yaml"] + valid_workspace_formats: t.List["CLIOutputFormatLiteral"] = ["json", "yaml"] def is_valid_ext(stem: t.Optional[str]) -> "TypeGuard[CLIOutputFormatLiteral]": - return stem in valid_config_formats + return stem in valid_workspace_formats - if not is_valid_ext(config_format): + if not is_valid_ext(workspace_format): - def extract_config_format(val: str) -> t.Optional["CLIOutputFormatLiteral"]: + def extract_workspace_format( + val: str, + ) -> t.Optional["CLIOutputFormatLiteral"]: suffix = pathlib.Path(val).suffix if isinstance(suffix, str): suffix = suffix.lower().lstrip(".") @@ -165,28 +174,28 @@ def extract_config_format(val: str) -> t.Optional["CLIOutputFormatLiteral"]: return suffix return None - config_format = extract_config_format(dest) - if not is_valid_ext(config_format): - config_format = prompt_choices( + workspace_format = extract_workspace_format(dest) + if not is_valid_ext(workspace_format): + workspace_format = prompt_choices( "Couldn't ascertain one of [%s] from file name. Convert to" - % ", ".join(valid_config_formats), - choices=valid_config_formats, + % ", ".join(valid_workspace_formats), + choices=valid_workspace_formats, default="yaml", ) - if config_format == "yaml": - newconfig = configparser.dump( + if workspace_format == "yaml": + workspace = configparser.dump( format="yaml", indent=2, default_flow_style=False, safe=True ) - elif config_format == "json": - newconfig = configparser.dump(format="json", indent=2) + elif workspace_format == "json": + workspace = configparser.dump(format="json", indent=2) if args.answer_yes or prompt_yes_no("Save to %s?" % dest): destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) buf = open(dest, "w") - buf.write(newconfig) + buf.write(workspace) buf.close() if not args.quiet: diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index 14a40076ce7..895ee6f2141 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -5,9 +5,10 @@ import typing as t from tmuxp.config_reader import ConfigReader +from tmuxp.workspace.finders import find_workspace_file -from .. import config -from .utils import prompt, prompt_choices, prompt_yes_no, scan_config, tmuxp_echo +from ..workspace import importers +from .utils import prompt, prompt_choices, prompt_yes_no, tmuxp_echo def get_tmuxinator_dir() -> str: @@ -23,7 +24,7 @@ def get_tmuxinator_dir() -> str: See Also -------- - :meth:`tmuxp.config.import_tmuxinator` + :meth:`tmuxp.workspace.importers.tmuxinator.import_tmuxinator` """ if "TMUXINATOR_CONFIG" in os.environ: return os.path.expanduser(os.environ["TMUXINATOR_CONFIG"]) @@ -42,20 +43,20 @@ def get_teamocil_dir() -> str: See Also -------- - :meth:`tmuxp.config.import_teamocil` + :meth:`tmuxp.workspace.importers.teamocil.import_teamocil` """ return os.path.expanduser("~/.teamocil/") -def _resolve_path_no_overwrite(config: str) -> str: - path = pathlib.Path(config).resolve() +def _resolve_path_no_overwrite(workspace_file: str) -> str: + path = pathlib.Path(workspace_file).resolve() if path.exists(): raise ValueError("%s exists. Pick a new filename." % path) return str(path) def command_import( - config_file: str, + workspace_file: str, print_list: str, parser: argparse.ArgumentParser, ): @@ -74,11 +75,11 @@ def create_import_subparser( ) import_teamocilgroup = import_teamocil.add_mutually_exclusive_group(required=True) - teamocil_config_file = import_teamocilgroup.add_argument( - dest="config_file", + teamocil_workspace_file = import_teamocilgroup.add_argument( + dest="workspace_file", type=str, nargs="?", - metavar="config-file", + metavar="workspace-file", help="checks current ~/.teamocil and current directory for yaml files", ) import_teamocil.set_defaults( @@ -92,11 +93,11 @@ def create_import_subparser( import_tmuxinatorgroup = import_tmuxinator.add_mutually_exclusive_group( required=True ) - tmuxinator_config_file = import_tmuxinatorgroup.add_argument( - dest="config_file", + tmuxinator_workspace_file = import_tmuxinatorgroup.add_argument( + dest="workspace_file", type=str, nargs="?", - metavar="config-file", + metavar="workspace-file", help="checks current ~/.tmuxinator and current directory for yaml files", ) @@ -107,8 +108,8 @@ def create_import_subparser( try: import shtab - teamocil_config_file.complete = shtab.FILE # type: ignore - tmuxinator_config_file.complete = shtab.FILE # type: ignore + teamocil_workspace_file.complete = shtab.FILE # type: ignore + tmuxinator_workspace_file.complete = shtab.FILE # type: ignore except ImportError: pass @@ -116,20 +117,20 @@ def create_import_subparser( def import_config( - config_file: str, + workspace_file: str, importfunc: t.Callable, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - existing_config = ConfigReader._from_file(pathlib.Path(config_file)) - cfg_reader = ConfigReader(importfunc(existing_config)) + existing_workspace_file = ConfigReader._from_file(pathlib.Path(workspace_file)) + cfg_reader = ConfigReader(importfunc(existing_workspace_file)) - config_format = prompt_choices( + workspace_file_format = prompt_choices( "Convert to", choices=["yaml", "json"], default="yaml" ) - if config_format == "yaml": + if workspace_file_format == "yaml": new_config = cfg_reader.dump("yaml", indent=2, default_flow_style=False) - elif config_format == "json": + elif workspace_file_format == "json": new_config = cfg_reader.dump("json", indent=2) else: sys.exit("Unknown config format.") @@ -167,21 +168,25 @@ def import_config( def command_import_tmuxinator( - config_file: str, + workspace_file: str, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Convert a tmuxinator config from config_file to tmuxp format and import + """Convert a tmuxinator config from workspace_file to tmuxp format and import it into tmuxp.""" - config_file = scan_config(config_file, config_dir=get_tmuxinator_dir()) - import_config(config_file, config.import_tmuxinator) + workspace_file = find_workspace_file( + workspace_file, workspace_dir=get_tmuxinator_dir() + ) + import_config(workspace_file, importers.import_tmuxinator) def command_import_teamocil( - config_file: str, + workspace_file: str, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Convert a teamocil config from config_file to tmuxp format and import + """Convert a teamocil config from workspace_file to tmuxp format and import it into tmuxp.""" - config_file = scan_config(config_file, config_dir=get_teamocil_dir()) + workspace_file = find_workspace_file( + workspace_file, workspace_dir=get_teamocil_dir() + ) - import_config(config_file, config.import_teamocil) + import_config(workspace_file, importers.import_teamocil) diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index 7ae88210f53..6443bcb05f0 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -1,4 +1,4 @@ -"""Command line tool for managing tmux workspaces and tmuxp configurations. +"""Command line tool for managing tmuxp workspaces. tmuxp.cli.load ~~~~~~~~~~~~~~ @@ -18,16 +18,11 @@ from libtmux.session import Session from tmuxp.types import StrPath -from .. import config, config_reader, exc, log, util -from ..workspacebuilder import WorkspaceBuilder -from .utils import ( - get_config_dir, - prompt_choices, - prompt_yes_no, - scan_config, - style, - tmuxp_echo, -) +from .. import config_reader, exc, log, util +from ..workspace import loader +from ..workspace.builder import WorkspaceBuilder +from ..workspace.finders import find_workspace_file, get_workspace_dir, in_dir +from .utils import prompt_choices, prompt_yes_no, style, tmuxp_echo if t.TYPE_CHECKING: from typing_extensions import Literal, NotRequired, TypeAlias, TypedDict @@ -40,7 +35,7 @@ class OptionOverrides(TypedDict): class CLILoadNamespace(argparse.Namespace): - config_files: t.List[str] + workspace_files: t.List[str] socket_name: t.Optional[str] socket_path: t.Optional[str] tmux_config_file: t.Optional[str] @@ -114,7 +109,7 @@ def set_layout_hook(session: Session, hook_name: str) -> None: def load_plugins(sconf: t.Any) -> t.List[t.Any]: """ - Load and return plugins in config + Load and return plugins in workspace """ plugins = [] if "plugins" in sconf: @@ -153,7 +148,7 @@ def _reattach(builder: WorkspaceBuilder): Parameters ---------- - builder: :class:`workspacebuilder.WorkspaceBuilder` + builder: :class:`workspace.builder.WorkspaceBuilder` Notes ----- @@ -178,11 +173,11 @@ def _reattach(builder: WorkspaceBuilder): def _load_attached(builder: WorkspaceBuilder, detached: bool) -> None: """ - Load config in new session + Load workspace in new session Parameters ---------- - builder: :class:`workspacebuilder.WorkspaceBuilder` + builder: :class:`workspace.builder.WorkspaceBuilder` detached : bool """ builder.build() @@ -211,11 +206,11 @@ def _load_attached(builder: WorkspaceBuilder, detached: bool) -> None: def _load_detached(builder: WorkspaceBuilder) -> None: """ - Load config in new session but don't attach + Load workspace in new session but don't attach Parameters ---------- - builder: :class:`workspacebuilder.WorkspaceBuilder` + builder: :class:`workspace.builder.WorkspaceBuilder` """ builder.build() @@ -228,11 +223,11 @@ def _load_detached(builder: WorkspaceBuilder) -> None: def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None: """ - Load config as new windows in current session + Load workspace as new windows in current session Parameters ---------- - builder: :class:`workspacebuilder.WorkspaceBuilder` + builder: :class:`workspace.builder.WorkspaceBuilder` """ current_attached_session = builder.find_current_attached_session() builder.build(current_attached_session, append=True) @@ -247,7 +242,7 @@ def _setup_plugins(builder: WorkspaceBuilder) -> Session: Parameters ---------- - builder: :class:`workspacebuilder.WorkspaceBuilder` + builder: :class:`workspace.builder.WorkspaceBuilder` """ for plugin in builder.plugins: plugin.before_script(builder.session) @@ -256,7 +251,7 @@ def _setup_plugins(builder: WorkspaceBuilder) -> Session: def load_workspace( - config_file: StrPath, + workspace_file: StrPath, socket_name: t.Optional[str] = None, socket_path: None = None, tmux_config_file: None = None, @@ -271,7 +266,7 @@ def load_workspace( Parameters ---------- - config_file : list of str + workspace_file : list of str paths or session names to workspace files socket_name : str, optional ``tmux -L `` @@ -294,33 +289,33 @@ def load_workspace( Notes ----- - tmuxp will check and load a configuration file. The file will use ConfigReader - to load a JSON/YAML into a :py:obj:`dict`. Then :func:`config.expand` and - :func:`config.trickle` will be used to expand any shorthands, template + tmuxp will check and load a workspace file. The file will use ConfigReader + to load a JSON/YAML into a :py:obj:`dict`. Then :func:`loader.expand` and + :func:`loader.trickle` will be used to expand any shorthands, template variables, or file paths relative to where the config/script is executed from. - :func:`config.expand` accepts the directory of the config file, so the - user's configuration can resolve absolute paths relative to where the - config file is. In otherwords, if a config file at */var/moo/hi.yaml* - has *./* in its configs, we want to be sure any file path with *./* is + :func:`loader.expand` accepts the directory of the config file, so the + user's workspace can resolve absolute paths relative to where the + workspace file is. In otherwords, if a workspace file at */var/moo/hi.yaml* + has *./* in its workspaces, we want to be sure any file path with *./* is relative to */var/moo*, not the user's PWD. A :class:`libtmux.Server` object is created. No tmux server is started yet, just the object. - The prepared configuration and the server object is passed into an instance - of :class:`~tmuxp.workspacebuilder.WorkspaceBuilder`. + The prepared workspace and its server object is passed into an instance + of :class:`~tmuxp.workspace.builder.WorkspaceBuilder`. A sanity check against :meth:`libtmux.common.which` is ran. It will raise an exception if tmux isn't found. If a tmux session under the same name as ``session_name`` in the tmuxp - configuration exists, tmuxp offers to attach the session. Currently, tmuxp + workspace exists, tmuxp offers to attach the session. Currently, tmuxp does not allow appending a workspace / incremental building on top of a current session (pull requests are welcome!). - :meth:`~tmuxp.workspacebuilder.WorkspaceBuilder.build` will build the session in + :meth:`~tmuxp.workspace.builder.build` will build the session in the background via using tmux's detached state (``-d``). After the session (workspace) is built, unless the user decided to load @@ -361,42 +356,47 @@ def load_workspace( Accessed April 8th, 2018. """ # get the canonical path, eliminating any symlinks - if isinstance(config_file, (str, os.PathLike)): - config_file = pathlib.Path(config_file) + if isinstance(workspace_file, (str, os.PathLike)): + workspace_file = pathlib.Path(workspace_file) tmuxp_echo( - style("[Loading] ", fg="green") + style(str(config_file), fg="blue", bold=True) + style("[Loading] ", fg="green") + + style(str(workspace_file), fg="blue", bold=True) ) # ConfigReader allows us to open a yaml or json file as a dict - raw_config = config_reader.ConfigReader._from_file(config_file) or {} + raw_workspace = config_reader.ConfigReader._from_file(workspace_file) or {} - # shapes configurations relative to config / profile file location - sconfig = config.expand(raw_config, cwd=os.path.dirname(config_file)) - # Overwrite session name + # shapes workspaces relative to config / profile file location + expanded_workspace = loader.expand( + raw_workspace, cwd=os.path.dirname(workspace_file) + ) + + # Overridden session name if new_session_name: - sconfig["session_name"] = new_session_name - # propagate config inheritance (e.g. session -> window, window -> pane) - sconfig = config.trickle(sconfig) + expanded_workspace["session_name"] = new_session_name + + # propagate workspace inheritance (e.g. session -> window, window -> pane) + expanded_workspace = loader.trickle(expanded_workspace) t = Server( # create tmux server object socket_name=socket_name, socket_path=socket_path, - config_file=tmux_config_file, + workspace_file=tmux_config_file, colors=colors, ) shutil.which("tmux") # raise exception if tmux not found - try: # load WorkspaceBuilder object for tmuxp config / tmux server + try: # load WorkspaceBuilder object for tmuxp workspace / tmux server builder = WorkspaceBuilder( - sconf=sconfig, plugins=load_plugins(sconfig), server=t + sconf=expanded_workspace, plugins=load_plugins(expanded_workspace), server=t ) - except exc.EmptyConfigException: - tmuxp_echo("%s is empty or parsed no config data" % config_file) + except exc.EmptyWorkspaceException: + tmuxp_echo("%s is empty or parsed no workspace data" % workspace_file) return None - session_name = sconfig["session_name"] + session_name = expanded_workspace["session_name"] # if the session already exists, prompt the user to attach if builder.session_exists(session_name) and not append: @@ -468,15 +468,15 @@ def load_workspace( return _setup_plugins(builder) -def config_file_completion(ctx, params, incomplete): - config_dir = pathlib.Path(get_config_dir()) +def workspace_file_completion(ctx, params, incomplete): + workspace_dir = pathlib.Path(get_workspace_dir()) choices: t.List[pathlib.Path] = [] # CWD Paths choices += sorted( pathlib.Path(os.path.relpath(p, pathlib.Path.cwd())) for p in [pathlib.Path.cwd(), *pathlib.Path.cwd().parents] - if config.in_dir(str(p)) or len(list(p.glob(".tmuxp.*"))) + if in_dir(str(p)) or len(list(p.glob(".tmuxp.*"))) ) # CWD look one directory up choices += [ @@ -484,18 +484,18 @@ def config_file_completion(ctx, params, incomplete): for p in pathlib.Path.cwd().glob("*/.tmuxp.*") ] - # Project configs - choices += sorted((config_dir / c).stem for c in config.in_dir(str(config_dir))) + # Project workspace + choices += sorted((workspace_dir / c).stem for c in in_dir(str(workspace_dir))) return sorted(str(c) for c in choices if str(c).startswith(incomplete)) def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: - config_files = parser.add_argument( - "config_files", + workspace_files = parser.add_argument( + "workspace_files", nargs="+", - metavar="config-file", - help="filepath to session or filename of session if in tmuxp config directory", + metavar="workspace-file", + help="filepath to session or filename of session in tmuxp workspace directory", ) parser.add_argument( "-L", @@ -543,7 +543,7 @@ def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP "--append", dest="append", action="store_true", - help="load configuration, appending windows to the current session", + help="load workspace, appending windows to the current session", ) colorsgroup = parser.add_mutually_exclusive_group() @@ -574,7 +574,7 @@ def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP try: import shtab - config_files.complete = shtab.FILE # type: ignore + workspace_files.complete = shtab.FILE # type: ignore tmux_config_file.complete = shtab.FILE # type: ignore log_file.complete = shtab.FILE # type: ignore except ImportError: @@ -587,25 +587,25 @@ def command_load( args: CLILoadNamespace, parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - """Load a tmux workspace from each CONFIG. + """Load a tmux workspace from each WORKSPACE_FILE. - CONFIG is a specifier for a configuration file. + WORKSPACE_FILE is a specifier for a workspace file. - If CONFIG is a path to a directory, tmuxp will search it for + If WORKSPACE_FILE is a path to a directory, tmuxp will search it for ".tmuxp.{yaml,yml,json}". - If CONFIG is has no directory component and only a filename, e.g. - "myconfig.yaml", tmuxp will search the users's config directory for that + If WORKSPACE_FILE is has no directory component and only a filename, e.g. + "myworkspace.yaml", tmuxp will search the users's workspace directory for that file. - If CONFIG has no directory component, and only a name with no extension, - e.g. "myconfig", tmuxp will search the users's config directory for any + If WORKSPACE_FILE has no directory component, and only a name with no extension, + e.g. "myworkspace", tmuxp will search the users's workspace directory for any file with the extension ".yaml", ".yml", or ".json" that matches that name. - If multiple configuration files that match a given CONFIG are found, tmuxp + If multiple workspace files that match a given WORKSPACE_FILE are found, tmuxp will warn and pick the first one found. - If multiple CONFIGs are provided, workspaces will be created for all of + If multiple WORKSPACE_FILEs are provided, workspaces will be created for all of them. The last one provided will be attached. The others will be created in detached mode. """ @@ -629,19 +629,21 @@ def command_load( "append": args.append, } - if args.config_files is None or len(args.config_files) == 0: + if args.workspace_files is None or len(args.workspace_files) == 0: tmuxp_echo("Enter at least one config") if parser is not None: parser.print_help() sys.exit() return - last_idx = len(args.config_files) - 1 + last_idx = len(args.workspace_files) - 1 original_detached_option = tmux_options.pop("detached") original_new_session_name = tmux_options.pop("new_session_name") - for idx, config_file in enumerate(args.config_files): - config_file = scan_config(config_file, config_dir=get_config_dir()) + for idx, workspace_file in enumerate(args.workspace_files): + workspace_file = find_workspace_file( + workspace_file, workspace_dir=get_workspace_dir() + ) detached = original_detached_option new_session_name = original_new_session_name @@ -651,7 +653,7 @@ def command_load( new_session_name = None load_workspace( - config_file, + workspace_file, detached=detached, new_session_name=new_session_name, **tmux_options, diff --git a/src/tmuxp/cli/ls.py b/src/tmuxp/cli/ls.py index e859652d3fd..e0447edf32e 100644 --- a/src/tmuxp/cli/ls.py +++ b/src/tmuxp/cli/ls.py @@ -2,8 +2,8 @@ import os import typing as t -from .constants import VALID_CONFIG_DIR_FILE_EXTENSIONS -from .utils import get_config_dir +from tmuxp.workspace.constants import VALID_WORKSPACE_DIR_FILE_EXTENSIONS +from tmuxp.workspace.finders import get_workspace_dir def create_ls_subparser( @@ -15,10 +15,10 @@ def create_ls_subparser( def command_ls( parser: t.Optional[argparse.ArgumentParser] = None, ) -> None: - tmuxp_dir = get_config_dir() + tmuxp_dir = get_workspace_dir() if os.path.exists(tmuxp_dir) and os.path.isdir(tmuxp_dir): for f in sorted(os.listdir(tmuxp_dir)): stem, ext = os.path.splitext(f) - if os.path.isdir(f) or ext not in VALID_CONFIG_DIR_FILE_EXTENSIONS: + if os.path.isdir(f) or ext not in VALID_WORKSPACE_DIR_FILE_EXTENSIONS: continue print(stem) diff --git a/src/tmuxp/cli/utils.py b/src/tmuxp/cli/utils.py index dfb6936eae2..9a2d41df9c4 100644 --- a/src/tmuxp/cli/utils.py +++ b/src/tmuxp/cli/utils.py @@ -1,13 +1,8 @@ import logging -import os -import pathlib import re import typing as t -from colorama import Fore - from .. import log -from .constants import VALID_CONFIG_DIR_FILE_EXTENSIONS logger = logging.getLogger(__name__) @@ -31,161 +26,6 @@ def tmuxp_echo( print(message) -def get_config_dir() -> str: - """ - Return tmuxp configuration directory. - - ``TMUXP_CONFIGDIR`` environmental variable has precedence if set. We also - evaluate XDG default directory from XDG_CONFIG_HOME environmental variable - if set or its default. Then the old default ~/.tmuxp is returned for - compatibility. - - Returns - ------- - str : - absolute path to tmuxp config directory - """ - - paths = [] - if "TMUXP_CONFIGDIR" in os.environ: - paths.append(os.environ["TMUXP_CONFIGDIR"]) - if "XDG_CONFIG_HOME" in os.environ: - paths.append(os.path.join(os.environ["XDG_CONFIG_HOME"], "tmuxp")) - else: - paths.append("~/.config/tmuxp/") - paths.append("~/.tmuxp") - - for path in paths: - path = os.path.expanduser(path) - if os.path.isdir(path): - return path - # Return last path as default if none of the previous ones matched - return path - - -def scan_config( - config: t.Union[pathlib.Path, str], - config_dir: t.Optional[t.Union[pathlib.Path, str]] = None, -) -> str: - """ - Return the real config path or raise an exception. - - If config is directory, scan for .tmuxp.{yaml,yml,json} in directory. If - one or more found, it will warn and pick the first. - - If config is ".", "./" or None, it will scan current directory. - - If config is has no path and only a filename, e.g. "myconfig.yaml" it will - search config dir. - - If config has no path and only a name with no extension, e.g. "myconfig", - it will scan for file name with yaml, yml and json. If multiple exist, it - will warn and pick the first. - - Parameters - ---------- - config : str - config file, valid examples: - - - a file name, myconfig.yaml - - relative path, ../config.yaml or ../project - - a period, . - """ - if not config_dir: - config_dir = get_config_dir() - path = os.path - exists, join, isabs = path.exists, path.join, path.isabs - dirname, normpath, splitext = path.dirname, path.normpath, path.splitext - cwd = os.getcwd() - is_name = False - file_error = None - - config = os.path.expanduser(config) - # if purename, resolve to confg dir - if is_pure_name(config): - is_name = True - elif ( - not isabs(config) - or len(dirname(config)) > 1 - or config == "." - or config == "" - or config == "./" - ): # if relative, fill in full path - config = normpath(join(cwd, config)) - - # no extension, scan - if path.isdir(config) or not splitext(config)[1]: - if is_name: - candidates = [ - x - for x in [ - f"{join(config_dir, config)}{ext}" - for ext in VALID_CONFIG_DIR_FILE_EXTENSIONS - ] - if exists(x) - ] - if not len(candidates): - file_error = ( - "config not found in config dir (yaml/yml/json) %s " - "for name" % (config_dir) - ) - else: - candidates = [ - x - for x in [ - join(config, ext) - for ext in [".tmuxp.yaml", ".tmuxp.yml", ".tmuxp.json"] - ] - if exists(x) - ] - - if len(candidates) > 1: - tmuxp_echo( - Fore.RED - + "Multiple .tmuxp.{yml,yaml,json} configs in %s" % dirname(config) - + Fore.RESET - ) - tmuxp_echo( - "This is undefined behavior, use only one. " - "Use file names e.g. myproject.json, coolproject.yaml. " - "You can load them by filename." - ) - elif not len(candidates): - file_error = "No tmuxp files found in directory" - if len(candidates): - config = candidates[0] - elif not exists(config): - file_error = "file not found" - - if file_error: - raise FileNotFoundError(file_error, config) - - return config - - -def is_pure_name(path: str) -> bool: - """ - Return True if path is a name and not a file path. - - Parameters - ---------- - path : str - Path (can be absolute, relative, etc.) - - Returns - ------- - bool - True if path is a name of config in config dir, not file path. - """ - return ( - not os.path.isabs(path) - and len(os.path.dirname(path)) == 0 - and not os.path.splitext(path)[1] - and path != "." - and path != "" - ) - - def prompt( name: str, default: t.Any = None, diff --git a/src/tmuxp/config.py b/src/tmuxp/config.py deleted file mode 100644 index 3efe49b138b..00000000000 --- a/src/tmuxp/config.py +++ /dev/null @@ -1,582 +0,0 @@ -"""Configuration parsing and export for tmuxp. - -tmuxp.config -~~~~~~~~~~~~ - -""" -import logging -import os -from typing import Dict - -from . import exc - -logger = logging.getLogger(__name__) - - -def validate_schema(session_config): - """ - Return True if config schema is correct. - - Parameters - ---------- - session_config : dict - session configuration - - Returns - ------- - bool - """ - - # verify session_name - if "session_name" not in session_config: - raise exc.ConfigError('config requires "session_name"') - - if "windows" not in session_config: - raise exc.ConfigError('config requires list of "windows"') - - for window in session_config["windows"]: - if "window_name" not in window: - raise exc.ConfigError('config window is missing "window_name"') - - if "plugins" in session_config: - if not isinstance(session_config["plugins"], list): - raise exc.ConfigError('"plugins" only supports list type') - - return True - - -def is_config_file(filename, extensions=[".yml", ".yaml", ".json"]): - """ - Return True if file has a valid config file type. - - Parameters - ---------- - filename : str - filename to check (e.g. ``mysession.json``). - extensions : str or list - filetypes to check (e.g. ``['.yaml', '.json']``). - - Returns - ------- - bool - """ - extensions = [extensions] if isinstance(extensions, str) else extensions - return any(filename.endswith(e) for e in extensions) - - -def in_dir( - config_dir=os.path.expanduser("~/.tmuxp"), extensions=[".yml", ".yaml", ".json"] -): - """ - Return a list of configs in ``config_dir``. - - Parameters - ---------- - config_dir : str - directory to search - extensions : list - filetypes to check (e.g. ``['.yaml', '.json']``). - - Returns - ------- - list - """ - configs = [] - - for filename in os.listdir(config_dir): - if is_config_file(filename, extensions) and not filename.startswith("."): - configs.append(filename) - - return configs - - -def in_cwd(): - """ - Return list of configs in current working directory. - - If filename is ``.tmuxp.py``, ``.tmuxp.json``, ``.tmuxp.yaml``. - - Returns - ------- - list - configs in current working directory - - Examples - -------- - >>> sorted(in_cwd()) - ['.tmuxp.json', '.tmuxp.yaml'] - """ - configs = [] - - for filename in os.listdir(os.getcwd()): - if filename.startswith(".tmuxp") and is_config_file(filename): - configs.append(filename) - - return configs - - -def expandshell(_path): - """ - Return expanded path based on user's ``$HOME`` and ``env``. - - :py:func:`os.path.expanduser` and :py:func:`os.path.expandvars`. - - Parameters - ---------- - path : str - path to expand - - Returns - ------- - str - path with shell variables expanded - """ - return os.path.expandvars(os.path.expanduser(_path)) - - -def inline(session_config): - """ - Return config in inline form, opposite of :meth:`config.expand`. - - Parameters - ---------- - session_config : dict - - Returns - ------- - dict - configuration with optional inlined configs. - """ - - if ( - "shell_command" in session_config - and isinstance(session_config["shell_command"], list) - and len(session_config["shell_command"]) == 1 - ): - session_config["shell_command"] = session_config["shell_command"][0] - - if len(session_config.keys()) == int(1): - session_config = session_config["shell_command"] - if ( - "shell_command_before" in session_config - and isinstance(session_config["shell_command_before"], list) - and len(session_config["shell_command_before"]) == 1 - ): - session_config["shell_command_before"] = session_config["shell_command_before"][ - 0 - ] - - # recurse into window and pane config items - if "windows" in session_config: - session_config["windows"] = [ - inline(window) for window in session_config["windows"] - ] - if "panes" in session_config: - session_config["panes"] = [inline(pane) for pane in session_config["panes"]] - - return session_config - - -def expand_cmd(p: Dict) -> Dict: - if isinstance(p, str): - p = {"shell_command": [p]} - elif isinstance(p, list): - p = {"shell_command": p} - elif not p: - p = {"shell_command": []} - - assert isinstance(p, dict) - if "shell_command" in p: - cmds = p["shell_command"] - - if isinstance(p["shell_command"], str): - cmds = [cmds] - - if not cmds or any(a == cmds for a in [None, "blank", "pane"]): - cmds = [] - - if isinstance(cmds, list) and len(cmds) == int(1): - if any(a in cmds for a in [None, "blank", "pane"]): - cmds = [] - - for cmd_idx, cmd in enumerate(cmds): - if isinstance(cmd, str): - cmds[cmd_idx] = {"cmd": cmd} - cmds[cmd_idx]["cmd"] = expandshell(cmds[cmd_idx]["cmd"]) - - p["shell_command"] = cmds - else: - p["shell_command"] = [] - return p - - -def expand(session_config, cwd=None, parent=None): - """Return config with shorthand and inline properties expanded. - - This is necessary to keep the code in the :class:`WorkspaceBuilder` clean - and also allow for neat, short-hand configurations. - - As a simple example, internally, tmuxp expects that config options - like ``shell_command`` are a list (array):: - - 'shell_command': ['htop'] - - tmuxp configs allow for it to be simply a string:: - - 'shell_command': 'htop' - - ConfigReader will load JSON/YAML files into python dicts for you. - - Parameters - ---------- - session_config : dict - the configuration for the session - cwd : str - directory to expand relative paths against. should be the dir of the - config directory. - parent : str - (used on recursive entries) start_directory of parent window or session - object. - - Returns - ------- - dict - """ - - # Note: cli.py will expand configs relative to project's config directory - # for the first cwd argument. - if not cwd: - cwd = os.getcwd() - - if "session_name" in session_config: - session_config["session_name"] = expandshell(session_config["session_name"]) - if "window_name" in session_config: - session_config["window_name"] = expandshell(session_config["window_name"]) - if "environment" in session_config: - for key in session_config["environment"]: - val = session_config["environment"][key] - val = expandshell(val) - if any(val.startswith(a) for a in [".", "./"]): - val = os.path.normpath(os.path.join(cwd, val)) - session_config["environment"][key] = val - if "global_options" in session_config: - for key in session_config["global_options"]: - val = session_config["global_options"][key] - if isinstance(val, str): - val = expandshell(val) - if any(val.startswith(a) for a in [".", "./"]): - val = os.path.normpath(os.path.join(cwd, val)) - session_config["global_options"][key] = val - if "options" in session_config: - for key in session_config["options"]: - val = session_config["options"][key] - if isinstance(val, str): - val = expandshell(val) - if any(val.startswith(a) for a in [".", "./"]): - val = os.path.normpath(os.path.join(cwd, val)) - session_config["options"][key] = val - - # Any config section, session, window, pane that can contain the - # 'shell_command' value - if "start_directory" in session_config: - session_config["start_directory"] = expandshell( - session_config["start_directory"] - ) - start_path = session_config["start_directory"] - if any(start_path.startswith(a) for a in [".", "./"]): - # if window has a session, or pane has a window with a - # start_directory of . or ./, make sure the start_directory can be - # relative to the parent. - # - # This is for the case where you may be loading a config from - # outside your shell current directory. - if parent: - cwd = parent["start_directory"] - start_path = os.path.normpath(os.path.join(cwd, start_path)) - session_config["start_directory"] = start_path - - if "before_script" in session_config: - session_config["before_script"] = expandshell(session_config["before_script"]) - if any(session_config["before_script"].startswith(a) for a in [".", "./"]): - session_config["before_script"] = os.path.normpath( - os.path.join(cwd, session_config["before_script"]) - ) - - if "shell_command" in session_config and isinstance( - session_config["shell_command"], str - ): - session_config["shell_command"] = [session_config["shell_command"]] - - if "shell_command_before" in session_config: - shell_command_before = session_config["shell_command_before"] - - session_config["shell_command_before"] = expand_cmd(shell_command_before) - - # recurse into window and pane config items - if "windows" in session_config: - session_config["windows"] = [ - expand(window, parent=session_config) - for window in session_config["windows"] - ] - elif "panes" in session_config: - pane_configs = session_config["panes"] - for pane_idx, pane_config in enumerate(pane_configs): - pane_configs[pane_idx] = {} - pane_configs[pane_idx].update(expand_cmd(pane_config)) - session_config["panes"] = [ - expand(pane, parent=session_config) for pane in pane_configs - ] - - return session_config - - -def trickle(session_config): - """Return a dict with "trickled down" / inherited config values. - - This will only work if config has been expanded to full form with - :meth:`config.expand`. - - tmuxp allows certain commands to be default at the session, window - level. shell_command_before trickles down and prepends the - ``shell_command`` for the pane. - - Parameters - ---------- - session_config : dict - the session configuration. - - Returns - ------- - dict - """ - - # prepends a pane's ``shell_command`` list with the window and sessions' - # ``shell_command_before``. - - if "start_directory" in session_config: - session_start_directory = session_config["start_directory"] - else: - session_start_directory = None - - if "suppress_history" in session_config: - suppress_history = session_config["suppress_history"] - else: - suppress_history = None - - for window_config in session_config["windows"]: - - # Prepend start_directory to relative window commands - if session_start_directory: - if "start_directory" not in window_config: - window_config["start_directory"] = session_start_directory - else: - if not any( - window_config["start_directory"].startswith(a) for a in ["~", "/"] - ): - window_start_path = os.path.join( - session_start_directory, window_config["start_directory"] - ) - window_config["start_directory"] = window_start_path - - # We only need to trickle to the window, workspace builder checks wconf - if suppress_history is not None: - if "suppress_history" not in window_config: - window_config["suppress_history"] = suppress_history - - # If panes were NOT specified for a window, assume that a single pane - # with no shell commands is desired - if "panes" not in window_config: - window_config["panes"] = [{"shell_command": []}] - - for pane_idx, pane_config in enumerate(window_config["panes"]): - commands_before = [] - - # Prepend shell_command_before to commands - if "shell_command_before" in session_config: - commands_before.extend( - session_config["shell_command_before"]["shell_command"] - ) - if "shell_command_before" in window_config: - commands_before.extend( - window_config["shell_command_before"]["shell_command"] - ) - if "shell_command_before" in pane_config: - commands_before.extend( - pane_config["shell_command_before"]["shell_command"] - ) - - if "shell_command" in pane_config: - commands_before.extend(pane_config["shell_command"]) - - window_config["panes"][pane_idx]["shell_command"] = commands_before - # pane_config['shell_command'] = commands_before - - return session_config - - -def import_tmuxinator(session_config): - """Return tmuxp config from a `tmuxinator`_ yaml config. - - .. _tmuxinator: https://github.com/aziz/tmuxinator - - Parameters - ---------- - session_config : dict - python dict for session configuration. - - Returns - ------- - dict - """ - - tmuxp_config = {} - - if "project_name" in session_config: - tmuxp_config["session_name"] = session_config.pop("project_name") - elif "name" in session_config: - tmuxp_config["session_name"] = session_config.pop("name") - else: - tmuxp_config["session_name"] = None - - if "project_root" in session_config: - tmuxp_config["start_directory"] = session_config.pop("project_root") - elif "root" in session_config: - tmuxp_config["start_directory"] = session_config.pop("root") - - if "cli_args" in session_config: - tmuxp_config["config"] = session_config["cli_args"] - - if "-f" in tmuxp_config["config"]: - tmuxp_config["config"] = tmuxp_config["config"].replace("-f", "").strip() - elif "tmux_options" in session_config: - tmuxp_config["config"] = session_config["tmux_options"] - - if "-f" in tmuxp_config["config"]: - tmuxp_config["config"] = tmuxp_config["config"].replace("-f", "").strip() - - if "socket_name" in session_config: - tmuxp_config["socket_name"] = session_config["socket_name"] - - tmuxp_config["windows"] = [] - - if "tabs" in session_config: - session_config["windows"] = session_config.pop("tabs") - - if "pre" in session_config and "pre_window" in session_config: - tmuxp_config["shell_command"] = session_config["pre"] - - if isinstance(session_config["pre"], str): - tmuxp_config["shell_command_before"] = [session_config["pre_window"]] - else: - tmuxp_config["shell_command_before"] = session_config["pre_window"] - elif "pre" in session_config: - if isinstance(session_config["pre"], str): - tmuxp_config["shell_command_before"] = [session_config["pre"]] - else: - tmuxp_config["shell_command_before"] = session_config["pre"] - - if "rbenv" in session_config: - if "shell_command_before" not in tmuxp_config: - tmuxp_config["shell_command_before"] = [] - tmuxp_config["shell_command_before"].append( - "rbenv shell %s" % session_config["rbenv"] - ) - - for window_config in session_config["windows"]: - for k, v in window_config.items(): - window_config = {"window_name": k} - - if isinstance(v, str) or v is None: - window_config["panes"] = [v] - tmuxp_config["windows"].append(window_config) - continue - elif isinstance(v, list): - window_config["panes"] = v - tmuxp_config["windows"].append(window_config) - continue - - if "pre" in v: - window_config["shell_command_before"] = v["pre"] - if "panes" in v: - window_config["panes"] = v["panes"] - if "root" in v: - window_config["start_directory"] = v["root"] - - if "layout" in v: - window_config["layout"] = v["layout"] - tmuxp_config["windows"].append(window_config) - return tmuxp_config - - -def import_teamocil(session_config): - """Return tmuxp config from a `teamocil`_ yaml config. - - .. _teamocil: https://github.com/remiprev/teamocil - - Parameters - ---------- - session_config : dict - python dict for session configuration - - Notes - ----- - - Todos: - - - change 'root' to a cd or start_directory - - width in pane -> main-pain-width - - with_env_var - - clear - - cmd_separator - """ - - tmuxp_config = {} - - if "session" in session_config: - session_config = session_config["session"] - - if "name" in session_config: - tmuxp_config["session_name"] = session_config["name"] - else: - tmuxp_config["session_name"] = None - - if "root" in session_config: - tmuxp_config["start_directory"] = session_config.pop("root") - - tmuxp_config["windows"] = [] - - for w in session_config["windows"]: - - windowdict = {"window_name": w["name"]} - - if "clear" in w: - windowdict["clear"] = w["clear"] - - if "filters" in w: - if "before" in w["filters"]: - for b in w["filters"]["before"]: - windowdict["shell_command_before"] = w["filters"]["before"] - if "after" in w["filters"]: - for b in w["filters"]["after"]: - windowdict["shell_command_after"] = w["filters"]["after"] - - if "root" in w: - windowdict["start_directory"] = w.pop("root") - - if "splits" in w: - w["panes"] = w.pop("splits") - - if "panes" in w: - for p in w["panes"]: - if "cmd" in p: - p["shell_command"] = p.pop("cmd") - if "width" in p: - # todo support for height/width - p.pop("width") - windowdict["panes"] = w["panes"] - - if "layout" in w: - windowdict["layout"] = w["layout"] - tmuxp_config["windows"].append(windowdict) - - return tmuxp_config diff --git a/src/tmuxp/exc.py b/src/tmuxp/exc.py index a8d78847a27..2f3128a3e7c 100644 --- a/src/tmuxp/exc.py +++ b/src/tmuxp/exc.py @@ -12,12 +12,12 @@ class TmuxpException(Exception): """Base Exception for Tmuxp Errors.""" -class ConfigError(TmuxpException): +class WorkspaceError(TmuxpException): """Error parsing tmuxp configuration dict.""" -class EmptyConfigException(ConfigError): +class EmptyWorkspaceException(WorkspaceError): """Configuration is empty.""" diff --git a/src/tmuxp/workspace/__init__.py b/src/tmuxp/workspace/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/tmuxp/workspacebuilder.py b/src/tmuxp/workspace/builder.py similarity index 87% rename from src/tmuxp/workspacebuilder.py rename to src/tmuxp/workspace/builder.py index cdfc4932567..67d36c60cc5 100644 --- a/src/tmuxp/workspacebuilder.py +++ b/src/tmuxp/workspace/builder.py @@ -1,7 +1,7 @@ -"""Create a tmux workspace from a configuration :py:obj:`dict`. +"""Create a tmux workspace from a workspace configuration :py:obj:`dict`. -tmuxp.workspacebuilder -~~~~~~~~~~~~~~~~~~~~~~ +tmuxp.workspace.builder +~~~~~~~~~~~~~~~~~~~~~~~ """ import logging @@ -13,8 +13,8 @@ from libtmux.session import Session from libtmux.window import Window -from . import exc -from .util import get_current_pane, run_before_script +from .. import exc +from ..util import get_current_pane, run_before_script logger = logging.getLogger(__name__) @@ -37,7 +37,7 @@ class WorkspaceBuilder: >>> import yaml >>> session_config = yaml.load(''' - ... session_name: sampleconfig + ... session_name: sample workspace ... start_directory: '~' ... windows: ... - window_name: editor @@ -67,7 +67,7 @@ class WorkspaceBuilder: >>> new_session = builder.session - >>> new_session.name == 'sampleconfig' + >>> new_session.name == 'sample workspace' True >>> len(new_session._windows) @@ -85,7 +85,7 @@ class WorkspaceBuilder: _Caveat:_ Preserves old session name: - >>> session.name == 'sampleconfig' + >>> session.name == 'sample workspace' False >>> len(session._windows) @@ -157,7 +157,7 @@ def __init__(self, sconf, plugins=[], server=None): if not sconf: raise exc.EmptyConfigException("session configuration is empty.") - # config.validate_schema(sconf) + # validation.validate_schema(sconf) if isinstance(server, Server): self.server = server @@ -494,69 +494,3 @@ def find_current_attached_session(self): def first_window_pass(self, i, session, append): return len(session.windows) == 1 and i == 1 and not append - - -def freeze(session): - """ - Freeze live tmux session and Return session config :py:obj:`dict`. - - Parameters - ---------- - session : :class:`libtmux.Session` - session object - - Returns - ------- - dict - tmuxp compatible workspace config - """ - sconf = {"session_name": session["session_name"], "windows": []} - - for w in session.windows: - wconf = { - "options": w.show_window_options(), - "window_name": w.name, - "layout": w.layout, - "panes": [], - } - if w.get("window_active", "0") == "1": - wconf["focus"] = "true" - - # If all panes have same path, set 'start_directory' instead - # of using 'cd' shell commands. - def pane_has_same_path(p): - return w.panes[0].current_path == p.current_path - - if all(pane_has_same_path(p) for p in w.panes): - wconf["start_directory"] = w.panes[0].current_path - - for p in w.panes: - pconf = {"shell_command": []} - - if "start_directory" not in wconf: - pconf["shell_command"].append("cd " + p.current_path) - - if p.get("pane_active", "0") == "1": - pconf["focus"] = "true" - - current_cmd = p.current_command - - def filter_interpretters_and_shells(): - return current_cmd.startswith("-") or any( - current_cmd.endswith(cmd) for cmd in ["python", "ruby", "node"] - ) - - if filter_interpretters_and_shells(): - current_cmd = None - - if current_cmd: - pconf["shell_command"].append(current_cmd) - else: - if not len(pconf["shell_command"]): - pconf = "pane" - - wconf["panes"].append(pconf) - - sconf["windows"].append(wconf) - - return sconf diff --git a/src/tmuxp/workspace/constants.py b/src/tmuxp/workspace/constants.py new file mode 100644 index 00000000000..50a463af895 --- /dev/null +++ b/src/tmuxp/workspace/constants.py @@ -0,0 +1 @@ +VALID_WORKSPACE_DIR_FILE_EXTENSIONS = [".yaml", ".yml", ".json"] diff --git a/src/tmuxp/workspace/finders.py b/src/tmuxp/workspace/finders.py new file mode 100644 index 00000000000..eaed100d946 --- /dev/null +++ b/src/tmuxp/workspace/finders.py @@ -0,0 +1,237 @@ +import logging +import os +import typing as t + +from colorama import Fore + +from tmuxp.cli.utils import tmuxp_echo +from tmuxp.types import StrPath +from tmuxp.workspace.constants import VALID_WORKSPACE_DIR_FILE_EXTENSIONS + +logger = logging.getLogger(__name__) + + +def is_workspace_file(filename, extensions=[".yml", ".yaml", ".json"]): + """ + Return True if file has a valid workspace file type. + + Parameters + ---------- + filename : str + filename to check (e.g. ``mysession.json``). + extensions : str or list + filetypes to check (e.g. ``['.yaml', '.json']``). + + Returns + ------- + bool + """ + extensions = [extensions] if isinstance(extensions, str) else extensions + return any(filename.endswith(e) for e in extensions) + + +def in_dir( + config_dir=os.path.expanduser("~/.tmuxp"), extensions=[".yml", ".yaml", ".json"] +): + """ + Return a list of configs in ``config_dir``. + + Parameters + ---------- + config_dir : str + directory to search + extensions : list + filetypes to check (e.g. ``['.yaml', '.json']``). + + Returns + ------- + list + """ + configs = [] + + for filename in os.listdir(config_dir): + if is_workspace_file(filename, extensions) and not filename.startswith("."): + configs.append(filename) + + return configs + + +def in_cwd(): + """ + Return list of configs in current working directory. + + If filename is ``.tmuxp.py``, ``.tmuxp.json``, ``.tmuxp.yaml``. + + Returns + ------- + list + configs in current working directory + + Examples + -------- + >>> sorted(in_cwd()) + ['.tmuxp.json', '.tmuxp.yaml'] + """ + configs = [] + + for filename in os.listdir(os.getcwd()): + if filename.startswith(".tmuxp") and is_workspace_file(filename): + configs.append(filename) + + return configs + + +def get_workspace_dir() -> str: + """ + Return tmuxp configuration directory. + + ``TMUXP_CONFIGDIR`` environmental variable has precedence if set. We also + evaluate XDG default directory from XDG_CONFIG_HOME environmental variable + if set or its default. Then the old default ~/.tmuxp is returned for + compatibility. + + Returns + ------- + str : + absolute path to tmuxp config directory + """ + + paths = [] + if "TMUXP_CONFIGDIR" in os.environ: + paths.append(os.environ["TMUXP_CONFIGDIR"]) + if "XDG_CONFIG_HOME" in os.environ: + paths.append(os.path.join(os.environ["XDG_CONFIG_HOME"], "tmuxp")) + else: + paths.append("~/.config/tmuxp/") + paths.append("~/.tmuxp") + + for path in paths: + path = os.path.expanduser(path) + if os.path.isdir(path): + return path + # Return last path as default if none of the previous ones matched + return path + + +def find_workspace_file( + workspace_file: StrPath, + workspace_dir: t.Optional[StrPath] = None, +) -> str: + """ + Return the real config path or raise an exception. + + If config is directory, scan for .tmuxp.{yaml,yml,json} in directory. If + one or more found, it will warn and pick the first. + + If config is ".", "./" or None, it will scan current directory. + + If config is has no path and only a filename, e.g. "myconfig.yaml" it will + search config dir. + + If config has no path and only a name with no extension, e.g. "myconfig", + it will scan for file name with yaml, yml and json. If multiple exist, it + will warn and pick the first. + + Parameters + ---------- + workspace_file : str + workspace file, valid examples: + + - a file name, myconfig.yaml + - relative path, ../config.yaml or ../project + - a period, . + """ + if not workspace_dir: + workspace_dir = get_workspace_dir() + path = os.path + exists, join, isabs = path.exists, path.join, path.isabs + dirname, normpath, splitext = path.dirname, path.normpath, path.splitext + cwd = os.getcwd() + is_name = False + file_error = None + + workspace_file = os.path.expanduser(workspace_file) + # if purename, resolve to confg dir + if is_pure_name(workspace_file): + is_name = True + elif ( + not isabs(workspace_file) + or len(dirname(workspace_file)) > 1 + or workspace_file == "." + or workspace_file == "" + or workspace_file == "./" + ): # if relative, fill in full path + workspace_file = normpath(join(cwd, workspace_file)) + + # no extension, scan + if path.isdir(workspace_file) or not splitext(workspace_file)[1]: + if is_name: + candidates = [ + x + for x in [ + f"{join(workspace_dir, workspace_file)}{ext}" + for ext in VALID_WORKSPACE_DIR_FILE_EXTENSIONS + ] + if exists(x) + ] + if not len(candidates): + file_error = ( + "workspace-file not found in workspace dir (yaml/yml/json) %s " + "for name" % (workspace_dir) + ) + else: + candidates = [ + x + for x in [ + join(workspace_file, ext) + for ext in [".tmuxp.yaml", ".tmuxp.yml", ".tmuxp.json"] + ] + if exists(x) + ] + + if len(candidates) > 1: + tmuxp_echo( + Fore.RED + + "Multiple .tmuxp.{yml,yaml,json} workspace_files in %s" + % dirname(workspace_file) + + Fore.RESET + ) + tmuxp_echo( + "This is undefined behavior, use only one. " + "Use file names e.g. myproject.json, coolproject.yaml. " + "You can load them by filename." + ) + elif not len(candidates): + file_error = "No tmuxp files found in directory" + if len(candidates): + workspace_file = candidates[0] + elif not exists(workspace_file): + file_error = "file not found" + + if file_error: + raise FileNotFoundError(file_error, workspace_file) + + return workspace_file + + +def is_pure_name(path: str) -> bool: + """ + Return True if path is a name and not a file path. + + Parameters + ---------- + path : str + Path (can be absolute, relative, etc.) + + Returns + ------- + bool + True if path is a name of config in config dir, not file path. + """ + return ( + not os.path.isabs(path) + and len(os.path.dirname(path)) == 0 + and not os.path.splitext(path)[1] + and path != "." + and path != "" + ) diff --git a/src/tmuxp/workspace/freezer.py b/src/tmuxp/workspace/freezer.py new file mode 100644 index 00000000000..542a1998cdb --- /dev/null +++ b/src/tmuxp/workspace/freezer.py @@ -0,0 +1,107 @@ +def inline(workspace_dict): + """ + Return config in inline form, opposite of :meth:`config.expand`. + + Parameters + ---------- + workspace_dict : dict + + Returns + ------- + dict + configuration with optional inlined configs. + """ + + if ( + "shell_command" in workspace_dict + and isinstance(workspace_dict["shell_command"], list) + and len(workspace_dict["shell_command"]) == 1 + ): + workspace_dict["shell_command"] = workspace_dict["shell_command"][0] + + if len(workspace_dict.keys()) == int(1): + workspace_dict = workspace_dict["shell_command"] + if ( + "shell_command_before" in workspace_dict + and isinstance(workspace_dict["shell_command_before"], list) + and len(workspace_dict["shell_command_before"]) == 1 + ): + workspace_dict["shell_command_before"] = workspace_dict["shell_command_before"][ + 0 + ] + + # recurse into window and pane config items + if "windows" in workspace_dict: + workspace_dict["windows"] = [ + inline(window) for window in workspace_dict["windows"] + ] + if "panes" in workspace_dict: + workspace_dict["panes"] = [inline(pane) for pane in workspace_dict["panes"]] + + return workspace_dict + + +def freeze(session): + """ + Freeze live tmux session and Return session config :py:obj:`dict`. + + Parameters + ---------- + session : :class:`libtmux.Session` + session object + + Returns + ------- + dict + tmuxp compatible workspace config + """ + sconf = {"session_name": session["session_name"], "windows": []} + + for w in session.windows: + wconf = { + "options": w.show_window_options(), + "window_name": w.name, + "layout": w.layout, + "panes": [], + } + if w.get("window_active", "0") == "1": + wconf["focus"] = "true" + + # If all panes have same path, set 'start_directory' instead + # of using 'cd' shell commands. + def pane_has_same_path(p): + return w.panes[0].current_path == p.current_path + + if all(pane_has_same_path(p) for p in w.panes): + wconf["start_directory"] = w.panes[0].current_path + + for p in w.panes: + pconf = {"shell_command": []} + + if "start_directory" not in wconf: + pconf["shell_command"].append("cd " + p.current_path) + + if p.get("pane_active", "0") == "1": + pconf["focus"] = "true" + + current_cmd = p.current_command + + def filter_interpretters_and_shells(): + return current_cmd.startswith("-") or any( + current_cmd.endswith(cmd) for cmd in ["python", "ruby", "node"] + ) + + if filter_interpretters_and_shells(): + current_cmd = None + + if current_cmd: + pconf["shell_command"].append(current_cmd) + else: + if not len(pconf["shell_command"]): + pconf = "pane" + + wconf["panes"].append(pconf) + + sconf["windows"].append(wconf) + + return sconf diff --git a/src/tmuxp/workspace/importers.py b/src/tmuxp/workspace/importers.py new file mode 100644 index 00000000000..ff05e497a22 --- /dev/null +++ b/src/tmuxp/workspace/importers.py @@ -0,0 +1,169 @@ +def import_tmuxinator(workspace_dict): + """Return tmuxp workspace from a `tmuxinator`_ yaml workspace. + + .. _tmuxinator: https://github.com/aziz/tmuxinator + + Parameters + ---------- + workspace_dict : dict + python dict for tmuxp workspace. + + Returns + ------- + dict + """ + + tmuxp_workspace = {} + + if "project_name" in workspace_dict: + tmuxp_workspace["session_name"] = workspace_dict.pop("project_name") + elif "name" in workspace_dict: + tmuxp_workspace["session_name"] = workspace_dict.pop("name") + else: + tmuxp_workspace["session_name"] = None + + if "project_root" in workspace_dict: + tmuxp_workspace["start_directory"] = workspace_dict.pop("project_root") + elif "root" in workspace_dict: + tmuxp_workspace["start_directory"] = workspace_dict.pop("root") + + if "cli_args" in workspace_dict: + tmuxp_workspace["config"] = workspace_dict["cli_args"] + + if "-f" in tmuxp_workspace["config"]: + tmuxp_workspace["config"] = ( + tmuxp_workspace["config"].replace("-f", "").strip() + ) + elif "tmux_options" in workspace_dict: + tmuxp_workspace["config"] = workspace_dict["tmux_options"] + + if "-f" in tmuxp_workspace["config"]: + tmuxp_workspace["config"] = ( + tmuxp_workspace["config"].replace("-f", "").strip() + ) + + 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") + + if "pre" in workspace_dict and "pre_window" in workspace_dict: + tmuxp_workspace["shell_command"] = workspace_dict["pre"] + + if isinstance(workspace_dict["pre"], str): + tmuxp_workspace["shell_command_before"] = [workspace_dict["pre_window"]] + else: + tmuxp_workspace["shell_command_before"] = workspace_dict["pre_window"] + elif "pre" in workspace_dict: + if isinstance(workspace_dict["pre"], str): + tmuxp_workspace["shell_command_before"] = [workspace_dict["pre"]] + else: + tmuxp_workspace["shell_command_before"] = workspace_dict["pre"] + + if "rbenv" in workspace_dict: + if "shell_command_before" not in tmuxp_workspace: + tmuxp_workspace["shell_command_before"] = [] + tmuxp_workspace["shell_command_before"].append( + "rbenv shell %s" % workspace_dict["rbenv"] + ) + + for window_dict in workspace_dict["windows"]: + for k, v in window_dict.items(): + window_dict = {"window_name": k} + + if isinstance(v, str) or v is None: + window_dict["panes"] = [v] + tmuxp_workspace["windows"].append(window_dict) + continue + elif isinstance(v, list): + window_dict["panes"] = v + tmuxp_workspace["windows"].append(window_dict) + continue + + if "pre" in v: + window_dict["shell_command_before"] = v["pre"] + if "panes" in v: + window_dict["panes"] = v["panes"] + if "root" in v: + window_dict["start_directory"] = v["root"] + + if "layout" in v: + window_dict["layout"] = v["layout"] + tmuxp_workspace["windows"].append(window_dict) + return tmuxp_workspace + + +def import_teamocil(workspace_dict): + """Return tmuxp workspace from a `teamocil`_ yaml workspace. + + .. _teamocil: https://github.com/remiprev/teamocil + + Parameters + ---------- + workspace_dict : dict + python dict for tmuxp workspace + + Notes + ----- + + Todos: + + - change 'root' to a cd or start_directory + - width in pane -> main-pain-width + - with_env_var + - clear + - cmd_separator + """ + + tmuxp_workspace = {} + + if "session" in workspace_dict: + workspace_dict = workspace_dict["session"] + + if "name" in workspace_dict: + tmuxp_workspace["session_name"] = workspace_dict["name"] + else: + tmuxp_workspace["session_name"] = None + + 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"]} + + 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"] + if "after" in w["filters"]: + for b in w["filters"]["after"]: + window_dict["shell_command_after"] = w["filters"]["after"] + + if "root" in w: + window_dict["start_directory"] = w.pop("root") + + if "splits" in w: + w["panes"] = w.pop("splits") + + if "panes" in w: + for p in w["panes"]: + if "cmd" in p: + p["shell_command"] = p.pop("cmd") + if "width" in p: + # todo support for height/width + p.pop("width") + window_dict["panes"] = w["panes"] + + if "layout" in w: + window_dict["layout"] = w["layout"] + tmuxp_workspace["windows"].append(window_dict) + + return tmuxp_workspace diff --git a/src/tmuxp/workspace/loader.py b/src/tmuxp/workspace/loader.py new file mode 100644 index 00000000000..016e97e02ec --- /dev/null +++ b/src/tmuxp/workspace/loader.py @@ -0,0 +1,267 @@ +"""Workspace parsing for tmuxp. + +tmuxp.config +~~~~~~~~~~~~ + +""" +import logging +import os +from typing import Dict + +logger = logging.getLogger(__name__) + + +def expandshell(_path): + """ + Return expanded path based on user's ``$HOME`` and ``env``. + + :py:func:`os.path.expanduser` and :py:func:`os.path.expandvars`. + + Parameters + ---------- + path : str + path to expand + + Returns + ------- + str + path with shell variables expanded + """ + return os.path.expandvars(os.path.expanduser(_path)) + + +def expand_cmd(p: Dict) -> Dict: + if isinstance(p, str): + p = {"shell_command": [p]} + elif isinstance(p, list): + p = {"shell_command": p} + elif not p: + p = {"shell_command": []} + + assert isinstance(p, dict) + if "shell_command" in p: + cmds = p["shell_command"] + + if isinstance(p["shell_command"], str): + cmds = [cmds] + + if not cmds or any(a == cmds for a in [None, "blank", "pane"]): + cmds = [] + + if isinstance(cmds, list) and len(cmds) == int(1): + if any(a in cmds for a in [None, "blank", "pane"]): + cmds = [] + + for cmd_idx, cmd in enumerate(cmds): + if isinstance(cmd, str): + cmds[cmd_idx] = {"cmd": cmd} + cmds[cmd_idx]["cmd"] = expandshell(cmds[cmd_idx]["cmd"]) + + p["shell_command"] = cmds + else: + p["shell_command"] = [] + return p + + +def expand(workspace_dict, cwd=None, parent=None): + """Return workspace with shorthand and inline properties expanded. + + This is necessary to keep the code in the :class:`WorkspaceBuilder` clean + and also allow for neat, short-hand "sugarified" syntax. + + As a simple example, internally, tmuxp expects that workspace options + like ``shell_command`` are a list (array):: + + 'shell_command': ['htop'] + + tmuxp workspace allow for it to be simply a string:: + + 'shell_command': 'htop' + + ConfigReader will load JSON/YAML files into python dicts for you. + + Parameters + ---------- + workspace_dict : dict + the tmuxp workspace for the session + cwd : str + directory to expand relative paths against. should be the dir of the + workspace directory. + parent : str + (used on recursive entries) start_directory of parent window or session + object. + + Returns + ------- + dict + """ + + # Note: cli.py will expand workspaces relative to project's workspace directory + # for the first cwd argument. + if not cwd: + cwd = os.getcwd() + + if "session_name" in workspace_dict: + workspace_dict["session_name"] = expandshell(workspace_dict["session_name"]) + if "window_name" in workspace_dict: + workspace_dict["window_name"] = expandshell(workspace_dict["window_name"]) + if "environment" in workspace_dict: + for key in workspace_dict["environment"]: + val = workspace_dict["environment"][key] + val = expandshell(val) + if any(val.startswith(a) for a in [".", "./"]): + val = os.path.normpath(os.path.join(cwd, val)) + workspace_dict["environment"][key] = val + if "global_options" in workspace_dict: + for key in workspace_dict["global_options"]: + val = workspace_dict["global_options"][key] + if isinstance(val, str): + val = expandshell(val) + if any(val.startswith(a) for a in [".", "./"]): + val = os.path.normpath(os.path.join(cwd, val)) + workspace_dict["global_options"][key] = val + if "options" in workspace_dict: + for key in workspace_dict["options"]: + val = workspace_dict["options"][key] + if isinstance(val, str): + val = expandshell(val) + if any(val.startswith(a) for a in [".", "./"]): + val = os.path.normpath(os.path.join(cwd, val)) + workspace_dict["options"][key] = val + + # Any workspace section, session, window, pane that can contain the + # 'shell_command' value + if "start_directory" in workspace_dict: + workspace_dict["start_directory"] = expandshell( + workspace_dict["start_directory"] + ) + start_path = workspace_dict["start_directory"] + if any(start_path.startswith(a) for a in [".", "./"]): + # if window has a session, or pane has a window with a + # start_directory of . or ./, make sure the start_directory can be + # relative to the parent. + # + # This is for the case where you may be loading a workspace from + # outside your shell current directory. + if parent: + cwd = parent["start_directory"] + start_path = os.path.normpath(os.path.join(cwd, start_path)) + workspace_dict["start_directory"] = start_path + + if "before_script" in workspace_dict: + workspace_dict["before_script"] = expandshell(workspace_dict["before_script"]) + if any(workspace_dict["before_script"].startswith(a) for a in [".", "./"]): + workspace_dict["before_script"] = os.path.normpath( + os.path.join(cwd, workspace_dict["before_script"]) + ) + + if "shell_command" in workspace_dict and isinstance( + workspace_dict["shell_command"], str + ): + workspace_dict["shell_command"] = [workspace_dict["shell_command"]] + + if "shell_command_before" in workspace_dict: + shell_command_before = workspace_dict["shell_command_before"] + + workspace_dict["shell_command_before"] = expand_cmd(shell_command_before) + + # recurse into window and pane workspace items + if "windows" in workspace_dict: + workspace_dict["windows"] = [ + expand(window, parent=workspace_dict) + for window in workspace_dict["windows"] + ] + elif "panes" in workspace_dict: + pane_dicts = workspace_dict["panes"] + for pane_idx, pane_dict in enumerate(pane_dicts): + pane_dicts[pane_idx] = {} + pane_dicts[pane_idx].update(expand_cmd(pane_dict)) + workspace_dict["panes"] = [ + expand(pane, parent=workspace_dict) for pane in pane_dicts + ] + + return workspace_dict + + +def trickle(workspace_dict): + """Return a dict with "trickled down" / inherited workspace values. + + This will only work if workspace has been expanded to full form with + :meth:`config.expand`. + + tmuxp allows certain commands to be default at the session, window + level. shell_command_before trickles down and prepends the + ``shell_command`` for the pane. + + Parameters + ---------- + workspace_dict : dict + the tmuxp workspace. + + Returns + ------- + dict + """ + + # prepends a pane's ``shell_command`` list with the window and sessions' + # ``shell_command_before``. + + if "start_directory" in workspace_dict: + session_start_directory = workspace_dict["start_directory"] + else: + session_start_directory = None + + if "suppress_history" in workspace_dict: + suppress_history = workspace_dict["suppress_history"] + else: + suppress_history = None + + for window_dict in workspace_dict["windows"]: + + # Prepend start_directory to relative window commands + if session_start_directory: + if "start_directory" not in window_dict: + window_dict["start_directory"] = session_start_directory + else: + if not any( + window_dict["start_directory"].startswith(a) for a in ["~", "/"] + ): + window_start_path = os.path.join( + session_start_directory, window_dict["start_directory"] + ) + window_dict["start_directory"] = window_start_path + + # We only need to trickle to the window, workspace builder checks wconf + if suppress_history is not None: + if "suppress_history" not in window_dict: + window_dict["suppress_history"] = suppress_history + + # If panes were NOT specified for a window, assume that a single pane + # with no shell commands is desired + if "panes" not in window_dict: + window_dict["panes"] = [{"shell_command": []}] + + for pane_idx, pane_dict in enumerate(window_dict["panes"]): + commands_before = [] + + # Prepend shell_command_before to commands + if "shell_command_before" in workspace_dict: + commands_before.extend( + workspace_dict["shell_command_before"]["shell_command"] + ) + if "shell_command_before" in window_dict: + commands_before.extend( + window_dict["shell_command_before"]["shell_command"] + ) + if "shell_command_before" in pane_dict: + commands_before.extend( + pane_dict["shell_command_before"]["shell_command"] + ) + + if "shell_command" in pane_dict: + commands_before.extend(pane_dict["shell_command"]) + + window_dict["panes"][pane_idx]["shell_command"] = commands_before + # pane_dict['shell_command'] = commands_before + + return workspace_dict diff --git a/src/tmuxp/workspace/validation.py b/src/tmuxp/workspace/validation.py new file mode 100644 index 00000000000..f1d99ec60f6 --- /dev/null +++ b/src/tmuxp/workspace/validation.py @@ -0,0 +1,32 @@ +from .. import exc + + +def validate_schema(workspace_dict): + """ + Return True if workspace schema is correct. + + Parameters + ---------- + workspace_dict : dict + tmuxp workspace data + + Returns + ------- + bool + """ + # verify session_name + if "session_name" not in workspace_dict: + raise exc.WorkspaceError('workspace requires "session_name"') + + if "windows" not in workspace_dict: + raise exc.WorkspaceError('workspace requires list of "windows"') + + for window in workspace_dict["windows"]: + if "window_name" not in window: + raise exc.WorkspaceError('workspace window is missing "window_name"') + + if "plugins" in workspace_dict: + if not isinstance(workspace_dict["plugins"], list): + raise exc.WorkspaceError('"plugins" only supports list type') + + return True diff --git a/tests/fixtures/config/expand2.py b/tests/fixtures/config/expand2.py deleted file mode 100644 index 7e73132e5fb..00000000000 --- a/tests/fixtures/config/expand2.py +++ /dev/null @@ -1,13 +0,0 @@ -import os - -from .. import utils as test_utils - - -def unexpanded_yaml(): - return test_utils.read_config_file("config/expand2-unexpanded.yaml") - - -def expanded_yaml(): - return test_utils.read_config_file("config/expand2-expanded.yaml").format( - HOME=os.path.expanduser("~") - ) diff --git a/tests/fixtures/config/shell_command_before_session.py b/tests/fixtures/config/shell_command_before_session.py deleted file mode 100644 index c4f260f7a01..00000000000 --- a/tests/fixtures/config/shell_command_before_session.py +++ /dev/null @@ -1,6 +0,0 @@ -from .. import utils as test_utils - -before = test_utils.read_config_file("config/shell_command_before_session.yaml") -expected = test_utils.read_config_file( - "config/shell_command_before_session-expected.yaml" -) diff --git a/tests/fixtures/config_teamocil/__init__.py b/tests/fixtures/import_teamocil/__init__.py similarity index 100% rename from tests/fixtures/config_teamocil/__init__.py rename to tests/fixtures/import_teamocil/__init__.py diff --git a/tests/fixtures/config_teamocil/layouts.py b/tests/fixtures/import_teamocil/layouts.py similarity index 96% rename from tests/fixtures/config_teamocil/layouts.py rename to tests/fixtures/import_teamocil/layouts.py index c6a2f2fd413..14d509c9697 100644 --- a/tests/fixtures/config_teamocil/layouts.py +++ b/tests/fixtures/import_teamocil/layouts.py @@ -1,7 +1,7 @@ from .. import utils as test_utils -teamocil_yaml_file = test_utils.get_config_file("config_teamocil/layouts.yaml") -teamocil_yaml = test_utils.read_config_file("config_teamocil/layouts.yaml") +teamocil_yaml_file = test_utils.get_workspace_file("import_teamocil/layouts.yaml") +teamocil_yaml = test_utils.read_workspace_file("import_teamocil/layouts.yaml") teamocil_dict = { "two-windows": { diff --git a/tests/fixtures/config_teamocil/layouts.yaml b/tests/fixtures/import_teamocil/layouts.yaml similarity index 100% rename from tests/fixtures/config_teamocil/layouts.yaml rename to tests/fixtures/import_teamocil/layouts.yaml diff --git a/tests/fixtures/config_teamocil/test1.py b/tests/fixtures/import_teamocil/test1.py similarity index 89% rename from tests/fixtures/config_teamocil/test1.py rename to tests/fixtures/import_teamocil/test1.py index ee329c06616..d55a92d4a34 100644 --- a/tests/fixtures/config_teamocil/test1.py +++ b/tests/fixtures/import_teamocil/test1.py @@ -1,6 +1,6 @@ from .. import utils as test_utils -teamocil_yaml = test_utils.read_config_file("config_teamocil/test1.yaml") +teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test1.yaml") teamocil_conf = { "windows": [ { diff --git a/tests/fixtures/config_teamocil/test1.yaml b/tests/fixtures/import_teamocil/test1.yaml similarity index 100% rename from tests/fixtures/config_teamocil/test1.yaml rename to tests/fixtures/import_teamocil/test1.yaml diff --git a/tests/fixtures/config_teamocil/test2.py b/tests/fixtures/import_teamocil/test2.py similarity index 90% rename from tests/fixtures/config_teamocil/test2.py rename to tests/fixtures/import_teamocil/test2.py index dd68433b05a..3478b22c77b 100644 --- a/tests/fixtures/config_teamocil/test2.py +++ b/tests/fixtures/import_teamocil/test2.py @@ -1,6 +1,6 @@ from .. import utils as test_utils -teamocil_yaml = test_utils.read_config_file("config_teamocil/test2.yaml") +teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test2.yaml") teamocil_dict = { "windows": [ { diff --git a/tests/fixtures/config_teamocil/test2.yaml b/tests/fixtures/import_teamocil/test2.yaml similarity index 100% rename from tests/fixtures/config_teamocil/test2.yaml rename to tests/fixtures/import_teamocil/test2.yaml diff --git a/tests/fixtures/config_teamocil/test3.py b/tests/fixtures/import_teamocil/test3.py similarity index 94% rename from tests/fixtures/config_teamocil/test3.py rename to tests/fixtures/import_teamocil/test3.py index 436e9e4a737..4f18f11d049 100644 --- a/tests/fixtures/config_teamocil/test3.py +++ b/tests/fixtures/import_teamocil/test3.py @@ -1,6 +1,6 @@ from .. import utils as test_utils -teamocil_yaml = test_utils.read_config_file("config_teamocil/test3.yaml") +teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test3.yaml") teamocil_dict = { "windows": [ diff --git a/tests/fixtures/config_teamocil/test3.yaml b/tests/fixtures/import_teamocil/test3.yaml similarity index 100% rename from tests/fixtures/config_teamocil/test3.yaml rename to tests/fixtures/import_teamocil/test3.yaml diff --git a/tests/fixtures/config_teamocil/test4.py b/tests/fixtures/import_teamocil/test4.py similarity index 85% rename from tests/fixtures/config_teamocil/test4.py rename to tests/fixtures/import_teamocil/test4.py index 293fb958fb3..62c0ead874f 100644 --- a/tests/fixtures/config_teamocil/test4.py +++ b/tests/fixtures/import_teamocil/test4.py @@ -1,6 +1,6 @@ from .. import utils as test_utils -teamocil_yaml = test_utils.read_config_file("config_teamocil/test4.yaml") +teamocil_yaml = test_utils.read_workspace_file("import_teamocil/test4.yaml") teamocil_dict = { "windows": [ diff --git a/tests/fixtures/config_teamocil/test4.yaml b/tests/fixtures/import_teamocil/test4.yaml similarity index 100% rename from tests/fixtures/config_teamocil/test4.yaml rename to tests/fixtures/import_teamocil/test4.yaml diff --git a/tests/fixtures/config_tmuxinator/__init__.py b/tests/fixtures/import_tmuxinator/__init__.py similarity index 100% rename from tests/fixtures/config_tmuxinator/__init__.py rename to tests/fixtures/import_tmuxinator/__init__.py diff --git a/tests/fixtures/config_tmuxinator/test1.py b/tests/fixtures/import_tmuxinator/test1.py similarity index 87% rename from tests/fixtures/config_tmuxinator/test1.py rename to tests/fixtures/import_tmuxinator/test1.py index b1499717433..d7139f68a0d 100644 --- a/tests/fixtures/config_tmuxinator/test1.py +++ b/tests/fixtures/import_tmuxinator/test1.py @@ -1,6 +1,6 @@ from .. import utils as test_utils -tmuxinator_yaml = test_utils.read_config_file("config_tmuxinator/test1.yaml") +tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test1.yaml") tmuxinator_dict = { "windows": [ {"editor": {"layout": "main-vertical", "panes": ["vim", "guard"]}}, diff --git a/tests/fixtures/config_tmuxinator/test1.yaml b/tests/fixtures/import_tmuxinator/test1.yaml similarity index 100% rename from tests/fixtures/config_tmuxinator/test1.yaml rename to tests/fixtures/import_tmuxinator/test1.yaml diff --git a/tests/fixtures/config_tmuxinator/test2.py b/tests/fixtures/import_tmuxinator/test2.py similarity index 96% rename from tests/fixtures/config_tmuxinator/test2.py rename to tests/fixtures/import_tmuxinator/test2.py index f90caa8cd92..008b4e2f31d 100644 --- a/tests/fixtures/config_tmuxinator/test2.py +++ b/tests/fixtures/import_tmuxinator/test2.py @@ -1,6 +1,6 @@ from .. import utils as test_utils -tmuxinator_yaml = test_utils.read_config_file("config_tmuxinator/test2.yaml") +tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test2.yaml") tmuxinator_dict = { "project_name": "sample", diff --git a/tests/fixtures/config_tmuxinator/test2.yaml b/tests/fixtures/import_tmuxinator/test2.yaml similarity index 100% rename from tests/fixtures/config_tmuxinator/test2.yaml rename to tests/fixtures/import_tmuxinator/test2.yaml diff --git a/tests/fixtures/config_tmuxinator/test3.py b/tests/fixtures/import_tmuxinator/test3.py similarity index 96% rename from tests/fixtures/config_tmuxinator/test3.py rename to tests/fixtures/import_tmuxinator/test3.py index ce8a8b93f4e..dc0468d414d 100644 --- a/tests/fixtures/config_tmuxinator/test3.py +++ b/tests/fixtures/import_tmuxinator/test3.py @@ -1,6 +1,6 @@ from .. import utils as test_utils -tmuxinator_yaml = test_utils.read_config_file("config_tmuxinator/test3.yaml") +tmuxinator_yaml = test_utils.read_workspace_file("import_tmuxinator/test3.yaml") tmuxinator_dict = { "name": "sample", diff --git a/tests/fixtures/config_tmuxinator/test3.yaml b/tests/fixtures/import_tmuxinator/test3.yaml similarity index 100% rename from tests/fixtures/config_tmuxinator/test3.yaml rename to tests/fixtures/import_tmuxinator/test3.yaml diff --git a/tests/fixtures/structures.py b/tests/fixtures/structures.py index 8deb3b82dd1..85f35d10f40 100644 --- a/tests/fixtures/structures.py +++ b/tests/fixtures/structures.py @@ -3,11 +3,11 @@ @dataclasses.dataclass -class ConfigTestData: +class WorkspaceTestData: expand1: t.Any expand2: t.Any expand_blank: t.Any - sampleconfig: t.Any + sample_workspace: t.Any shell_command_before: t.Any shell_command_before_session: t.Any trickle: t.Any diff --git a/tests/fixtures/utils.py b/tests/fixtures/utils.py index 4c39f67defa..7b296eb3574 100644 --- a/tests/fixtures/utils.py +++ b/tests/fixtures/utils.py @@ -3,12 +3,12 @@ from ..constants import FIXTURE_PATH -def get_config_file(_file): # return fixture data, relative to __file__ +def get_workspace_file(_file): # return fixture data, relative to __file__ return FIXTURE_PATH / _file -def read_config_file(_file): # return fixture data, relative to __file__ - return open(get_config_file(_file)).read() +def read_workspace_file(_file): # return fixture data, relative to __file__ + return open(get_workspace_file(_file)).read() def write_config( diff --git a/tests/fixtures/config/__init__.py b/tests/fixtures/workspace/__init__.py similarity index 86% rename from tests/fixtures/config/__init__.py rename to tests/fixtures/workspace/__init__.py index ce1f06197ef..11692230916 100644 --- a/tests/fixtures/config/__init__.py +++ b/tests/fixtures/workspace/__init__.py @@ -2,7 +2,7 @@ expand1, expand2, expand_blank, - sampleconfig, + sample_workspace, shell_command_before, shell_command_before_session, trickle, diff --git a/tests/fixtures/workspacebuilder/config_script_completes.yaml b/tests/fixtures/workspace/builder/config_script_completes.yaml similarity index 65% rename from tests/fixtures/workspacebuilder/config_script_completes.yaml rename to tests/fixtures/workspace/builder/config_script_completes.yaml index baa539f06a0..3772ee8aca2 100644 --- a/tests/fixtures/workspacebuilder/config_script_completes.yaml +++ b/tests/fixtures/workspace/builder/config_script_completes.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace before_script: {script_complete} windows: - panes: diff --git a/tests/fixtures/workspacebuilder/config_script_fails.yaml b/tests/fixtures/workspace/builder/config_script_fails.yaml similarity index 66% rename from tests/fixtures/workspacebuilder/config_script_fails.yaml rename to tests/fixtures/workspace/builder/config_script_fails.yaml index 3a317d24956..25b3b899d49 100644 --- a/tests/fixtures/workspacebuilder/config_script_fails.yaml +++ b/tests/fixtures/workspace/builder/config_script_fails.yaml @@ -1,4 +1,4 @@ - session_name: sampleconfig + session_name: sample workspace before_script: {script_failed} windows: - panes: diff --git a/tests/fixtures/workspacebuilder/config_script_not_exists.yaml b/tests/fixtures/workspace/builder/config_script_not_exists.yaml similarity index 66% rename from tests/fixtures/workspacebuilder/config_script_not_exists.yaml rename to tests/fixtures/workspace/builder/config_script_not_exists.yaml index ca9f57c9dd8..51c76226b30 100644 --- a/tests/fixtures/workspacebuilder/config_script_not_exists.yaml +++ b/tests/fixtures/workspace/builder/config_script_not_exists.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace before_script: {script_not_exists} windows: - panes: diff --git a/tests/fixtures/workspacebuilder/env_var_options.yaml b/tests/fixtures/workspace/builder/env_var_options.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/env_var_options.yaml rename to tests/fixtures/workspace/builder/env_var_options.yaml diff --git a/tests/fixtures/workspacebuilder/environment_vars.yaml b/tests/fixtures/workspace/builder/environment_vars.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/environment_vars.yaml rename to tests/fixtures/workspace/builder/environment_vars.yaml diff --git a/tests/fixtures/workspacebuilder/first_pane_start_directory.yaml b/tests/fixtures/workspace/builder/first_pane_start_directory.yaml similarity index 71% rename from tests/fixtures/workspacebuilder/first_pane_start_directory.yaml rename to tests/fixtures/workspace/builder/first_pane_start_directory.yaml index c45ed9059f8..1f9ae7ed23d 100644 --- a/tests/fixtures/workspacebuilder/first_pane_start_directory.yaml +++ b/tests/fixtures/workspace/builder/first_pane_start_directory.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace windows: - panes: - start_directory: /usr diff --git a/tests/fixtures/workspacebuilder/focus_and_pane.yaml b/tests/fixtures/workspace/builder/focus_and_pane.yaml similarity index 94% rename from tests/fixtures/workspacebuilder/focus_and_pane.yaml rename to tests/fixtures/workspace/builder/focus_and_pane.yaml index d7e9b5d9903..1dd456eb879 100644 --- a/tests/fixtures/workspacebuilder/focus_and_pane.yaml +++ b/tests/fixtures/workspace/builder/focus_and_pane.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: '~' windows: - window_name: focused window diff --git a/tests/fixtures/workspacebuilder/global_options.yaml b/tests/fixtures/workspace/builder/global_options.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/global_options.yaml rename to tests/fixtures/workspace/builder/global_options.yaml diff --git a/tests/fixtures/workspacebuilder/pane_ordering.yaml b/tests/fixtures/workspace/builder/pane_ordering.yaml similarity index 82% rename from tests/fixtures/workspacebuilder/pane_ordering.yaml rename to tests/fixtures/workspace/builder/pane_ordering.yaml index 597ae0649e7..59858582def 100644 --- a/tests/fixtures/workspacebuilder/pane_ordering.yaml +++ b/tests/fixtures/workspace/builder/pane_ordering.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: {HOME} windows: - options: diff --git a/tests/fixtures/workspacebuilder/plugin_awf.yaml b/tests/fixtures/workspace/builder/plugin_awf.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_awf.yaml rename to tests/fixtures/workspace/builder/plugin_awf.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_awf_multiple_windows.yaml b/tests/fixtures/workspace/builder/plugin_awf_multiple_windows.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_awf_multiple_windows.yaml rename to tests/fixtures/workspace/builder/plugin_awf_multiple_windows.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_bs.yaml b/tests/fixtures/workspace/builder/plugin_bs.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_bs.yaml rename to tests/fixtures/workspace/builder/plugin_bs.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_bwb.yaml b/tests/fixtures/workspace/builder/plugin_bwb.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_bwb.yaml rename to tests/fixtures/workspace/builder/plugin_bwb.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_missing_fail.yaml b/tests/fixtures/workspace/builder/plugin_missing_fail.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_missing_fail.yaml rename to tests/fixtures/workspace/builder/plugin_missing_fail.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_multiple_plugins.yaml b/tests/fixtures/workspace/builder/plugin_multiple_plugins.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_multiple_plugins.yaml rename to tests/fixtures/workspace/builder/plugin_multiple_plugins.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_owc.yaml b/tests/fixtures/workspace/builder/plugin_owc.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_owc.yaml rename to tests/fixtures/workspace/builder/plugin_owc.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_owc_multiple_windows.yaml b/tests/fixtures/workspace/builder/plugin_owc_multiple_windows.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_owc_multiple_windows.yaml rename to tests/fixtures/workspace/builder/plugin_owc_multiple_windows.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_r.yaml b/tests/fixtures/workspace/builder/plugin_r.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_r.yaml rename to tests/fixtures/workspace/builder/plugin_r.yaml diff --git a/tests/fixtures/workspacebuilder/plugin_versions_fail.yaml b/tests/fixtures/workspace/builder/plugin_versions_fail.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/plugin_versions_fail.yaml rename to tests/fixtures/workspace/builder/plugin_versions_fail.yaml diff --git a/tests/fixtures/workspacebuilder/regression_00132_dots.yaml b/tests/fixtures/workspace/builder/regression_00132_dots.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/regression_00132_dots.yaml rename to tests/fixtures/workspace/builder/regression_00132_dots.yaml diff --git a/tests/fixtures/workspacebuilder/session_options.yaml b/tests/fixtures/workspace/builder/session_options.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/session_options.yaml rename to tests/fixtures/workspace/builder/session_options.yaml diff --git a/tests/fixtures/workspacebuilder/start_directory.yaml b/tests/fixtures/workspace/builder/start_directory.yaml similarity index 97% rename from tests/fixtures/workspacebuilder/start_directory.yaml rename to tests/fixtures/workspace/builder/start_directory.yaml index e27f8be1896..e5ed2ec5dfe 100644 --- a/tests/fixtures/workspacebuilder/start_directory.yaml +++ b/tests/fixtures/workspace/builder/start_directory.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: '/usr' windows: - window_name: supposed to be /usr/bin diff --git a/tests/fixtures/workspacebuilder/start_directory_relative.yaml b/tests/fixtures/workspace/builder/start_directory_relative.yaml similarity index 85% rename from tests/fixtures/workspacebuilder/start_directory_relative.yaml rename to tests/fixtures/workspace/builder/start_directory_relative.yaml index 1b4ac7cdadc..c6b0e592990 100644 --- a/tests/fixtures/workspacebuilder/start_directory_relative.yaml +++ b/tests/fixtures/workspace/builder/start_directory_relative.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: ./ windows: - window_name: supposed to be /usr/bin @@ -28,7 +28,7 @@ windows: - echo "hey" - shell_command: - echo "moo" -- window_name: inherit start_directory which is rel to config file +- window_name: inherit start_directory which is rel to workspace file panes: - shell_command: - echo hello @@ -36,7 +36,7 @@ windows: - echo "hey" - shell_command: - echo "moo3" -- window_name: cwd relative to config file +- window_name: cwd relative to workspace file start_directory: ./ panes: - shell_command: diff --git a/tests/fixtures/workspacebuilder/start_directory_session_path.yaml b/tests/fixtures/workspace/builder/start_directory_session_path.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/start_directory_session_path.yaml rename to tests/fixtures/workspace/builder/start_directory_session_path.yaml diff --git a/tests/fixtures/workspacebuilder/suppress_history.yaml b/tests/fixtures/workspace/builder/suppress_history.yaml similarity index 85% rename from tests/fixtures/workspacebuilder/suppress_history.yaml rename to tests/fixtures/workspace/builder/suppress_history.yaml index b480f5418a2..04722f9f3ce 100644 --- a/tests/fixtures/workspacebuilder/suppress_history.yaml +++ b/tests/fixtures/workspace/builder/suppress_history.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: '~' suppress_history: false windows: diff --git a/tests/fixtures/workspacebuilder/three_pane.yaml b/tests/fixtures/workspace/builder/three_pane.yaml similarity index 86% rename from tests/fixtures/workspacebuilder/three_pane.yaml rename to tests/fixtures/workspace/builder/three_pane.yaml index 08995253252..860fcd9371a 100644 --- a/tests/fixtures/workspacebuilder/three_pane.yaml +++ b/tests/fixtures/workspace/builder/three_pane.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: '~' windows: - window_name: test diff --git a/tests/fixtures/workspacebuilder/three_windows.yaml b/tests/fixtures/workspace/builder/three_windows.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/three_windows.yaml rename to tests/fixtures/workspace/builder/three_windows.yaml diff --git a/tests/fixtures/workspacebuilder/two_pane.yaml b/tests/fixtures/workspace/builder/two_pane.yaml similarity index 90% rename from tests/fixtures/workspacebuilder/two_pane.yaml rename to tests/fixtures/workspace/builder/two_pane.yaml index 9bb96e70f37..216080feb27 100644 --- a/tests/fixtures/workspacebuilder/two_pane.yaml +++ b/tests/fixtures/workspace/builder/two_pane.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: '~' windows: - layout: main-vertical diff --git a/tests/fixtures/workspacebuilder/two_windows.yaml b/tests/fixtures/workspace/builder/two_windows.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/two_windows.yaml rename to tests/fixtures/workspace/builder/two_windows.yaml diff --git a/tests/fixtures/workspacebuilder/window_automatic_rename.yaml b/tests/fixtures/workspace/builder/window_automatic_rename.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/window_automatic_rename.yaml rename to tests/fixtures/workspace/builder/window_automatic_rename.yaml diff --git a/tests/fixtures/workspacebuilder/window_index.yaml b/tests/fixtures/workspace/builder/window_index.yaml similarity index 83% rename from tests/fixtures/workspacebuilder/window_index.yaml rename to tests/fixtures/workspace/builder/window_index.yaml index 3f6c080dcd4..dda27298595 100644 --- a/tests/fixtures/workspacebuilder/window_index.yaml +++ b/tests/fixtures/workspace/builder/window_index.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace windows: - window_name: zero panes: diff --git a/tests/fixtures/workspacebuilder/window_options.yaml b/tests/fixtures/workspace/builder/window_options.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/window_options.yaml rename to tests/fixtures/workspace/builder/window_options.yaml diff --git a/tests/fixtures/workspacebuilder/window_options_after.yaml b/tests/fixtures/workspace/builder/window_options_after.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/window_options_after.yaml rename to tests/fixtures/workspace/builder/window_options_after.yaml diff --git a/tests/fixtures/workspacebuilder/window_shell.yaml b/tests/fixtures/workspace/builder/window_shell.yaml similarity index 100% rename from tests/fixtures/workspacebuilder/window_shell.yaml rename to tests/fixtures/workspace/builder/window_shell.yaml diff --git a/tests/fixtures/config/expand1.py b/tests/fixtures/workspace/expand1.py similarity index 95% rename from tests/fixtures/config/expand1.py rename to tests/fixtures/workspace/expand1.py index 4547dd43bbd..97d4e3f253a 100644 --- a/tests/fixtures/config/expand1.py +++ b/tests/fixtures/workspace/expand1.py @@ -1,7 +1,7 @@ import os -before_config = { - "session_name": "sampleconfig", +before_workspace = { + "session_name": "sample workspace", "start_directory": "~", "windows": [ { @@ -30,9 +30,9 @@ } -def after_config(): +def after_workspace(): return { - "session_name": "sampleconfig", + "session_name": "sample workspace", "start_directory": os.path.expanduser("~"), "windows": [ { diff --git a/tests/fixtures/config/expand2-expanded.yaml b/tests/fixtures/workspace/expand2-expanded.yaml similarity index 94% rename from tests/fixtures/config/expand2-expanded.yaml rename to tests/fixtures/workspace/expand2-expanded.yaml index e407146427f..0809447aaae 100644 --- a/tests/fixtures/config/expand2-expanded.yaml +++ b/tests/fixtures/workspace/expand2-expanded.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: {HOME} windows: - window_name: focused window diff --git a/tests/fixtures/config/expand2-unexpanded.yaml b/tests/fixtures/workspace/expand2-unexpanded.yaml similarity index 94% rename from tests/fixtures/config/expand2-unexpanded.yaml rename to tests/fixtures/workspace/expand2-unexpanded.yaml index 6e70f42f896..6c1a49ed51e 100644 --- a/tests/fixtures/config/expand2-unexpanded.yaml +++ b/tests/fixtures/workspace/expand2-unexpanded.yaml @@ -1,4 +1,4 @@ -session_name: sampleconfig +session_name: sample workspace start_directory: '~' windows: - window_name: focused window diff --git a/tests/fixtures/workspace/expand2.py b/tests/fixtures/workspace/expand2.py new file mode 100644 index 00000000000..3aa5bd3aa67 --- /dev/null +++ b/tests/fixtures/workspace/expand2.py @@ -0,0 +1,13 @@ +import os + +from .. import utils as test_utils + + +def unexpanded_yaml(): + return test_utils.read_workspace_file("workspace/expand2-unexpanded.yaml") + + +def expanded_yaml(): + return test_utils.read_workspace_file("workspace/expand2-expanded.yaml").format( + HOME=os.path.expanduser("~") + ) diff --git a/tests/fixtures/config/expand_blank.py b/tests/fixtures/workspace/expand_blank.py similarity index 100% rename from tests/fixtures/config/expand_blank.py rename to tests/fixtures/workspace/expand_blank.py diff --git a/tests/fixtures/workspacefreezer/sampleconfig.yaml b/tests/fixtures/workspace/freezer/sample_workspace.yaml similarity index 80% rename from tests/fixtures/workspacefreezer/sampleconfig.yaml rename to tests/fixtures/workspace/freezer/sample_workspace.yaml index fb8b11e3e22..ebd3ab3f9aa 100644 --- a/tests/fixtures/workspacefreezer/sampleconfig.yaml +++ b/tests/fixtures/workspace/freezer/sample_workspace.yaml @@ -1,11 +1,11 @@ -session_name: sampleconfig -start_directory: '~' +session_name: sample workspace +start_directory: "~" windows: - layout: main-vertical panes: - shell_command: - cmd: vim - start_directory: '~' + start_directory: "~" - shell_command: - cmd: echo "hey" - cmd: cd ../ diff --git a/tests/fixtures/config/sampleconfig.py b/tests/fixtures/workspace/sample_workspace.py similarity index 90% rename from tests/fixtures/config/sampleconfig.py rename to tests/fixtures/workspace/sample_workspace.py index 46bc6f2d6fa..1db93f7ab43 100644 --- a/tests/fixtures/config/sampleconfig.py +++ b/tests/fixtures/workspace/sample_workspace.py @@ -1,5 +1,5 @@ -sampleconfigdict = { - "session_name": "sampleconfig", +sample_workspace_dict = { + "session_name": "sample workspace", "start_directory": "~", "windows": [ { diff --git a/tests/fixtures/config/shell_command_before.py b/tests/fixtures/workspace/shell_command_before.py similarity index 97% rename from tests/fixtures/config/shell_command_before.py rename to tests/fixtures/workspace/shell_command_before.py index f458746ad0a..f1b9d601445 100644 --- a/tests/fixtures/config/shell_command_before.py +++ b/tests/fixtures/workspace/shell_command_before.py @@ -1,7 +1,7 @@ import os config_unexpanded = { # shell_command_before is string in some areas - "session_name": "sampleconfig", + "session_name": "sample workspace", "start_directory": "/", "windows": [ { @@ -39,7 +39,7 @@ def config_expanded(): return { # shell_command_before is string in some areas - "session_name": "sampleconfig", + "session_name": "sample workspace", "start_directory": "/", "windows": [ { @@ -91,7 +91,7 @@ def config_expanded(): def config_after(): return { # shell_command_before is string in some areas - "session_name": "sampleconfig", + "session_name": "sample workspace", "start_directory": "/", "windows": [ { diff --git a/tests/fixtures/config/shell_command_before_session-expected.yaml b/tests/fixtures/workspace/shell_command_before_session-expected.yaml similarity index 100% rename from tests/fixtures/config/shell_command_before_session-expected.yaml rename to tests/fixtures/workspace/shell_command_before_session-expected.yaml diff --git a/tests/fixtures/workspace/shell_command_before_session.py b/tests/fixtures/workspace/shell_command_before_session.py new file mode 100644 index 00000000000..79329382d4a --- /dev/null +++ b/tests/fixtures/workspace/shell_command_before_session.py @@ -0,0 +1,6 @@ +from .. import utils as test_utils + +before = test_utils.read_workspace_file("workspace/shell_command_before_session.yaml") +expected = test_utils.read_workspace_file( + "workspace/shell_command_before_session-expected.yaml" +) diff --git a/tests/fixtures/config/shell_command_before_session.yaml b/tests/fixtures/workspace/shell_command_before_session.yaml similarity index 100% rename from tests/fixtures/config/shell_command_before_session.yaml rename to tests/fixtures/workspace/shell_command_before_session.yaml diff --git a/tests/fixtures/config/trickle.py b/tests/fixtures/workspace/trickle.py similarity index 94% rename from tests/fixtures/config/trickle.py rename to tests/fixtures/workspace/trickle.py index 595ee563604..d2c3d165fd7 100644 --- a/tests/fixtures/config/trickle.py +++ b/tests/fixtures/workspace/trickle.py @@ -1,5 +1,5 @@ before = { # shell_command_before is string in some areas - "session_name": "sampleconfig", + "session_name": "sample workspace", "start_directory": "/var", "windows": [ { @@ -23,7 +23,7 @@ } expected = { # shell_command_before is string in some areas - "session_name": "sampleconfig", + "session_name": "sample workspace", "start_directory": "/var", "windows": [ { diff --git a/tests/test_cli.py b/tests/test_cli.py index 0fd1f381a87..7e4651572a1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -14,7 +14,7 @@ from libtmux.common import has_lt_version from libtmux.exc import LibTmuxException from libtmux.session import Session -from tmuxp import cli, config, exc +from tmuxp import cli, exc from tmuxp.cli.import_config import get_teamocil_dir, get_tmuxinator_dir from tmuxp.cli.load import ( _load_append_windows_to_current_session, @@ -23,9 +23,11 @@ load_plugins, load_workspace, ) -from tmuxp.cli.utils import get_config_dir, is_pure_name, scan_config, tmuxp_echo +from tmuxp.cli.utils import tmuxp_echo from tmuxp.config_reader import ConfigReader -from tmuxp.workspacebuilder import WorkspaceBuilder +from tmuxp.workspace import loader +from tmuxp.workspace.builder import WorkspaceBuilder +from tmuxp.workspace.finders import find_workspace_file from .constants import FIXTURE_PATH from .fixtures import utils as test_utils @@ -43,69 +45,6 @@ def test_creates_config_dir_not_exists(tmp_path: pathlib.Path) -> None: assert os.path.exists(tmp_path) -def test_in_dir_from_config_dir(tmp_path: pathlib.Path) -> None: - """config.in_dir() finds configs config dir.""" - - cli.startup(tmp_path) - yaml_config = tmp_path / "myconfig.yaml" - yaml_config.touch() - json_config = tmp_path / "myconfig.json" - json_config.touch() - configs_found = config.in_dir(tmp_path) - - assert len(configs_found) == 2 - - -def test_ignore_non_configs_from_current_dir(tmp_path: pathlib.Path) -> None: - """cli.in_dir() ignore non-config from config dir.""" - - cli.startup(tmp_path) - - junk_config = tmp_path / "myconfig.psd" - junk_config.touch() - conf = tmp_path / "watmyconfig.json" - conf.touch() - configs_found = config.in_dir(tmp_path) - assert len(configs_found) == 1 - - -def test_get_configs_cwd( - tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch -) -> None: - """config.in_cwd() find config in shell current working directory.""" - - confdir = tmp_path / "tmuxpconf2" - confdir.mkdir() - - monkeypatch.chdir(confdir) - config1 = open(".tmuxp.json", "w+b") - config1.close() - - configs_found = config.in_cwd() - assert len(configs_found) == 1 - assert ".tmuxp.json" in configs_found - - -@pytest.mark.parametrize( - "path,expect", - [ - (".", False), - ("./", False), - ("", False), - (".tmuxp.yaml", False), - ("../.tmuxp.yaml", False), - ("../", False), - ("/hello/world", False), - ("~/.tmuxp/hey", False), - ("~/work/c/tmux/", False), - ("~/work/c/tmux/.tmuxp.yaml", False), - ("myproject", True), - ], -) -def test_is_pure_name(path: str, expect: bool) -> None: - assert is_pure_name(path) == expect - - """ scans for .tmuxp.{yaml,yml,json} in directory, returns first result log warning if multiple found: @@ -125,207 +64,12 @@ def test_is_pure_name(path: str, expect: bool) -> None: """ -@pytest.fixture -def homedir(tmp_path: pathlib.Path) -> pathlib.Path: - home = tmp_path / "home" - home.mkdir() - return home - - -@pytest.fixture -def configdir(homedir: pathlib.Path) -> pathlib.Path: - conf = homedir / ".tmuxp" - conf.mkdir() - return conf - - -@pytest.fixture -def projectdir(homedir: pathlib.Path) -> pathlib.Path: - proj = homedir / "work" / "project" - proj.mkdir(parents=True) - return proj - - -def test_tmuxp_configdir_env_var( - tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch -) -> None: - monkeypatch.setenv("TMUXP_CONFIGDIR", str(tmp_path)) - - assert get_config_dir() == str(tmp_path) - - -def test_tmuxp_configdir_xdg_config_dir( - tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch -) -> None: - monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)) - tmux_dir = tmp_path / "tmuxp" - tmux_dir.mkdir() - - assert get_config_dir() == str(tmux_dir) - - -def test_resolve_dot( - tmp_path: pathlib.Path, - homedir: pathlib.Path, - configdir: pathlib.Path, - projectdir: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - monkeypatch.setenv("HOME", str(homedir)) - monkeypatch.setenv("XDG_CONFIG_HOME", str(homedir / ".config")) - - tmuxp_conf_path = projectdir / ".tmuxp.yaml" - tmuxp_conf_path.touch() - user_config_name = "myconfig" - user_config = configdir / f"{user_config_name}.yaml" - user_config.touch() - - project_config = tmuxp_conf_path - - monkeypatch.chdir(projectdir) - - expect = str(project_config) - assert scan_config(".") == expect - assert scan_config("./") == expect - assert scan_config("") == expect - assert scan_config("../project") == expect - assert scan_config("../project/") == expect - assert scan_config(".tmuxp.yaml") == expect - assert scan_config("../../.tmuxp/%s.yaml" % user_config_name) == str(user_config) - assert scan_config("myconfig") == str(user_config) - assert scan_config("~/.tmuxp/myconfig.yaml") == str(user_config) - - with pytest.raises(Exception): - scan_config(".tmuxp.json") - with pytest.raises(Exception): - scan_config(".tmuxp.ini") - with pytest.raises(Exception): - scan_config("../") - with pytest.raises(Exception): - scan_config("mooooooo") - - monkeypatch.chdir(homedir) - - expect = str(project_config) - assert scan_config("work/project") == expect - assert scan_config("work/project/") == expect - assert scan_config("./work/project") == expect - assert scan_config("./work/project/") == expect - assert scan_config(".tmuxp/%s.yaml" % user_config_name) == str(user_config) - assert scan_config("./.tmuxp/%s.yaml" % user_config_name) == str(user_config) - assert scan_config("myconfig") == str(user_config) - assert scan_config("~/.tmuxp/myconfig.yaml") == str(user_config) - - with pytest.raises(Exception): - scan_config("") - with pytest.raises(Exception): - scan_config(".") - with pytest.raises(Exception): - scan_config(".tmuxp.yaml") - with pytest.raises(Exception): - scan_config("../") - with pytest.raises(Exception): - scan_config("mooooooo") - - monkeypatch.chdir(configdir) - - expect = str(project_config) - assert scan_config("../work/project") == expect - assert scan_config("../../home/work/project") == expect - assert scan_config("../work/project/") == expect - assert scan_config("%s.yaml" % user_config_name) == str(user_config) - assert scan_config("./%s.yaml" % user_config_name) == str(user_config) - assert scan_config("myconfig") == str(user_config) - assert scan_config("~/.tmuxp/myconfig.yaml") == str(user_config) - - with pytest.raises(Exception): - scan_config("") - with pytest.raises(Exception): - scan_config(".") - with pytest.raises(Exception): - scan_config(".tmuxp.yaml") - with pytest.raises(Exception): - scan_config("../") - with pytest.raises(Exception): - scan_config("mooooooo") - - monkeypatch.chdir(tmp_path) - - expect = str(project_config) - assert scan_config("home/work/project") == expect - assert scan_config("./home/work/project/") == expect - assert scan_config("home/.tmuxp/%s.yaml" % user_config_name) == str(user_config) - assert scan_config("./home/.tmuxp/%s.yaml" % user_config_name) == str(user_config) - assert scan_config("myconfig") == str(user_config) - assert scan_config("~/.tmuxp/myconfig.yaml") == str(user_config) - - with pytest.raises(Exception): - scan_config("") - with pytest.raises(Exception): - scan_config(".") - with pytest.raises(Exception): - scan_config(".tmuxp.yaml") - with pytest.raises(Exception): - scan_config("../") - with pytest.raises(Exception): - scan_config("mooooooo") - - -def test_scan_config_arg( - homedir: pathlib.Path, - configdir: pathlib.Path, - projectdir: pathlib.Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture, -) -> None: - parser = argparse.ArgumentParser() - parser.add_argument("config_file", type=str) - - def config_cmd(config_file: str) -> None: - tmuxp_echo(scan_config(config_file, config_dir=configdir)) - - monkeypatch.setenv("HOME", str(homedir)) - tmuxp_config_path = projectdir / ".tmuxp.yaml" - tmuxp_config_path.touch() - user_config_name = "myconfig" - user_config = configdir / f"{user_config_name}.yaml" - user_config.touch() - - project_config = projectdir / ".tmuxp.yaml" - - def check_cmd(config_arg) -> "_pytest.capture.CaptureResult": - args = parser.parse_args([config_arg]) - config_cmd(config_file=args.config_file) - return capsys.readouterr() - - monkeypatch.chdir(projectdir) - expect = str(project_config) - assert expect in check_cmd(".").out - assert expect in check_cmd("./").out - assert expect in check_cmd("").out - assert expect in check_cmd("../project").out - assert expect in check_cmd("../project/").out - assert expect in check_cmd(".tmuxp.yaml").out - assert str(user_config) in check_cmd("../../.tmuxp/%s.yaml" % user_config_name).out - assert user_config.stem in check_cmd("myconfig").out - assert str(user_config) in check_cmd("~/.tmuxp/myconfig.yaml").out - - with pytest.raises(FileNotFoundError, match="file not found"): - assert "file not found" in check_cmd(".tmuxp.json").err - with pytest.raises(FileNotFoundError, match="file not found"): - assert "file not found" in check_cmd(".tmuxp.ini").err - with pytest.raises(FileNotFoundError, match="No tmuxp files found"): - assert "No tmuxp files found" in check_cmd("../").err - with pytest.raises(FileNotFoundError, match="config not found in config dir"): - assert "config not found in config dir" in check_cmd("moo").err - - def test_load_workspace(server: "Server", monkeypatch: pytest.MonkeyPatch) -> None: # this is an implementation test. Since this testsuite may be ran within # a tmux session by the developer himself, delete the TMUX variable # temporarily. monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspacebuilder" / "two_pane.yaml" + session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" # open it detached session = load_workspace( @@ -333,7 +77,7 @@ def test_load_workspace(server: "Server", monkeypatch: pytest.MonkeyPatch) -> No ) assert isinstance(session, Session) - assert session.name == "sampleconfig" + assert session.name == "sample workspace" def test_load_workspace_named_session( @@ -343,7 +87,7 @@ def test_load_workspace_named_session( # a tmux session by the developer himself, delete the TMUX variable # temporarily. monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspacebuilder" / "two_pane.yaml" + session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" # open it detached session = load_workspace( @@ -364,7 +108,7 @@ def test_load_workspace_name_match_regression_252( tmp_path: pathlib.Path, server: "Server", monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspacebuilder" / "two_pane.yaml" + session_file = FIXTURE_PATH / "workspace/builder" / "two_pane.yaml" # open it detached session = load_workspace( @@ -372,7 +116,7 @@ def test_load_workspace_name_match_regression_252( ) assert isinstance(session, Session) - assert session.name == "sampleconfig" + assert session.name == "sample workspace" projfile = tmp_path / "simple.yaml" @@ -597,7 +341,7 @@ def test_regression_00132_session_name_with_dots( session: Session, capsys: pytest.CaptureFixture, ) -> None: - yaml_config = FIXTURE_PATH / "workspacebuilder" / "regression_00132_dots.yaml" + yaml_config = FIXTURE_PATH / "workspace/builder" / "regression_00132_dots.yaml" cli_args = [str(yaml_config)] with pytest.raises(libtmux.exc.BadSessionName): cli.cli(["load", *cli_args]) @@ -993,8 +737,8 @@ def test_convert( filename = ".tmuxp.yaml" file_ext = filename.rsplit(".", 1)[-1] assert file_ext in ["yaml", "yml"], file_ext - config_file_path = tmp_path / filename - config_file_path.write_text("\nsession_name: hello\n", encoding="utf-8") + workspace_file_path = tmp_path / filename + workspace_file_path.write_text("\nsession_name: hello\n", encoding="utf-8") oh_my_zsh_path = tmp_path / ".oh-my-zsh" oh_my_zsh_path.mkdir() monkeypatch.setenv("HOME", str(tmp_path)) @@ -1108,7 +852,7 @@ def test_import_teamocil( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: - teamocil_config = test_utils.read_config_file("config_teamocil/test4.yaml") + teamocil_config = test_utils.read_workspace_file("import_teamocil/test4.yaml") teamocil_path = tmp_path / ".teamocil" teamocil_path.mkdir() @@ -1156,7 +900,7 @@ def test_import_tmuxinator( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: - tmuxinator_config = test_utils.read_config_file("config_tmuxinator/test3.yaml") + tmuxinator_config = test_utils.read_workspace_file("import_tmuxinator/test3.yaml") tmuxinator_path = tmp_path / ".tmuxinator" tmuxinator_path.mkdir() @@ -1320,14 +1064,14 @@ def test_pass_config_dir_ClickPath( expect = str(user_config) parser = argparse.ArgumentParser() - parser.add_argument("config_file", type=str) + parser.add_argument("workspace_file", type=str) - def config_cmd(config_file: str) -> None: - tmuxp_echo(scan_config(config_file, config_dir=configdir)) + def config_cmd(workspace_file: str) -> None: + tmuxp_echo(find_workspace_file(workspace_file, workspace_dir=configdir)) def check_cmd(config_arg) -> "_pytest.capture.CaptureResult": args = parser.parse_args([config_arg]) - config_cmd(config_file=args.config_file) + config_cmd(workspace_file=args.workspace_file) return capsys.readouterr() monkeypatch.chdir(configdir) @@ -1360,7 +1104,7 @@ def test_ls_cli( # should ignore: # - directories should be ignored - # - extensions not covered in VALID_CONFIG_DIR_FILE_EXTENSIONS + # - extensions not covered in VALID_WORKSPACE_DIR_FILE_EXTENSIONS ignored_filenames = [".git/", ".gitignore/", "session_4.txt"] stems = [os.path.splitext(f)[0] for f in filenames if f not in ignored_filenames] @@ -1383,10 +1127,10 @@ def test_ls_cli( def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: from tmuxp_test_plugin_bwb.plugin import PluginBeforeWorkspaceBuilder - plugins_config = test_utils.read_config_file("workspacebuilder/plugin_bwb.yaml") + plugins_config = test_utils.read_workspace_file("workspace/builder/plugin_bwb.yaml") sconfig = ConfigReader._load(format="yaml", content=plugins_config) - sconfig = config.expand(sconfig) + sconfig = loader.expand(sconfig) plugins = load_plugins(sconfig) @@ -1404,7 +1148,7 @@ def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: "cli_args,inputs", [ ( - ["load", "tests/fixtures/workspacebuilder/plugin_versions_fail.yaml"], + ["load", "tests/fixtures/workspace/builder/plugin_versions_fail.yaml"], ["y\n"], ) ], @@ -1425,7 +1169,7 @@ def test_load_plugins_version_fail_skip( "cli_args,inputs", [ ( - ["load", "tests/fixtures/workspacebuilder/plugin_versions_fail.yaml"], + ["load", "tests/fixtures/workspace/builder/plugin_versions_fail.yaml"], ["n\n"], ) ], @@ -1449,7 +1193,8 @@ def test_load_plugins_version_fail_no_skip( @pytest.mark.parametrize( - "cli_args", [(["load", "tests/fixtures/workspacebuilder/plugin_missing_fail.yaml"])] + "cli_args", + [(["load", "tests/fixtures/workspace/builder/plugin_missing_fail.yaml"])], ) def test_load_plugins_plugin_missing( monkeypatch_plugin_test_packages: None, @@ -1474,7 +1219,7 @@ def test_plugin_system_before_script( # a tmux session by the developer himself, delete the TMUX variable # temporarily. monkeypatch.delenv("TMUX", raising=False) - session_file = FIXTURE_PATH / "workspacebuilder" / "plugin_bs.yaml" + session_file = FIXTURE_PATH / "workspace/builder" / "plugin_bs.yaml" # open it detached session = load_workspace( @@ -1488,10 +1233,10 @@ def test_plugin_system_before_script( def test_reattach_plugins( monkeypatch_plugin_test_packages: None, server: "Server" ) -> None: - config_plugins = test_utils.read_config_file("workspacebuilder/plugin_r.yaml") + config_plugins = test_utils.read_workspace_file("workspace/builder/plugin_r.yaml") sconfig = ConfigReader._load(format="yaml", content=config_plugins) - sconfig = config.expand(sconfig) + sconfig = loader.expand(sconfig) # open it detached builder = WorkspaceBuilder( @@ -1518,7 +1263,7 @@ def test_load_attached( attach_session_mock = mocker.patch("libtmux.session.Session.attach_session") attach_session_mock.return_value.stderr = None - yaml_config = test_utils.read_config_file("workspacebuilder/two_pane.yaml") + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") sconfig = ConfigReader._load(format="yaml", content=yaml_config) builder = WorkspaceBuilder(sconf=sconfig, server=server) @@ -1537,7 +1282,7 @@ def test_load_attached_detached( attach_session_mock = mocker.patch("libtmux.session.Session.attach_session") attach_session_mock.return_value.stderr = None - yaml_config = test_utils.read_config_file("workspacebuilder/two_pane.yaml") + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") sconfig = ConfigReader._load(format="yaml", content=yaml_config) builder = WorkspaceBuilder(sconf=sconfig, server=server) @@ -1556,7 +1301,7 @@ def test_load_attached_within_tmux( switch_client_mock = mocker.patch("libtmux.session.Session.switch_client") switch_client_mock.return_value.stderr = None - yaml_config = test_utils.read_config_file("workspacebuilder/two_pane.yaml") + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") sconfig = ConfigReader._load(format="yaml", content=yaml_config) builder = WorkspaceBuilder(sconf=sconfig, server=server) @@ -1575,7 +1320,7 @@ def test_load_attached_within_tmux_detached( switch_client_mock = mocker.patch("libtmux.session.Session.switch_client") switch_client_mock.return_value.stderr = None - yaml_config = test_utils.read_config_file("workspacebuilder/two_pane.yaml") + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") sconfig = ConfigReader._load(format="yaml", content=yaml_config) builder = WorkspaceBuilder(sconf=sconfig, server=server) @@ -1588,7 +1333,7 @@ def test_load_attached_within_tmux_detached( def test_load_append_windows_to_current_session( server: "Server", monkeypatch: pytest.MonkeyPatch ) -> None: - yaml_config = test_utils.read_config_file("workspacebuilder/two_pane.yaml") + yaml_config = test_utils.read_workspace_file("workspace/builder/two_pane.yaml") sconfig = ConfigReader._load(format="yaml", content=yaml_config) builder = WorkspaceBuilder(sconf=sconfig, server=server) diff --git a/tests/test_workspacefreezer.py b/tests/test_workspacefreezer.py deleted file mode 100644 index a3103ccca34..00000000000 --- a/tests/test_workspacefreezer.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Tests for freezing tmux sessions with tmuxp.""" -import time - -from tmuxp import config -from tmuxp.config_reader import ConfigReader -from tmuxp.workspacebuilder import WorkspaceBuilder, freeze - -from .fixtures import utils as test_utils - - -def test_freeze_config(session): - session_config = ConfigReader._from_file( - test_utils.get_config_file("workspacefreezer/sampleconfig.yaml") - ) - - builder = WorkspaceBuilder(sconf=session_config) - builder.build(session=session) - assert session == builder.session - - time.sleep(0.50) - - session = session - new_config = freeze(session) - - config.validate_schema(new_config) - - # These should dump without an error - ConfigReader._dump(format="json", content=new_config) - ConfigReader._dump(format="yaml", content=new_config) - - # Inline configs should also dump without an error - compact_config = config.inline(new_config) - - ConfigReader._dump(format="json", content=compact_config) - ConfigReader._dump(format="yaml", content=compact_config) diff --git a/tests/workspace/__init__.py b/tests/workspace/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/workspace/conftest.py b/tests/workspace/conftest.py new file mode 100644 index 00000000000..f870c845ec8 --- /dev/null +++ b/tests/workspace/conftest.py @@ -0,0 +1,23 @@ +import types + +import pytest + +from ..fixtures.structures import WorkspaceTestData + + +@pytest.fixture +def config_fixture(): + """Deferred import of tmuxp.tests.fixtures.* + + pytest setup (conftest.py) patches os.environ["HOME"], delay execution of + os.path.expanduser until here. + """ + from ..fixtures import workspace as test_workspace_data + + return WorkspaceTestData( + **{ + k: v + for k, v in test_workspace_data.__dict__.items() + if isinstance(v, types.ModuleType) + } + ) diff --git a/tests/test_workspacebuilder.py b/tests/workspace/test_builder.py similarity index 72% rename from tests/test_workspacebuilder.py rename to tests/workspace/test_builder.py index 7a77c689ae0..4ee513cbc9d 100644 --- a/tests/test_workspacebuilder.py +++ b/tests/workspace/test_builder.py @@ -11,24 +11,25 @@ from libtmux.common import has_gte_version, has_lt_version from libtmux.test import retry_until, temp_session from libtmux.window import Window -from tmuxp import config, exc +from tmuxp import exc from tmuxp.cli.load import load_plugins from tmuxp.config_reader import ConfigReader -from tmuxp.workspacebuilder import WorkspaceBuilder +from tmuxp.workspace import loader +from tmuxp.workspace.builder import WorkspaceBuilder -from .constants import EXAMPLE_PATH, FIXTURE_PATH -from .fixtures import utils as test_utils +from ..constants import EXAMPLE_PATH, FIXTURE_PATH +from ..fixtures import utils as test_utils if t.TYPE_CHECKING: from libtmux.server import Server def test_split_windows(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/two_pane.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/two_pane.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) window_count = len(session._windows) # current window count assert len(session._windows) == window_count @@ -44,11 +45,11 @@ def test_split_windows(session): def test_split_windows_three_pane(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/three_pane.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/three_pane.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) window_count = len(session._windows) # current window count assert len(session._windows) == window_count @@ -66,13 +67,13 @@ def test_split_windows_three_pane(session): def test_focus_pane_index(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/focus_and_pane.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/focus_and_pane.yaml") ) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) @@ -140,13 +141,13 @@ def f(): """.strip() ) def test_suppress_history(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/suppress_history.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/suppress_history.yaml") ) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) inHistoryWindow = session.find_where({"window_name": "inHistory"}) @@ -194,12 +195,12 @@ def f(): def test_session_options(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/session_options.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/session_options.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) assert "/bin/sh" in session.show_option("default-shell") @@ -207,12 +208,12 @@ def test_session_options(session): def test_global_options(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/global_options.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/global_options.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) assert "top" in session.show_option("status-position", _global=True) @@ -227,12 +228,12 @@ def test_global_session_env_options(session, monkeypatch): main_pane_height = 8 monkeypatch.setenv("MAIN_PANE_HEIGHT", str(main_pane_height)) - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/env_var_options.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/env_var_options.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) assert visual_silence in session.show_option("visual-silence", _global=True) @@ -243,15 +244,15 @@ def test_global_session_env_options(session, monkeypatch): def test_window_options(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/window_options.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/window_options.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) if has_gte_version("2.3"): - sconfig["windows"][0]["options"]["pane-border-format"] = " #P " + workspace["windows"][0]["options"]["pane-border-format"] = " #P " - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) window_count = len(session._windows) # current window count assert len(session._windows) == window_count @@ -272,12 +273,12 @@ def test_window_options(session): @pytest.mark.flaky(reruns=5) def test_window_options_after(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/window_options_after.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/window_options_after.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) def assert_last_line(p, s): @@ -310,12 +311,12 @@ def f(): def test_window_shell(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/window_shell.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/window_shell.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) for w, wconf in builder.iter_create_windows(session): if "window_shell" in wconf: @@ -331,12 +332,12 @@ def f(): def test_environment_variables(session): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/environment_vars.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/environment_vars.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session) assert session.getenv("FOO") == "BAR" @@ -345,17 +346,17 @@ def test_environment_variables(session): def test_automatic_rename_option(session): """With option automatic-rename: on.""" - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/window_automatic_rename.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/window_automatic_rename.yaml") ) # This should be a command guaranteed to be terminal name across systems - portable_command = sconfig["windows"][0]["panes"][0]["shell_command"][0]["cmd"] + portable_command = workspace["windows"][0]["panes"][0]["shell_command"][0]["cmd"] # If a command is like "man ls", get the command base name, "ls" if " " in portable_command: portable_command = portable_command.split(" ")[0] - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) window_count = len(session._windows) # current window count assert len(session._windows) == window_count @@ -400,10 +401,10 @@ def check_window_name_match() -> bool: def test_blank_pane_count(session): """:todo: Verify blank panes of various types build into workspaces.""" - yaml_config_file = EXAMPLE_PATH / "blank-panes.yaml" - test_config = ConfigReader._from_file(yaml_config_file) + yaml_workspace_file = EXAMPLE_PATH / "blank-panes.yaml" + test_config = ConfigReader._from_file(yaml_workspace_file) - test_config = config.expand(test_config) + test_config = loader.expand(test_config) builder = WorkspaceBuilder(sconf=test_config) builder.build(session=session) @@ -426,14 +427,16 @@ def test_start_directory(session, tmp_path: pathlib.Path): test_dir = tmp_path / "foo bar" test_dir.mkdir() - yaml_config = test_utils.read_config_file("workspacebuilder/start_directory.yaml") - test_config = yaml_config.format(TEST_DIR=test_dir) + yaml_workspace = test_utils.read_workspace_file( + "workspace/builder/start_directory.yaml" + ) + test_config = yaml_workspace.format(TEST_DIR=test_dir) - sconfig = ConfigReader._load(format="yaml", content=test_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = ConfigReader._load(format="yaml", content=test_config) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) assert session == builder.session @@ -463,8 +466,8 @@ def test_start_directory_relative(session, tmp_path: pathlib.Path): $ tmuxp load . """ - yaml_config = test_utils.read_config_file( - "workspacebuilder/start_directory_relative.yaml" + yaml_workspace = test_utils.read_workspace_file( + "workspace/builder/start_directory_relative.yaml" ) test_dir = tmp_path / "foo bar" @@ -472,17 +475,17 @@ def test_start_directory_relative(session, tmp_path: pathlib.Path): config_dir = tmp_path / "testRelConfigDir" config_dir.mkdir() - test_config = yaml_config.format(TEST_DIR=test_dir) - sconfig = ConfigReader._load(format="yaml", content=test_config) + test_config = yaml_workspace.format(TEST_DIR=test_dir) + workspace = ConfigReader._load(format="yaml", content=test_config) # the second argument of os.getcwd() mimics the behavior - # the CLI loader will do, but it passes in the config file's location. - sconfig = config.expand(sconfig, config_dir) + # the CLI loader will do, but it passes in the workspace file's location. + workspace = loader.expand(workspace, config_dir) - sconfig = config.trickle(sconfig) + workspace = loader.trickle(workspace) assert os.path.exists(config_dir) assert os.path.exists(test_dir) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) assert session == builder.session @@ -505,13 +508,15 @@ def f(): has_lt_version("3.2a"), reason="needs format introduced in tmux >= 3.2a" ) def test_start_directory_sets_session_path(server): - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/start_directory_session_path.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file( + "workspace/builder/start_directory_session_path.yaml" + ) ) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() session = builder.session @@ -526,8 +531,8 @@ def test_pane_order(session): Regression test for https://github.com/tmux-python/tmuxp/issues/15. """ - yaml_config = test_utils.read_config_file( - "workspacebuilder/pane_ordering.yaml" + yaml_workspace = test_utils.read_workspace_file( + "workspace/builder/pane_ordering.yaml" ).format(HOME=os.path.realpath(os.path.expanduser("~"))) # test order of `panes` (and pane_index) above against pane_dirs @@ -538,11 +543,11 @@ def test_pane_order(session): os.path.realpath(os.path.expanduser("~")), ] - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = ConfigReader._load(format="yaml", content=yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) window_count = len(session._windows) # current window count assert len(session._windows) == window_count @@ -578,13 +583,13 @@ def test_window_index(session): base_index = int(proc.stdout[0]) name_index_map = {"zero": 0 + base_index, "one": 1 + base_index, "five": 5} - sconfig = ConfigReader._from_file( - test_utils.get_config_file("workspacebuilder/window_index.yaml") + workspace = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/builder/window_index.yaml") ) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) for window, _ in builder.iter_create_windows(session): expected_index = name_index_map[window["window_name"]] @@ -592,18 +597,18 @@ def test_window_index(session): def test_before_load_throw_error_if_retcode_error(server): - config_script_fails = test_utils.read_config_file( - "workspacebuilder/config_script_fails.yaml" + config_script_fails = test_utils.read_workspace_file( + "workspace/builder/config_script_fails.yaml" ) - yaml_config = config_script_fails.format( + yaml_workspace = config_script_fails.format( script_failed=FIXTURE_PATH / "script_failed.sh", ) - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = ConfigReader._load(format="yaml", content=yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) with temp_session(server) as sess: session_name = sess.name @@ -616,17 +621,17 @@ def test_before_load_throw_error_if_retcode_error(server): def test_before_load_throw_error_if_file_not_exists(server): - config_script_not_exists = test_utils.read_config_file( - "workspacebuilder/config_script_not_exists.yaml" + config_script_not_exists = test_utils.read_workspace_file( + "workspace/builder/config_script_not_exists.yaml" ) - yaml_config = config_script_not_exists.format( + yaml_workspace = config_script_not_exists.format( script_not_exists=FIXTURE_PATH / "script_not_exists.sh", ) - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = ConfigReader._load(format="yaml", content=yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) with temp_session(server) as session: session_name = session.name @@ -640,37 +645,37 @@ def test_before_load_throw_error_if_file_not_exists(server): def test_before_load_true_if_test_passes(server): - config_script_completes = test_utils.read_config_file( - "workspacebuilder/config_script_completes.yaml" + config_script_completes = test_utils.read_workspace_file( + "workspace/builder/config_script_completes.yaml" ) script_complete_sh = FIXTURE_PATH / "script_complete.sh" assert script_complete_sh.exists() - yaml_config = config_script_completes.format(script_complete=script_complete_sh) - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + yaml_workspace = config_script_completes.format(script_complete=script_complete_sh) + workspace = ConfigReader._load(format="yaml", content=yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) with temp_session(server) as session: builder.build(session=session) def test_before_load_true_if_test_passes_with_args(server): - config_script_completes = test_utils.read_config_file( - "workspacebuilder/config_script_completes.yaml" + config_script_completes = test_utils.read_workspace_file( + "workspace/builder/config_script_completes.yaml" ) script_complete_sh = FIXTURE_PATH / "script_complete.sh" assert script_complete_sh.exists() - yaml_config = config_script_completes.format(script_complete=script_complete_sh) + yaml_workspace = config_script_completes.format(script_complete=script_complete_sh) - sconfig = ConfigReader._load(format="yaml", content=yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = ConfigReader._load(format="yaml", content=yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) with temp_session(server) as session: builder.build(session=session) @@ -679,12 +684,12 @@ def test_before_load_true_if_test_passes_with_args(server): def test_plugin_system_before_workspace_builder( monkeypatch_plugin_test_packages, session ): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/plugin_bwb.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/plugin_bwb.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig, plugins=load_plugins(sconfig)) + builder = WorkspaceBuilder(sconf=workspace, plugins=load_plugins(workspace)) assert len(builder.plugins) > 0 builder.build(session=session) @@ -694,12 +699,12 @@ def test_plugin_system_before_workspace_builder( def test_plugin_system_on_window_create(monkeypatch_plugin_test_packages, session): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/plugin_owc.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/plugin_owc.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig, plugins=load_plugins(sconfig)) + builder = WorkspaceBuilder(sconf=workspace, plugins=load_plugins(workspace)) assert len(builder.plugins) > 0 builder.build(session=session) @@ -709,12 +714,12 @@ def test_plugin_system_on_window_create(monkeypatch_plugin_test_packages, sessio def test_plugin_system_after_window_finished(monkeypatch_plugin_test_packages, session): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/plugin_awf.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/plugin_awf.yaml") ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig, plugins=load_plugins(sconfig)) + builder = WorkspaceBuilder(sconf=workspace, plugins=load_plugins(workspace)) assert len(builder.plugins) > 0 builder.build(session=session) @@ -724,14 +729,14 @@ def test_plugin_system_after_window_finished(monkeypatch_plugin_test_packages, s def test_plugin_system_on_window_create_multiple_windows(session): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file( - "workspacebuilder/plugin_owc_multiple_windows.yaml" + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file( + "workspace/builder/plugin_owc_multiple_windows.yaml" ) ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig, plugins=load_plugins(sconfig)) + builder = WorkspaceBuilder(sconf=workspace, plugins=load_plugins(workspace)) assert len(builder.plugins) > 0 builder.build(session=session) @@ -744,14 +749,14 @@ def test_plugin_system_on_window_create_multiple_windows(session): def test_plugin_system_after_window_finished_multiple_windows( monkeypatch_plugin_test_packages, session ): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file( - "workspacebuilder/plugin_awf_multiple_windows.yaml" + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file( + "workspace/builder/plugin_awf_multiple_windows.yaml" ) ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig, plugins=load_plugins(sconfig)) + builder = WorkspaceBuilder(sconf=workspace, plugins=load_plugins(workspace)) assert len(builder.plugins) > 0 builder.build(session=session) @@ -762,12 +767,14 @@ def test_plugin_system_after_window_finished_multiple_windows( def test_plugin_system_multiple_plugins(monkeypatch_plugin_test_packages, session): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/plugin_multiple_plugins.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file( + "workspace/builder/plugin_multiple_plugins.yaml" + ) ) - sconfig = config.expand(sconfig) + workspace = loader.expand(workspace) - builder = WorkspaceBuilder(sconf=sconfig, plugins=load_plugins(sconfig)) + builder = WorkspaceBuilder(sconf=workspace, plugins=load_plugins(workspace)) assert len(builder.plugins) > 0 builder.build(session=session) @@ -784,30 +791,30 @@ def test_plugin_system_multiple_plugins(monkeypatch_plugin_test_packages, sessio def test_load_configs_same_session(server): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/three_windows.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() assert len(server.sessions) == 1 assert len(server.sessions[0]._windows) == 3 - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/two_windows.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/two_windows.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() assert len(server.sessions) == 2 assert len(server.sessions[1]._windows) == 2 - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/two_windows.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/two_windows.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build(server.sessions[1], True) assert len(server.sessions) == 2 @@ -815,21 +822,21 @@ def test_load_configs_same_session(server): def test_load_configs_separate_sessions(server): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/three_windows.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() assert len(server.sessions) == 1 assert len(server.sessions[0]._windows) == 3 - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/two_windows.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/two_windows.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() assert len(server.sessions) == 2 @@ -838,18 +845,18 @@ def test_load_configs_separate_sessions(server): def test_find_current_active_pane(server, monkeypatch): - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/three_windows.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/three_windows.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() - sconfig = ConfigReader._from_file( - path=test_utils.get_config_file("workspacebuilder/two_windows.yaml") + workspace = ConfigReader._from_file( + path=test_utils.get_workspace_file("workspace/builder/two_windows.yaml") ) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() assert len(server.list_sessions()) == 2 @@ -861,7 +868,7 @@ def test_find_current_active_pane(server, monkeypatch): ] monkeypatch.setenv("TMUX_PANE", first_pane_on_second_session_id) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) assert builder.find_current_attached_session() == second_session @@ -995,12 +1002,12 @@ def test_load_workspace_enter( output, should_see, ): - yaml_config = tmp_path / "simple.yaml" - yaml_config.write_text(yaml, encoding="utf-8") - sconfig = ConfigReader._from_file(yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + yaml_workspace = tmp_path / "simple.yaml" + yaml_workspace.write_text(yaml, encoding="utf-8") + workspace = ConfigReader._from_file(yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) + builder = WorkspaceBuilder(sconf=workspace, server=server) builder.build() session = builder.session @@ -1115,12 +1122,12 @@ def test_load_workspace_sleep( sleep: int, output, ): - yaml_config = tmp_path / "simple.yaml" - yaml_config.write_text(yaml, encoding="utf-8") - sconfig = ConfigReader._from_file(yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + yaml_workspace = tmp_path / "simple.yaml" + yaml_workspace.write_text(yaml, encoding="utf-8") + workspace = ConfigReader._from_file(yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) + builder = WorkspaceBuilder(sconf=workspace, server=server) t = time.process_time() @@ -1139,15 +1146,15 @@ def test_load_workspace_sleep( def test_first_pane_start_directory(session, tmp_path: pathlib.Path): - yaml_config = test_utils.get_config_file( - "workspacebuilder/first_pane_start_directory.yaml" + yaml_workspace = test_utils.get_workspace_file( + "workspace/builder/first_pane_start_directory.yaml" ) - sconfig = ConfigReader._from_file(yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = ConfigReader._from_file(yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) assert session == builder.session @@ -1170,10 +1177,10 @@ def f(): has_lt_version("2.9"), reason="needs option introduced in tmux >= 2.9" ) def test_layout_main_horizontal(session): - yaml_config = test_utils.get_config_file("workspacebuilder/three_pane.yaml") - sconfig = ConfigReader._from_file(path=yaml_config) + yaml_workspace = test_utils.get_workspace_file("workspace/builder/three_pane.yaml") + workspace = ConfigReader._from_file(path=yaml_workspace) - builder = WorkspaceBuilder(sconf=sconfig) + builder = WorkspaceBuilder(sconf=workspace) builder.build(session=session) assert session.windows @@ -1256,22 +1263,22 @@ def test_issue_800_default_size_many_windows( See also: https://github.com/tmux-python/tmuxp/issues/800 """ - yaml_config = test_utils.get_config_file( + yaml_workspace = test_utils.get_workspace_file( "regressions/issue_800_default_size_many_windows.yaml" ) - sconfig = ConfigReader._from_file(yaml_config) - sconfig = config.expand(sconfig) - sconfig = config.trickle(sconfig) + workspace = ConfigReader._from_file(yaml_workspace) + workspace = loader.expand(workspace) + workspace = loader.trickle(workspace) if isinstance(confoverrides, dict): for k, v in confoverrides.items(): - sconfig[k] = v + workspace[k] = v if TMUXP_DEFAULT_SIZE is not None: monkeypatch.setenv("TMUXP_DEFAULT_SIZE", TMUXP_DEFAULT_SIZE) - builder = WorkspaceBuilder(sconf=sconfig, server=server) + builder = WorkspaceBuilder(sconf=workspace, server=server) if raises: with pytest.raises(Exception): diff --git a/tests/test_config.py b/tests/workspace/test_config.py similarity index 54% rename from tests/test_config.py rename to tests/workspace/test_config.py index 8ae89298534..bba52886fa4 100644 --- a/tests/test_config.py +++ b/tests/workspace/test_config.py @@ -1,38 +1,19 @@ """Test for tmuxp configuration import, inlining, expanding and export.""" import os import pathlib -import types import typing from typing import Union import pytest -from tmuxp import config, exc +from tmuxp import exc from tmuxp.config_reader import ConfigReader +from tmuxp.workspace import loader, validation -from .constants import EXAMPLE_PATH +from ..constants import EXAMPLE_PATH if typing.TYPE_CHECKING: - from .fixtures.structures import ConfigTestData - - -@pytest.fixture -def config_fixture(): - """Deferred import of tmuxp.tests.fixtures.* - - pytest setup (conftest.py) patches os.environ["HOME"], delay execution of - os.path.expanduser until here. - """ - from .fixtures import config as test_config_data - from .fixtures.structures import ConfigTestData - - return ConfigTestData( - **{ - k: v - for k, v in test_config_data.__dict__.items() - if isinstance(v, types.ModuleType) - } - ) + from ..fixtures.structures import WorkspaceTestData def load_yaml(path: Union[str, pathlib.Path]) -> str: @@ -41,40 +22,29 @@ def load_yaml(path: Union[str, pathlib.Path]) -> str: ) -def load_config(path: Union[str, pathlib.Path]) -> str: +def load_workspace(path: Union[str, pathlib.Path]) -> str: return ConfigReader._from_file( pathlib.Path(path) if isinstance(path, str) else path ) -def test_export_json(tmp_path: pathlib.Path, config_fixture: "ConfigTestData"): - json_config_file = tmp_path / "config.json" - - configparser = ConfigReader(config_fixture.sampleconfig.sampleconfigdict) - - json_config_data = configparser.dump("json", indent=2) - - json_config_file.write_text(json_config_data, encoding="utf-8") - - new_config_data = ConfigReader._from_file(path=json_config_file) - assert config_fixture.sampleconfig.sampleconfigdict == new_config_data - +def test_export_json(tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData"): + json_workspace_file = tmp_path / "config.json" -def test_export_yaml(tmp_path: pathlib.Path, config_fixture: "ConfigTestData"): - yaml_config_file = tmp_path / "config.yaml" + configparser = ConfigReader(config_fixture.sample_workspace.sample_workspace_dict) - sampleconfig = config.inline(config_fixture.sampleconfig.sampleconfigdict) - configparser = ConfigReader(sampleconfig) + json_workspace_data = configparser.dump("json", indent=2) - yaml_config_data = configparser.dump("yaml", indent=2, default_flow_style=False) + json_workspace_file.write_text(json_workspace_data, encoding="utf-8") - yaml_config_file.write_text(yaml_config_data, encoding="utf-8") + new_workspace_data = ConfigReader._from_file(path=json_workspace_file) + assert config_fixture.sample_workspace.sample_workspace_dict == new_workspace_data - new_config_data = load_config(str(yaml_config_file)) - assert config_fixture.sampleconfig.sampleconfigdict == new_config_data - -def test_scan_config(tmp_path: pathlib.Path): +# +# There's no tests for this +# +def test_find_workspace_file(tmp_path: pathlib.Path): configs = [] garbage_file = tmp_path / "config.psd" @@ -103,13 +73,13 @@ def test_scan_config(tmp_path: pathlib.Path): assert len(configs) == files -def test_config_expand1(config_fixture: "ConfigTestData"): +def test_workspace_expand1(config_fixture: "WorkspaceTestData"): """Expand shell commands from string to list.""" - test_config = config.expand(config_fixture.expand1.before_config) - assert test_config == config_fixture.expand1.after_config() + test_workspace = loader.expand(config_fixture.expand1.before_workspace) + assert test_workspace == config_fixture.expand1.after_workspace() -def test_config_expand2(config_fixture: "ConfigTestData"): +def test_workspace_expand2(config_fixture: "WorkspaceTestData"): """Expand shell commands from string to list.""" unexpanded_dict = ConfigReader._load( format="yaml", content=config_fixture.expand2.unexpanded_yaml() @@ -117,56 +87,13 @@ def test_config_expand2(config_fixture: "ConfigTestData"): expanded_dict = ConfigReader._load( format="yaml", content=config_fixture.expand2.expanded_yaml() ) - assert config.expand(unexpanded_dict) == expanded_dict - - -"""Tests for :meth:`config.inline()`.""" - -ibefore_config = { # inline config - "session_name": "sampleconfig", - "start_directory": "~", - "windows": [ - { - "shell_command": ["top"], - "window_name": "editor", - "panes": [{"shell_command": ["vim"]}, {"shell_command": ['cowsay "hey"']}], - "layout": "main-verticle", - }, - { - "window_name": "logging", - "panes": [{"shell_command": ["tail -F /var/log/syslog"]}], - }, - {"options": {"automatic-rename": True}, "panes": [{"shell_command": ["htop"]}]}, - ], -} - -iafter_config = { - "session_name": "sampleconfig", - "start_directory": "~", - "windows": [ - { - "shell_command": "top", - "window_name": "editor", - "panes": ["vim", 'cowsay "hey"'], - "layout": "main-verticle", - }, - {"window_name": "logging", "panes": ["tail -F /var/log/syslog"]}, - {"options": {"automatic-rename": True}, "panes": ["htop"]}, - ], -} - - -def test_inline_config(): - """:meth:`config.inline()` shell commands list to string.""" - - test_config = config.inline(ibefore_config) - assert test_config == iafter_config + assert loader.expand(unexpanded_dict) == expanded_dict """Test config inheritance for the nested 'start_command'.""" -inheritance_config_before = { - "session_name": "sampleconfig", +inheritance_workspace_before = { + "session_name": "sample workspace", "start_directory": "/", "windows": [ { @@ -184,8 +111,8 @@ def test_inline_config(): ], } -inheritance_config_after = { - "session_name": "sampleconfig", +inheritance_workspace_after = { + "session_name": "sample workspace", "start_directory": "/", "windows": [ { @@ -204,16 +131,16 @@ def test_inline_config(): } -def test_inheritance_config(): - config = inheritance_config_before +def test_inheritance_workspace(): + workspace = inheritance_workspace_before # TODO: Look at verifying window_start_directory - # if 'start_directory' in config: - # session_start_directory = config['start_directory'] + # if 'start_directory' in workspace: + # session_start_directory = workspace['start_directory'] # else: # session_start_directory = None - # for windowconfitem in config['windows']: + # for windowconfitem in workspace['windows']: # window_start_directory = None # # if 'start_directory' in windowconfitem: @@ -229,39 +156,39 @@ def test_inheritance_config(): # elif session_start_directory: # paneconfitem['start_directory'] = session_start_directory - assert config == inheritance_config_after + assert workspace == inheritance_workspace_after -def test_shell_command_before(config_fixture: "ConfigTestData"): +def test_shell_command_before(config_fixture: "WorkspaceTestData"): """Config inheritance for the nested 'start_command'.""" - test_config = config_fixture.shell_command_before.config_unexpanded - test_config = config.expand(test_config) + test_workspace = config_fixture.shell_command_before.config_unexpanded + test_workspace = loader.expand(test_workspace) - assert test_config == config_fixture.shell_command_before.config_expanded() + assert test_workspace == config_fixture.shell_command_before.config_expanded() - test_config = config.trickle(test_config) - assert test_config == config_fixture.shell_command_before.config_after() + test_workspace = loader.trickle(test_workspace) + assert test_workspace == config_fixture.shell_command_before.config_after() -def test_in_session_scope(config_fixture: "ConfigTestData"): +def test_in_session_scope(config_fixture: "WorkspaceTestData"): sconfig = ConfigReader._load( format="yaml", content=config_fixture.shell_command_before_session.before ) - config.validate_schema(sconfig) + validation.validate_schema(sconfig) - assert config.expand(sconfig) == sconfig - assert config.expand(config.trickle(sconfig)) == ConfigReader._load( + assert loader.expand(sconfig) == sconfig + assert loader.expand(loader.trickle(sconfig)) == ConfigReader._load( format="yaml", content=config_fixture.shell_command_before_session.expected ) -def test_trickle_relative_start_directory(config_fixture: "ConfigTestData"): - test_config = config.trickle(config_fixture.trickle.before) - assert test_config == config_fixture.trickle.expected +def test_trickle_relative_start_directory(config_fixture: "WorkspaceTestData"): + test_workspace = loader.trickle(config_fixture.trickle.before) + assert test_workspace == config_fixture.trickle.expected -def test_trickle_window_with_no_pane_config(): +def test_trickle_window_with_no_pane_workspace(): test_yaml = """ session_name: test_session windows: @@ -272,14 +199,14 @@ def test_trickle_window_with_no_pane_config(): - window_name: test_no_panes """ sconfig = ConfigReader._load(format="yaml", content=test_yaml) - config.validate_schema(sconfig) + validation.validate_schema(sconfig) - assert config.expand(config.trickle(sconfig))["windows"][1]["panes"][0] == { + assert loader.expand(loader.trickle(sconfig))["windows"][1]["panes"][0] == { "shell_command": [] } -def test_expands_blank_panes(config_fixture: "ConfigTestData"): +def test_expands_blank_panes(config_fixture: "WorkspaceTestData"): """Expand blank config into full form. Handle ``NoneType`` and 'blank':: @@ -306,13 +233,13 @@ def test_expands_blank_panes(config_fixture: "ConfigTestData"): 'shell_command': [''] """ - yaml_config_file = EXAMPLE_PATH / "blank-panes.yaml" - test_config = load_config(yaml_config_file) - assert config.expand(test_config) == config_fixture.expand_blank.expected + yaml_workspace_file = EXAMPLE_PATH / "blank-panes.yaml" + test_workspace = load_workspace(yaml_workspace_file) + assert loader.expand(test_workspace) == config_fixture.expand_blank.expected def test_no_session_name(): - yaml_config = """ + yaml_workspace = """ - window_name: editor panes: shell_command: @@ -325,27 +252,27 @@ def test_no_session_name(): - htop """ - sconfig = ConfigReader._load(format="yaml", content=yaml_config) + sconfig = ConfigReader._load(format="yaml", content=yaml_workspace) - with pytest.raises(exc.ConfigError) as excinfo: - config.validate_schema(sconfig) + with pytest.raises(exc.WorkspaceError) as excinfo: + validation.validate_schema(sconfig) assert excinfo.matches(r'requires "session_name"') def test_no_windows(): - yaml_config = """ + yaml_workspace = """ session_name: test session """ - sconfig = ConfigReader._load(format="yaml", content=yaml_config) + sconfig = ConfigReader._load(format="yaml", content=yaml_workspace) - with pytest.raises(exc.ConfigError) as excinfo: - config.validate_schema(sconfig) + with pytest.raises(exc.WorkspaceError) as excinfo: + validation.validate_schema(sconfig) assert excinfo.match(r'list of "windows"') def test_no_window_name(): - yaml_config = """ + yaml_workspace = """ session_name: test session windows: - window_name: editor @@ -359,17 +286,17 @@ def test_no_window_name(): - htop """ - sconfig = ConfigReader._load(format="yaml", content=yaml_config) + sconfig = ConfigReader._load(format="yaml", content=yaml_workspace) - with pytest.raises(exc.ConfigError) as excinfo: - config.validate_schema(sconfig) + with pytest.raises(exc.WorkspaceError) as excinfo: + validation.validate_schema(sconfig) assert excinfo.matches('missing "window_name"') def test_replaces_env_variables(monkeypatch): env_key = "TESTHEY92" env_val = "HEYO1" - yaml_config = """ + yaml_workspace = """ start_directory: {TEST_VAR}/test shell_command_before: {TEST_VAR}/test2 before_script: {TEST_VAR}/test3 @@ -393,10 +320,10 @@ def test_replaces_env_variables(monkeypatch): TEST_VAR="${%s}" % env_key ) - sconfig = ConfigReader._load(format="yaml", content=yaml_config) + sconfig = ConfigReader._load(format="yaml", content=yaml_workspace) monkeypatch.setenv(str(env_key), str(env_val)) - sconfig = config.expand(sconfig) + sconfig = loader.expand(sconfig) assert "%s/test" % env_val == sconfig["start_directory"] assert ( "%s/test2" % env_val @@ -410,7 +337,7 @@ def test_replaces_env_variables(monkeypatch): def test_plugins(): - yaml_config = """ + yaml_workspace = """ session_name: test session plugins: tmuxp-plugin-one.plugin.TestPluginOne windows: @@ -421,8 +348,8 @@ def test_plugins(): start_directory: /var/log """ - sconfig = ConfigReader._load(format="yaml", content=yaml_config) + sconfig = ConfigReader._load(format="yaml", content=yaml_workspace) - with pytest.raises(exc.ConfigError) as excinfo: - config.validate_schema(sconfig) + with pytest.raises(exc.WorkspaceError) as excinfo: + validation.validate_schema(sconfig) assert excinfo.matches("only supports list type") diff --git a/tests/workspace/test_finder.py b/tests/workspace/test_finder.py new file mode 100644 index 00000000000..1d64505a4a5 --- /dev/null +++ b/tests/workspace/test_finder.py @@ -0,0 +1,286 @@ +import argparse +import pathlib +import typing as t + +import pytest + +from tmuxp import cli +from tmuxp.cli.utils import tmuxp_echo +from tmuxp.workspace.finders import ( + find_workspace_file, + get_workspace_dir, + in_cwd, + in_dir, + is_pure_name, +) + +if t.TYPE_CHECKING: + import _pytest.capture + + +def test_in_dir_from_config_dir(tmp_path: pathlib.Path) -> None: + """config.in_dir() finds configs config dir.""" + + cli.startup(tmp_path) + yaml_config = tmp_path / "myconfig.yaml" + yaml_config.touch() + json_config = tmp_path / "myconfig.json" + json_config.touch() + configs_found = in_dir(tmp_path) + + assert len(configs_found) == 2 + + +def test_ignore_non_configs_from_current_dir(tmp_path: pathlib.Path) -> None: + """cli.in_dir() ignore non-config from config dir.""" + + cli.startup(tmp_path) + + junk_config = tmp_path / "myconfig.psd" + junk_config.touch() + conf = tmp_path / "watmyconfig.json" + conf.touch() + configs_found = in_dir(tmp_path) + assert len(configs_found) == 1 + + +def test_get_configs_cwd( + tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """config.in_cwd() find config in shell current working directory.""" + + confdir = tmp_path / "tmuxpconf2" + confdir.mkdir() + + monkeypatch.chdir(confdir) + config1 = open(".tmuxp.json", "w+b") + config1.close() + + configs_found = in_cwd() + assert len(configs_found) == 1 + assert ".tmuxp.json" in configs_found + + +@pytest.mark.parametrize( + "path,expect", + [ + (".", False), + ("./", False), + ("", False), + (".tmuxp.yaml", False), + ("../.tmuxp.yaml", False), + ("../", False), + ("/hello/world", False), + ("~/.tmuxp/hey", False), + ("~/work/c/tmux/", False), + ("~/work/c/tmux/.tmuxp.yaml", False), + ("myproject", True), + ], +) +def test_is_pure_name(path: str, expect: bool) -> None: + assert is_pure_name(path) == expect + + +def test_tmuxp_configdir_env_var( + tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("TMUXP_CONFIGDIR", str(tmp_path)) + + assert get_workspace_dir() == str(tmp_path) + + +def test_tmuxp_configdir_xdg_config_dir( + tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path)) + tmux_dir = tmp_path / "tmuxp" + tmux_dir.mkdir() + + assert get_workspace_dir() == str(tmux_dir) + + +@pytest.fixture +def homedir(tmp_path: pathlib.Path) -> pathlib.Path: + home = tmp_path / "home" + home.mkdir() + return home + + +@pytest.fixture +def configdir(homedir: pathlib.Path) -> pathlib.Path: + conf = homedir / ".tmuxp" + conf.mkdir() + return conf + + +@pytest.fixture +def projectdir(homedir: pathlib.Path) -> pathlib.Path: + proj = homedir / "work" / "project" + proj.mkdir(parents=True) + return proj + + +def test_resolve_dot( + tmp_path: pathlib.Path, + homedir: pathlib.Path, + configdir: pathlib.Path, + projectdir: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("HOME", str(homedir)) + monkeypatch.setenv("XDG_CONFIG_HOME", str(homedir / ".config")) + + tmuxp_conf_path = projectdir / ".tmuxp.yaml" + tmuxp_conf_path.touch() + user_config_name = "myconfig" + user_config = configdir / f"{user_config_name}.yaml" + user_config.touch() + + project_config = tmuxp_conf_path + + monkeypatch.chdir(projectdir) + + expect = str(project_config) + assert find_workspace_file(".") == expect + assert find_workspace_file("./") == expect + assert find_workspace_file("") == expect + assert find_workspace_file("../project") == expect + assert find_workspace_file("../project/") == expect + assert find_workspace_file(".tmuxp.yaml") == expect + assert find_workspace_file("../../.tmuxp/%s.yaml" % user_config_name) == str( + user_config + ) + assert find_workspace_file("myconfig") == str(user_config) + assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) + + with pytest.raises(Exception): + find_workspace_file(".tmuxp.json") + with pytest.raises(Exception): + find_workspace_file(".tmuxp.ini") + with pytest.raises(Exception): + find_workspace_file("../") + with pytest.raises(Exception): + find_workspace_file("mooooooo") + + monkeypatch.chdir(homedir) + + expect = str(project_config) + assert find_workspace_file("work/project") == expect + assert find_workspace_file("work/project/") == expect + assert find_workspace_file("./work/project") == expect + assert find_workspace_file("./work/project/") == expect + assert find_workspace_file(".tmuxp/%s.yaml" % user_config_name) == str(user_config) + assert find_workspace_file("./.tmuxp/%s.yaml" % user_config_name) == str( + user_config + ) + assert find_workspace_file("myconfig") == str(user_config) + assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) + + with pytest.raises(Exception): + find_workspace_file("") + with pytest.raises(Exception): + find_workspace_file(".") + with pytest.raises(Exception): + find_workspace_file(".tmuxp.yaml") + with pytest.raises(Exception): + find_workspace_file("../") + with pytest.raises(Exception): + find_workspace_file("mooooooo") + + monkeypatch.chdir(configdir) + + expect = str(project_config) + assert find_workspace_file("../work/project") == expect + assert find_workspace_file("../../home/work/project") == expect + assert find_workspace_file("../work/project/") == expect + assert find_workspace_file("%s.yaml" % user_config_name) == str(user_config) + assert find_workspace_file("./%s.yaml" % user_config_name) == str(user_config) + assert find_workspace_file("myconfig") == str(user_config) + assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) + + with pytest.raises(Exception): + find_workspace_file("") + with pytest.raises(Exception): + find_workspace_file(".") + with pytest.raises(Exception): + find_workspace_file(".tmuxp.yaml") + with pytest.raises(Exception): + find_workspace_file("../") + with pytest.raises(Exception): + find_workspace_file("mooooooo") + + monkeypatch.chdir(tmp_path) + + expect = str(project_config) + assert find_workspace_file("home/work/project") == expect + assert find_workspace_file("./home/work/project/") == expect + assert find_workspace_file("home/.tmuxp/%s.yaml" % user_config_name) == str( + user_config + ) + assert find_workspace_file("./home/.tmuxp/%s.yaml" % user_config_name) == str( + user_config + ) + assert find_workspace_file("myconfig") == str(user_config) + assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) + + with pytest.raises(Exception): + find_workspace_file("") + with pytest.raises(Exception): + find_workspace_file(".") + with pytest.raises(Exception): + find_workspace_file(".tmuxp.yaml") + with pytest.raises(Exception): + find_workspace_file("../") + with pytest.raises(Exception): + find_workspace_file("mooooooo") + + +def test_find_workspace_file_arg( + homedir: pathlib.Path, + configdir: pathlib.Path, + projectdir: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture, +) -> None: + parser = argparse.ArgumentParser() + parser.add_argument("workspace_file", type=str) + + def config_cmd(workspace_file: str) -> None: + tmuxp_echo(find_workspace_file(workspace_file, workspace_dir=configdir)) + + monkeypatch.setenv("HOME", str(homedir)) + tmuxp_config_path = projectdir / ".tmuxp.yaml" + tmuxp_config_path.touch() + user_config_name = "myconfig" + user_config = configdir / f"{user_config_name}.yaml" + user_config.touch() + + project_config = projectdir / ".tmuxp.yaml" + + def check_cmd(config_arg) -> "_pytest.capture.CaptureResult": + args = parser.parse_args([config_arg]) + config_cmd(workspace_file=args.workspace_file) + return capsys.readouterr() + + monkeypatch.chdir(projectdir) + expect = str(project_config) + assert expect in check_cmd(".").out + assert expect in check_cmd("./").out + assert expect in check_cmd("").out + assert expect in check_cmd("../project").out + assert expect in check_cmd("../project/").out + assert expect in check_cmd(".tmuxp.yaml").out + assert str(user_config) in check_cmd("../../.tmuxp/%s.yaml" % user_config_name).out + assert user_config.stem in check_cmd("myconfig").out + assert str(user_config) in check_cmd("~/.tmuxp/myconfig.yaml").out + + with pytest.raises(FileNotFoundError, match="file not found"): + assert "file not found" in check_cmd(".tmuxp.json").err + with pytest.raises(FileNotFoundError, match="file not found"): + assert "file not found" in check_cmd(".tmuxp.ini").err + with pytest.raises(FileNotFoundError, match="No tmuxp files found"): + assert "No tmuxp files found" in check_cmd("../").err + with pytest.raises( + FileNotFoundError, match="workspace-file not found in workspace dir" + ): + assert "workspace-file not found in workspace dir" in check_cmd("moo").err diff --git a/tests/workspace/test_freezer.py b/tests/workspace/test_freezer.py new file mode 100644 index 00000000000..7725caec2f7 --- /dev/null +++ b/tests/workspace/test_freezer.py @@ -0,0 +1,99 @@ +"""Tests for freezing tmux sessions with tmuxp.""" +import pathlib +import time +import typing + +from tmuxp.config_reader import ConfigReader +from tmuxp.workspace import freezer, validation +from tmuxp.workspace.builder import WorkspaceBuilder + +from ..fixtures import utils as test_utils + +if typing.TYPE_CHECKING: + from ..fixtures.structures import WorkspaceTestData + + +def test_freeze_config(session): + session_config = ConfigReader._from_file( + test_utils.get_workspace_file("workspace/freezer/sample_workspace.yaml") + ) + + builder = WorkspaceBuilder(sconf=session_config) + builder.build(session=session) + assert session == builder.session + + time.sleep(0.50) + + session = session + new_config = freezer.freeze(session) + + validation.validate_schema(new_config) + + # These should dump without an error + ConfigReader._dump(format="json", content=new_config) + ConfigReader._dump(format="yaml", content=new_config) + + # Inline configs should also dump without an error + compact_config = freezer.inline(new_config) + + ConfigReader._dump(format="json", content=compact_config) + ConfigReader._dump(format="yaml", content=compact_config) + + +"""Tests for :meth:`freezer.inline()`.""" + +ibefore_workspace = { # inline config + "session_name": "sample workspace", + "start_directory": "~", + "windows": [ + { + "shell_command": ["top"], + "window_name": "editor", + "panes": [{"shell_command": ["vim"]}, {"shell_command": ['cowsay "hey"']}], + "layout": "main-verticle", + }, + { + "window_name": "logging", + "panes": [{"shell_command": ["tail -F /var/log/syslog"]}], + }, + {"options": {"automatic-rename": True}, "panes": [{"shell_command": ["htop"]}]}, + ], +} + +iafter_workspace = { + "session_name": "sample workspace", + "start_directory": "~", + "windows": [ + { + "shell_command": "top", + "window_name": "editor", + "panes": ["vim", 'cowsay "hey"'], + "layout": "main-verticle", + }, + {"window_name": "logging", "panes": ["tail -F /var/log/syslog"]}, + {"options": {"automatic-rename": True}, "panes": ["htop"]}, + ], +} + + +def test_inline_workspace(): + """:meth:`freezer.inline()` shell commands list to string.""" + + test_workspace = freezer.inline(ibefore_workspace) + assert test_workspace == iafter_workspace + + +def test_export_yaml(tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData"): + yaml_workspace_file = tmp_path / "config.yaml" + + sample_workspace = freezer.inline( + config_fixture.sample_workspace.sample_workspace_dict + ) + configparser = ConfigReader(sample_workspace) + + yaml_workspace_data = configparser.dump("yaml", indent=2, default_flow_style=False) + + yaml_workspace_file.write_text(yaml_workspace_data, encoding="utf-8") + + new_workspace_data = ConfigReader._from_file(yaml_workspace_file) + assert config_fixture.sample_workspace.sample_workspace_dict == new_workspace_data diff --git a/tests/test_config_teamocil.py b/tests/workspace/test_import_teamocil.py similarity index 81% rename from tests/test_config_teamocil.py rename to tests/workspace/test_import_teamocil.py index d7f96a3499f..39da8d83da4 100644 --- a/tests/test_config_teamocil.py +++ b/tests/workspace/test_import_teamocil.py @@ -1,9 +1,10 @@ """Test for tmuxp teamocil configuration.""" import pytest -from tmuxp import config, config_reader +from tmuxp import config_reader +from tmuxp.workspace import importers, validation -from .fixtures import config_teamocil as fixtures +from ..fixtures import import_teamocil as fixtures @pytest.mark.parametrize( @@ -37,9 +38,9 @@ def test_config_to_dict(teamocil_yaml, teamocil_dict, tmuxp_dict): ) assert yaml_to_dict == teamocil_dict - assert config.import_teamocil(teamocil_dict) == tmuxp_dict + assert importers.import_teamocil(teamocil_dict) == tmuxp_dict - config.validate_schema(config.import_teamocil(teamocil_dict)) + validation.validate_schema(importers.import_teamocil(teamocil_dict)) @pytest.fixture(scope="module") @@ -73,6 +74,8 @@ def multisession_config(): ) def test_multisession_config(session_name, expected, multisession_config): # teamocil can fit multiple sessions in a config - assert config.import_teamocil(multisession_config[session_name]) == expected + assert importers.import_teamocil(multisession_config[session_name]) == expected - config.validate_schema(config.import_teamocil(multisession_config[session_name])) + validation.validate_schema( + importers.import_teamocil(multisession_config[session_name]) + ) diff --git a/tests/test_config_tmuxinator.py b/tests/workspace/test_import_tmuxinator.py similarity index 78% rename from tests/test_config_tmuxinator.py rename to tests/workspace/test_import_tmuxinator.py index 192852aa866..795c48da4d5 100644 --- a/tests/test_config_tmuxinator.py +++ b/tests/workspace/test_import_tmuxinator.py @@ -1,10 +1,10 @@ """Test for tmuxp tmuxinator configuration.""" import pytest -from tmuxp import config from tmuxp.config_reader import ConfigReader +from tmuxp.workspace import importers, validation -from .fixtures import config_tmuxinator as fixtures +from ..fixtures import import_tmuxinator as fixtures @pytest.mark.parametrize( @@ -31,6 +31,6 @@ def test_config_to_dict(tmuxinator_yaml, tmuxinator_dict, tmuxp_dict): yaml_to_dict = ConfigReader._load(format="yaml", content=tmuxinator_yaml) assert yaml_to_dict == tmuxinator_dict - assert config.import_tmuxinator(tmuxinator_dict) == tmuxp_dict + assert importers.import_tmuxinator(tmuxinator_dict) == tmuxp_dict - config.validate_schema(config.import_tmuxinator(tmuxinator_dict)) + validation.validate_schema(importers.import_tmuxinator(tmuxinator_dict))