Skip to content

Commit

Permalink
Merge pull request #853 from douglasjacobsen/manage-includes
Browse files Browse the repository at this point in the history
Front-end commands for managing workspace includes.
  • Loading branch information
rfbgo authored Jan 31, 2025
2 parents 6377bd7 + 8ff3d33 commit e202a2c
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 2 deletions.
58 changes: 57 additions & 1 deletion lib/ramble/ramble/cmd/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import ramble.software_environments
import ramble.util.colors as rucolor
from ramble.util.logger import logger
from ramble.namespace import namespace


description = "manage experiment workspaces"
Expand All @@ -59,7 +60,7 @@
"manage",
]

manage_commands = ["experiments", "software"]
manage_commands = ["experiments", "software", "includes"]


def workspace_activate_setup_parser(subparser):
Expand Down Expand Up @@ -1315,6 +1316,61 @@ def workspace_manage_software(args):
ws.print_config()


def workspace_manage_includes_setup_parser(subparser):
"""manage workspace includes"""
actions = subparser.add_mutually_exclusive_group()
actions.add_argument(
"--list", "-l", action="store_true", help="whether to print existing includes"
)

actions.add_argument(
"--remove",
"-r",
dest="remove_pattern",
metavar="PATTERN",
help="whether to remove an existing include by name / pattern",
)

actions.add_argument(
"--remove-index",
dest="remove_index",
metavar="IDX",
help="whether to remove an existing include by index",
)

actions.add_argument(
"--add", "-a", dest="add_include", metavar="PATH", help="whether to add a new include"
)


def workspace_manage_includes(args):
"""Execute workspace manage include command"""

ws = ramble.cmd.require_active_workspace(cmd_name="workspace manage includes")

if args.list:
with ws.read_transaction():
workspace_dict = ws._get_workspace_dict()
if namespace.include in workspace_dict[namespace.ramble]:
includes = workspace_dict[namespace.ramble][namespace.include]
if includes:
logger.msg("Workspace includes:")
for idx, include in enumerate(includes):
logger.msg(f"{idx}: {include}")
return
logger.msg("Workspace contains no includes.")
elif args.remove_index:
remove_index = int(args.remove_index)
with ws.write_transaction():
ws.remove_include(index=remove_index)
elif args.remove_pattern:
with ws.write_transaction():
ws.remove_include(pattern=args.remove_pattern)
elif args.add_include:
with ws.write_transaction():
ws.add_include(args.add_include)


def workspace_generate_config_setup_parser(subparser):
"""generate current workspace config"""
workspace_manage_experiments_setup_parser(subparser)
Expand Down
1 change: 1 addition & 0 deletions lib/ramble/ramble/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ class namespace:
workflow_manager = "workflow_manager"

metadata = "metadata"
include = "include"
141 changes: 141 additions & 0 deletions lib/ramble/ramble/test/end_to_end/workspace_includes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Copyright 2022-2025 The Ramble Authors
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.

import os

import pytest

import ramble.workspace
import ramble.config
import ramble.software_environments
from ramble.main import RambleCommand


# everything here uses the mock_workspace_path
pytestmark = pytest.mark.usefixtures(
"mutable_config",
"mutable_mock_workspace_path",
)

workspace = RambleCommand("workspace")


def test_workspace_add_includes(request):
workspace_name = request.node.name
ws = ramble.workspace.create(workspace_name)
global_args = ["-w", workspace_name]

ws.write()

output = workspace("manage", "includes", "--list", global_args=global_args)

assert "Workspace contains no includes." in output

workspace(
"manage",
"includes",
"--add",
"$workspace_configs/auxiliary_software_files",
global_args=global_args,
)

ws._re_read()

config_path = os.path.join(ws.config_dir, ramble.workspace.config_file_name)

with open(config_path) as f:
data = f.read()
assert "- $workspace_configs/auxiliary_software_files" in data


def test_workspace_remove_includes_index(request):
workspace_name = request.node.name
ws = ramble.workspace.create(workspace_name)
global_args = ["-w", workspace_name]

ws.write()

output = workspace("manage", "includes", "--list", global_args=global_args)

assert "Workspace contains no includes." in output

workspace(
"manage",
"includes",
"--add",
"$workspace_configs/auxiliary_software_files",
global_args=global_args,
)

