Skip to content

Commit

Permalink
Merge pull request #93 from Wytamma/snk-edit
Browse files Browse the repository at this point in the history
Add snk edit command
  • Loading branch information
Wytamma authored Nov 16, 2024
2 parents c852343 + 40a3360 commit 023a309
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 6 deletions.
18 changes: 18 additions & 0 deletions docs/managing_workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,24 @@ Proceed (Y/n)? y
!!! Note
Use `--force` to force uninstall without asking.

## Editing workflow CLI configuration

The `snk edit` command is used to edit the CLI configuration of a installed workflow. This will open the configuration file in the default text editor.

```bash
snk edit workflow
```

Use the `--path` flag to print the path to the configuration file instead of opening it.
```bash
snk edit --path workflow
```

!!! note

The configuration file is a YAML file that contains the CLI configuration for the workflow. For more details on the CLI configuration file read the [snk config file docs](https://snk.wytamma.com/snk_config_file).


## Ejecting workflows

The `cp -r $(workflow-name -p) workflow-name` command is used to eject the workflow from the package. This will copy the workflow files to the current working directory. This will allow you to modify the workflow and run it with the standard `snakemake` command.
Expand Down
12 changes: 12 additions & 0 deletions docs/snk_config_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ title: Snk Config File

The `snk.yaml` file serves as the main interface for configuring the Snk workflow CLI. Users can tailor the workflow's settings, specify required resources, and control the appearance of the command line interface by setting various options in the `snk.yaml` file.

## Modifying the `snk.yaml` File

The `snk.yaml` file should be located in the root directory of the Snakemake workflow. It is used to configure the Snk CLI and provide additional information about the workflow. The `snk.yaml` file is written in YAML format and can be edited with any text editor.

For convenience, you can use the `snk edit [WORKFLOW_NAME]` command to open the `snk.yaml` file in your default text editor. This command will create a new `snk.yaml` file if one does not already exist.

```bash
snk edit workflow
```

```bash

## Available Configuration Options

The following options are available for configuration in `snk.yaml`:
Expand Down
38 changes: 33 additions & 5 deletions snk/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .__about__ import __version__
from .errors import WorkflowExistsError, WorkflowNotFoundError
from .nest import Nest
from .utils import open_text_editor

app = typer.Typer()

Expand Down Expand Up @@ -242,6 +243,38 @@ def list(
console = Console()
console.print(table)

@app.command()
def edit(
ctx: typer.Context,
workflow_name: str = typer.Argument(
..., help="Name of the workflow to configure."
),
path: bool = typer.Option(
False, "--path", "-p", help="Show the path to the snk.yaml file."
),
):
"""
Access the snk.yaml configuration file for a workflow.
"""
nest = Nest(snk_home=ctx.obj.snk_home, bin_dir=ctx.obj.snk_bin)
try:
workflows = nest.workflows
except FileNotFoundError:
workflows = []
workflow = next((w for w in workflows if w.name == workflow_name), None)
if not workflow:
typer.secho(f"Workflow '{workflow_name}' not found!", fg="red", err=True)
raise typer.Exit(1)
snk_config = SnkConfig.from_workflow_dir(workflow.path, create_if_not_exists=True)
snk_config.save()
if path:
typer.echo(snk_config._snk_config_path)
else:
try:
open_text_editor(snk_config._snk_config_path)
except Exception as e:
typer.secho(str(e), fg="red", err=True)
raise typer.Exit(1)

# @app.command()
# def run(
Expand All @@ -259,11 +292,6 @@ def list(
# """Generate annotations defaults from config file"""
# raise NotImplementedError

# @app.command()
# def create(name: str):
# """Create a default project that can be installed with snk"""
# raise NotImplementedError

# @app.command()
# def update():
# """
Expand Down
33 changes: 33 additions & 0 deletions snk/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
def open_text_editor(file_path):
"""
Opens the system's default text editor to edit the specified file.
Parameters:
file_path (str): The path to the file to be edited.
"""
import os
import platform
import subprocess

if platform.system() == "Windows":
os.startfile(file_path)
elif platform.system() == "Darwin": # macOS
subprocess.call(("open", file_path))
else: # Linux and other Unix-like systems
editors = ["nano", "vim", "vi"]
editor = None
for e in editors:
if (
subprocess.call(
["which", e], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
== 0
):
editor = e
break
if editor:
subprocess.call([editor, file_path])
else:
raise Exception(
"No suitable text editor found. Please install nano or vim."
)
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def basic_runner(tmp_path_factory):
return runner


@pytest.fixture(scope="session")
@pytest.fixture(scope="module")
def local_workflow(tmp_path_factory):
path = Path(tmp_path_factory.mktemp("snk"))
(path / "home").mkdir()
Expand Down
7 changes: 7 additions & 0 deletions tests/test_snk.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ def test_snk_list(local_workflow: Workflow):
assert "workflow" in result.stdout
assert "editable" in result.stdout

def test_config_path_cli(local_workflow: Workflow):
snk_home = local_workflow.path.parent.parent
bin_dir = local_workflow.path.parent.parent.parent / "bin"
result = runner.invoke(app, ["--home", snk_home, "--bin", bin_dir, "edit", "--path", "workflow"])
assert result.exit_code == 0
assert "/workflows/workflow/snk.yaml" in result.stdout

def test_snk_uninstall(local_workflow: Workflow):
snk_home = local_workflow.path.parent.parent
Expand Down Expand Up @@ -87,3 +93,4 @@ def test_import_create_cli(capsys):
pass
captured = capsys.readouterr()
assert "Usage" in captured.out

105 changes: 105 additions & 0 deletions tests/test_snk_edit_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import os # noqa: F401
import platform # noqa: F401
import subprocess
from pathlib import Path
from unittest.mock import call, patch

import pytest
import typer # noqa: F401
from snk_cli.workflow import Workflow
from typer.testing import CliRunner

from snk.main import app

runner = CliRunner()


@pytest.fixture
def mock_platform_system():
with patch('platform.system') as mock:
yield mock


@pytest.fixture
def mock_os_startfile():
# Only patch os.startfile if the platform is Windows
if hasattr(os, 'startfile'):
with patch('os.startfile') as mock:
yield mock
else:
yield None


@pytest.fixture
def mock_subprocess_call():
with patch('subprocess.call') as mock:
yield mock


@pytest.mark.skipif(platform.system() != 'Windows', reason="Requires Windows")
def test_open_text_editor_windows(
mock_platform_system, mock_os_startfile, local_workflow: Workflow
):
mock_platform_system.return_value = 'Windows'
file_path = local_workflow.path / "snk.yaml"

snk_home = local_workflow.path.parent.parent
bin_dir = local_workflow.path.parent.parent.parent / "bin"
res = runner.invoke(app, ["--home", snk_home, "--bin", bin_dir, "edit", "workflow"])

assert res.exit_code == 0, res.stderr
if mock_os_startfile:
mock_os_startfile.assert_called_once_with(file_path)


def test_open_text_editor_mac(
mock_platform_system, mock_subprocess_call, local_workflow: Workflow
):
mock_platform_system.return_value = 'Darwin'
file_path = local_workflow.path / "snk.yaml"

snk_home = local_workflow.path.parent.parent
bin_dir = local_workflow.path.parent.parent.parent / "bin"
res = runner.invoke(app, ["--home", snk_home, "--bin", bin_dir, "edit", "workflow"])

assert res.exit_code == 0, res.stderr
mock_subprocess_call.assert_called_once_with(('open', file_path))


def test_open_text_editor_linux(
mock_platform_system, mock_subprocess_call, local_workflow: Workflow
):
mock_platform_system.return_value = 'Linux'
file_path = local_workflow.path / "snk.yaml"

with patch('subprocess.call') as mock_call:
mock_call.side_effect = [1, 1, 0, 0] # Mocking 'which' command results: nano not found, vim not found, vi found

snk_home = local_workflow.path.parent.parent
bin_dir = local_workflow.path.parent.parent.parent / "bin"
res = runner.invoke(app, ["--home", snk_home, "--bin", bin_dir, "edit", "workflow"])
assert res.exit_code == 0, res.stderr
mock_call.assert_has_calls([
call(['which', 'nano'], stdout=subprocess.PIPE, stderr=subprocess.PIPE),
call(['which', 'vim'], stdout=subprocess.PIPE, stderr=subprocess.PIPE),
call(['which', 'vi'], stdout=subprocess.PIPE, stderr=subprocess.PIPE),
call(['vi', file_path])
])


def test_open_text_editor_no_editor_found(
mock_platform_system, mock_subprocess_call, local_workflow: Workflow
):
mock_platform_system.return_value = 'Linux'

with patch('subprocess.call') as mock_call:
mock_call.side_effect = [1, 1, 1] # Mocking 'which' command results: none of the editors found

with patch('typer.secho') as mock_print:
snk_home = local_workflow.path.parent.parent
bin_dir = local_workflow.path.parent.parent.parent / "bin"
res = runner.invoke(app, ["--home", snk_home, "--bin", bin_dir, "edit", "workflow"])
assert res.exit_code == 1, res.stderr
mock_print.assert_called_once_with(
"No suitable text editor found. Please install nano or vim.", fg='red', err=True
)

0 comments on commit 023a309

Please sign in to comment.