ws._re_read()

config_path = os.path.join(ws.config_dir, ramble.workspace.config_file_name)

output = workspace("manage", "includes", "--list", global_args=global_args)

assert "0: $workspace_configs/auxiliary_software_files" in output

with open(config_path) as f:
data = f.read()
assert "- $workspace_configs/auxiliary_software_files" in data

workspace("manage", "includes", "--remove-index", "0", global_args=global_args)

ws._re_read()

output = workspace("manage", "includes", "--list", global_args=global_args)

assert "Workspace contains no includes." in output

with open(config_path) as f:
data = f.read()
assert "- $workspace_configs/auxiliary_software_files" not in data


def test_workspace_remove_includes_pattern(request):
workspace_name = request.node.name
ws = ramble.workspace.create(workspace_name)
global_args = ["-w", workspace_name]

ws.write()

output = workspace("manage", "includes", "--list", global_args=global_args)

assert "Workspace contains no includes." in output

workspace(
"manage",
"includes",
"--add",
"$workspace_configs/auxiliary_software_files",
global_args=global_args,
)

ws._re_read()

config_path = os.path.join(ws.config_dir, ramble.workspace.config_file_name)

output = workspace("manage", "includes", "--list", global_args=global_args)

assert "0: $workspace_configs/auxiliary_software_files" in output

with open(config_path) as f:
data = f.read()
assert "- $workspace_configs/auxiliary_software_files" in data

workspace("manage", "includes", "--remove", "*aux*", global_args=global_args)

ws._re_read()

output = workspace("manage", "includes", "--list", global_args=global_args)

assert "Workspace contains no includes." in output

with open(config_path) as f:
data = f.read()
assert "- $workspace_configs/auxiliary_software_files" not in data
46 changes: 46 additions & 0 deletions lib/ramble/ramble/workspace/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,52 @@ def workspace_paths(self):

return self._workspace_path_replacements

def add_include(self, new_include):
"""Add a new include to this workspace"""

if namespace.include not in self.config_sections["workspace"]["yaml"][namespace.ramble]:
self.config_sections["workspace"]["yaml"][namespace.ramble][namespace.include] = []
includes = self.config_sections["workspace"]["yaml"][namespace.ramble][namespace.include]
includes.append(new_include)
self._write_config(config_section)

def remove_include(self, index=None, pattern=None):
"""Remove one or more includes from this workspace.
Args:
index (optional): Numerical position of include to remove
pattern (optional): String or pattern of include to remove.
Removes all matching includes.
"""

if namespace.include not in self.config_sections["workspace"]["yaml"][namespace.ramble]:
return

includes = self.config_sections["workspace"]["yaml"][namespace.ramble][namespace.include]
changed = False

if index is not None:
if index < 0 or index >= len(includes):
logger.die(
f"Requested index {index} " "is outside of the range of existing includes."
)
includes.pop(index)
changed = True

if pattern is not None:
remove_indices = []
for idx, include in enumerate(includes):
if fnmatch.fnmatch(include, pattern):
remove_indices.append(idx)

for remove_idx in reversed(remove_indices):
if remove_idx >= 0 and remove_idx < len(includes):
includes.pop(remove_idx)
changed = True

if changed:
self._write_config(config_section)

def included_config_scopes(self):
"""List of included configuration scopes from the environment.
Expand Down
6 changes: 5 additions & 1 deletion share/ramble/ramble-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ _ramble_workspace_manage() {
then
RAMBLE_COMPREPLY="-h --help"
else
RAMBLE_COMPREPLY="experiments software"
RAMBLE_COMPREPLY="experiments software includes"
fi
}

Expand All @@ -763,3 +763,7 @@ _ramble_workspace_manage_experiments() {
_ramble_workspace_manage_software() {
RAMBLE_COMPREPLY="-h --help --environment-name --env --environment-packages --external-env --package-name --pkg --package-spec --pkg-spec --spec --compiler-package --compiler-pkg --compiler --compiler-spec --package-manager-prefix --prefix --remove --delete --overwrite -o --dry-run --print"
}

_ramble_workspace_manage_includes() {
RAMBLE_COMPREPLY="-h --help --list -l --remove -r --remove-index --add -a"
}

0 comments on commit e202a2c

Please sign in to comment.