vaibify 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vaibify/__init__.py +4 -0
- vaibify/__main__.py +5 -0
- vaibify/_version.py +24 -0
- vaibify/cli/__init__.py +1 -0
- vaibify/cli/commandBuild.py +133 -0
- vaibify/cli/commandCat.py +39 -0
- vaibify/cli/commandConfig.py +82 -0
- vaibify/cli/commandDestroy.py +72 -0
- vaibify/cli/commandInit.py +117 -0
- vaibify/cli/commandLs.py +59 -0
- vaibify/cli/commandPublish.py +21 -0
- vaibify/cli/commandRun.py +130 -0
- vaibify/cli/commandStart.py +69 -0
- vaibify/cli/commandStatus.py +79 -0
- vaibify/cli/commandTest.py +148 -0
- vaibify/cli/commandUtilsDocker.py +65 -0
- vaibify/cli/commandVerifyStep.py +90 -0
- vaibify/cli/commandWorkflow.py +122 -0
- vaibify/cli/configLoader.py +167 -0
- vaibify/cli/main.py +239 -0
- vaibify/config/__init__.py +51 -0
- vaibify/config/containerConfig.py +171 -0
- vaibify/config/projectConfig.py +400 -0
- vaibify/config/registryManager.py +288 -0
- vaibify/config/secretManager.py +174 -0
- vaibify/config/templateManager.py +105 -0
- vaibify/docker/__init__.py +20 -0
- vaibify/docker/containerManager.py +228 -0
- vaibify/docker/dockerConnection.py +170 -0
- vaibify/docker/fileTransfer.py +70 -0
- vaibify/docker/imageBuilder.py +232 -0
- vaibify/docker/volumeManager.py +70 -0
- vaibify/docker/x11Forwarding.py +98 -0
- vaibify/gui/__init__.py +0 -0
- vaibify/gui/commandUtilities.py +98 -0
- vaibify/gui/dependencyScanner.py +471 -0
- vaibify/gui/director.py +549 -0
- vaibify/gui/figureServer.py +23 -0
- vaibify/gui/pipelineRunner.py +1053 -0
- vaibify/gui/pipelineServer.py +2947 -0
- vaibify/gui/pipelineState.py +118 -0
- vaibify/gui/registryRoutes.py +468 -0
- vaibify/gui/resourceMonitor.py +88 -0
- vaibify/gui/setupServer.py +143 -0
- vaibify/gui/static/favicon.png +0 -0
- vaibify/gui/static/favicon.svg +4 -0
- vaibify/gui/static/index.html +544 -0
- vaibify/gui/static/scriptApplication.js +7219 -0
- vaibify/gui/static/scriptFigureViewer.js +994 -0
- vaibify/gui/static/scriptResourceMonitor.js +257 -0
- vaibify/gui/static/scriptSetupWizard.js +372 -0
- vaibify/gui/static/scriptStepEditor.js +286 -0
- vaibify/gui/static/scriptTerminal.js +570 -0
- vaibify/gui/static/scriptUtilities.js +45 -0
- vaibify/gui/static/setupWizard.html +206 -0
- vaibify/gui/static/styleMain.css +3031 -0
- vaibify/gui/static/styleSetupWizard.css +410 -0
- vaibify/gui/static/vaibify_logo_paleblue.png +0 -0
- vaibify/gui/static/vaibify_logo_paleblue_dark.png +0 -0
- vaibify/gui/syncDispatcher.py +693 -0
- vaibify/gui/terminalSession.py +85 -0
- vaibify/gui/testGenerator.py +3407 -0
- vaibify/gui/workflowManager.py +699 -0
- vaibify/install/__init__.py +0 -0
- vaibify/install/installVaibify.sh +371 -0
- vaibify/install/setupServer.py +300 -0
- vaibify/install/shellSetup.py +185 -0
- vaibify/install/uninstallVaibify.sh +209 -0
- vaibify/reproducibility/__init__.py +1 -0
- vaibify/reproducibility/dataArchiver.py +248 -0
- vaibify/reproducibility/githubWorkflow.py +155 -0
- vaibify/reproducibility/latexConnector.py +272 -0
- vaibify/reproducibility/overleafSync.py +307 -0
- vaibify/reproducibility/provenanceTracker.py +252 -0
- vaibify/reproducibility/zenodoClient.py +256 -0
- vaibify-0.0.0.dist-info/METADATA +171 -0
- vaibify-0.0.0.dist-info/RECORD +80 -0
- vaibify-0.0.0.dist-info/WHEEL +5 -0
- vaibify-0.0.0.dist-info/entry_points.txt +3 -0
- vaibify-0.0.0.dist-info/top_level.txt +1 -0
vaibify/__init__.py
ADDED
vaibify/__main__.py
ADDED
vaibify/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.0.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
vaibify/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Vaibify CLI package."""
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""CLI subcommand: vaibify build."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from .configLoader import fconfigResolveProject, fsDockerDir
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def fnBuildFromConfig(config, sDockerDir, bNoCache):
|
|
13
|
+
"""Invoke the Docker image builder with the loaded configuration.
|
|
14
|
+
|
|
15
|
+
Uses lazy import so the CLI remains usable without Docker installed.
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
from vaibify.docker.imageBuilder import fnBuildImage
|
|
19
|
+
except ImportError:
|
|
20
|
+
click.echo(
|
|
21
|
+
"Error: Docker support is not installed. "
|
|
22
|
+
"Install with: pip install vaibify[docker]"
|
|
23
|
+
)
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
fnPrepareBuildContext(config, sDockerDir)
|
|
26
|
+
fnBuildImage(config, sDockerDir, bNoCache=bNoCache)
|
|
27
|
+
fnPruneDanglingImages()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def fnPruneDanglingImages():
|
|
31
|
+
"""Remove dangling Docker images left by previous builds."""
|
|
32
|
+
try:
|
|
33
|
+
resultPrune = subprocess.run(
|
|
34
|
+
["docker", "image", "prune", "-f"],
|
|
35
|
+
capture_output=True, text=True, timeout=30,
|
|
36
|
+
)
|
|
37
|
+
if resultPrune.returncode == 0:
|
|
38
|
+
listLines = resultPrune.stdout.strip().split("\n")
|
|
39
|
+
sLastLine = listLines[-1] if listLines else ""
|
|
40
|
+
if "reclaimed" in sLastLine.lower():
|
|
41
|
+
click.echo(f"[vaib] {sLastLine}")
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def fnPrepareBuildContext(config, sDockerDir):
|
|
47
|
+
"""Generate all config-derived files in the Docker build context."""
|
|
48
|
+
from vaibify.config.containerConfig import (
|
|
49
|
+
fnGenerateContainerConf,
|
|
50
|
+
)
|
|
51
|
+
fnGenerateContainerConf(
|
|
52
|
+
config, os.path.join(sDockerDir, "container.conf")
|
|
53
|
+
)
|
|
54
|
+
fnWriteSystemPackages(config, sDockerDir)
|
|
55
|
+
fnWritePythonPackages(config, sDockerDir)
|
|
56
|
+
fnWritePipInstallFlags(config, sDockerDir)
|
|
57
|
+
fnWriteBinariesEnv(config, sDockerDir)
|
|
58
|
+
fnCopyDirectorScript(sDockerDir)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def fnWriteSystemPackages(config, sDockerDir):
|
|
62
|
+
"""Write listSystemPackages to system-packages.txt."""
|
|
63
|
+
sPath = os.path.join(sDockerDir, "system-packages.txt")
|
|
64
|
+
sContent = "\n".join(config.listSystemPackages) + "\n"
|
|
65
|
+
_fnWriteFile(sPath, sContent)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def fnWritePythonPackages(config, sDockerDir):
|
|
69
|
+
"""Write listPythonPackages to requirements.txt."""
|
|
70
|
+
sPath = os.path.join(sDockerDir, "requirements.txt")
|
|
71
|
+
sContent = "\n".join(config.listPythonPackages) + "\n"
|
|
72
|
+
_fnWriteFile(sPath, sContent)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def fnWritePipInstallFlags(config, sDockerDir):
|
|
76
|
+
"""Write sPipInstallFlags to pip-flags.txt."""
|
|
77
|
+
sPath = os.path.join(sDockerDir, "pip-flags.txt")
|
|
78
|
+
_fnWriteFile(sPath, config.sPipInstallFlags.strip() + "\n")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def fnWriteBinariesEnv(config, sDockerDir):
|
|
82
|
+
"""Write listBinaries to binaries.env as KEY=VALUE lines."""
|
|
83
|
+
sPath = os.path.join(sDockerDir, "binaries.env")
|
|
84
|
+
listLines = []
|
|
85
|
+
for dictBinary in config.listBinaries:
|
|
86
|
+
sName = dictBinary.get("name", "")
|
|
87
|
+
sBinPath = dictBinary.get("path", "")
|
|
88
|
+
if sName and sBinPath:
|
|
89
|
+
listLines.append(f"{sName}={sBinPath}")
|
|
90
|
+
sContent = "\n".join(listLines) + "\n"
|
|
91
|
+
_fnWriteFile(sPath, sContent)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def fnCopyDirectorScript(sDockerDir):
|
|
95
|
+
"""Copy director.py into the Docker build context."""
|
|
96
|
+
import shutil
|
|
97
|
+
import pathlib
|
|
98
|
+
sSourcePath = str(
|
|
99
|
+
pathlib.Path(__file__).resolve().parents[1]
|
|
100
|
+
/ "gui" / "director.py"
|
|
101
|
+
)
|
|
102
|
+
sDestPath = os.path.join(sDockerDir, "director.py")
|
|
103
|
+
shutil.copy2(sSourcePath, sDestPath)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _fnWriteFile(sPath, sContent):
|
|
107
|
+
"""Write string content to a file."""
|
|
108
|
+
with open(sPath, "w") as fileHandle:
|
|
109
|
+
fileHandle.write(sContent)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@click.command("build")
|
|
113
|
+
@click.option(
|
|
114
|
+
"--no-cache",
|
|
115
|
+
"bNoCache",
|
|
116
|
+
is_flag=True,
|
|
117
|
+
default=False,
|
|
118
|
+
help="Build the image without using Docker cache.",
|
|
119
|
+
)
|
|
120
|
+
@click.option(
|
|
121
|
+
"--project", "-p", "sProjectName", default=None,
|
|
122
|
+
help="Project name (omit if in a project directory "
|
|
123
|
+
"or only one project exists).",
|
|
124
|
+
)
|
|
125
|
+
def build(bNoCache, sProjectName):
|
|
126
|
+
"""Build the Vaibify Docker image from vaibify.yml."""
|
|
127
|
+
config = fconfigResolveProject(sProjectName)
|
|
128
|
+
sDockerDir = fsDockerDir()
|
|
129
|
+
click.echo(
|
|
130
|
+
f"Building image {config.sProjectName}:latest ..."
|
|
131
|
+
)
|
|
132
|
+
fnBuildFromConfig(config, sDockerDir, bNoCache)
|
|
133
|
+
click.echo("Build complete.")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""CLI subcommand: vaibify cat."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from .configLoader import fconfigResolveProject
|
|
8
|
+
from .commandUtilsDocker import (
|
|
9
|
+
fconnectionRequireDocker,
|
|
10
|
+
fsRequireRunningContainer,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _fsNormalizePath(sPath):
|
|
15
|
+
"""Prepend /workspace/ if the path is not absolute."""
|
|
16
|
+
if not sPath.startswith("/"):
|
|
17
|
+
return f"/workspace/{sPath}"
|
|
18
|
+
return sPath
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command("cat")
|
|
22
|
+
@click.option(
|
|
23
|
+
"--project", "-p", "sProjectName", default=None,
|
|
24
|
+
help="Project name.",
|
|
25
|
+
)
|
|
26
|
+
@click.argument("path")
|
|
27
|
+
def cat(sProjectName, path):
|
|
28
|
+
"""Print file contents from the container."""
|
|
29
|
+
configProject = fconfigResolveProject(sProjectName)
|
|
30
|
+
connectionDocker = fconnectionRequireDocker()
|
|
31
|
+
sContainerName = fsRequireRunningContainer(configProject)
|
|
32
|
+
sNormalized = _fsNormalizePath(path)
|
|
33
|
+
iExitCode, sOutput = connectionDocker.ftResultExecuteCommand(
|
|
34
|
+
sContainerName, f"cat {sNormalized}"
|
|
35
|
+
)
|
|
36
|
+
if iExitCode != 0:
|
|
37
|
+
click.echo(f"Error: {sOutput.strip()}")
|
|
38
|
+
sys.exit(2)
|
|
39
|
+
click.echo(sOutput, nl=False)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""CLI subcommand group: vaibify config."""
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from .configLoader import fconfigResolveProject, fsConfigPath
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def fnWriteYaml(dictData, sPath):
|
|
13
|
+
"""Write a dictionary to a YAML file."""
|
|
14
|
+
try:
|
|
15
|
+
with open(sPath, "w") as fileHandle:
|
|
16
|
+
yaml.dump(dictData, fileHandle, default_flow_style=False)
|
|
17
|
+
except OSError as error:
|
|
18
|
+
click.echo(f"Error writing {sPath}: {error}")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def fdictLoadYamlFile(sPath):
|
|
23
|
+
"""Load and return a YAML file as a dictionary."""
|
|
24
|
+
if not pathlib.Path(sPath).is_file():
|
|
25
|
+
click.echo(f"Error: File '{sPath}' not found.")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
try:
|
|
28
|
+
with open(sPath, "r") as fileHandle:
|
|
29
|
+
dictData = yaml.safe_load(fileHandle)
|
|
30
|
+
except yaml.YAMLError as error:
|
|
31
|
+
click.echo(f"Error parsing {sPath}: {error}")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
if dictData is None:
|
|
34
|
+
return {}
|
|
35
|
+
return dictData
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.group("config")
|
|
39
|
+
def config():
|
|
40
|
+
"""View and manage Vaibify configuration."""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@config.command("export")
|
|
45
|
+
@click.option(
|
|
46
|
+
"--project", "-p", "sProjectName", default=None,
|
|
47
|
+
help="Project name (omit if in a project directory "
|
|
48
|
+
"or only one project exists).",
|
|
49
|
+
)
|
|
50
|
+
@click.argument("sfilepath", metavar="FILE")
|
|
51
|
+
def configExport(sProjectName, sfilepath):
|
|
52
|
+
"""Export the current configuration to a YAML file."""
|
|
53
|
+
from vaibify.config.projectConfig import fnSaveToFile
|
|
54
|
+
configProject = fconfigResolveProject(sProjectName)
|
|
55
|
+
fnSaveToFile(configProject, sfilepath)
|
|
56
|
+
click.echo(f"Configuration exported to {sfilepath}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@config.command("import")
|
|
60
|
+
@click.argument("sfilepath", metavar="FILE")
|
|
61
|
+
def configImport(sfilepath):
|
|
62
|
+
"""Import configuration from a YAML file and overwrite the current config."""
|
|
63
|
+
dictNewConfig = fdictLoadYamlFile(sfilepath)
|
|
64
|
+
sConfigPath = fsConfigPath()
|
|
65
|
+
bExists = pathlib.Path(sConfigPath).is_file()
|
|
66
|
+
if bExists and not click.confirm(
|
|
67
|
+
"Overwrite existing vaibify.yml?"
|
|
68
|
+
):
|
|
69
|
+
click.echo("Aborted.")
|
|
70
|
+
return
|
|
71
|
+
fnWriteYaml(dictNewConfig, sConfigPath)
|
|
72
|
+
click.echo(f"Configuration imported from {sfilepath}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@config.command("edit")
|
|
76
|
+
def configEdit():
|
|
77
|
+
"""Open vaibify.yml in the default editor."""
|
|
78
|
+
sConfigPath = fsConfigPath()
|
|
79
|
+
if not pathlib.Path(sConfigPath).is_file():
|
|
80
|
+
click.echo("Error: vaibify.yml not found.")
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
click.edit(filename=sConfigPath)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""CLI subcommand: vaibify destroy."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from .configLoader import fbDockerAvailable, fconfigResolveProject
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def fnRemoveVolume(sVolumeName):
|
|
11
|
+
"""Remove a Docker volume by name."""
|
|
12
|
+
import docker
|
|
13
|
+
dockerClient = docker.from_env()
|
|
14
|
+
try:
|
|
15
|
+
volume = dockerClient.volumes.get(sVolumeName)
|
|
16
|
+
volume.remove(force=True)
|
|
17
|
+
click.echo(f"Removed volume: {sVolumeName}")
|
|
18
|
+
except docker.errors.NotFound:
|
|
19
|
+
click.echo(f"Volume '{sVolumeName}' does not exist.")
|
|
20
|
+
except docker.errors.APIError as error:
|
|
21
|
+
click.echo(f"Error removing volume: {error}")
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def fnRemoveImage(sFullName):
|
|
26
|
+
"""Remove a Docker image by full name."""
|
|
27
|
+
import docker
|
|
28
|
+
dockerClient = docker.from_env()
|
|
29
|
+
try:
|
|
30
|
+
dockerClient.images.remove(sFullName, force=True)
|
|
31
|
+
click.echo(f"Removed image: {sFullName}")
|
|
32
|
+
except docker.errors.ImageNotFound:
|
|
33
|
+
click.echo(f"Image '{sFullName}' does not exist.")
|
|
34
|
+
except docker.errors.APIError as error:
|
|
35
|
+
click.echo(f"Error removing image: {error}")
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def fnRequireDocker():
|
|
40
|
+
"""Exit with an error if the Docker Python SDK is not installed."""
|
|
41
|
+
if not fbDockerAvailable():
|
|
42
|
+
click.echo(
|
|
43
|
+
"Error: Docker support is not installed. "
|
|
44
|
+
"Install with: pip install vaibify[docker]"
|
|
45
|
+
)
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@click.command("destroy")
|
|
50
|
+
@click.option(
|
|
51
|
+
"--project", "-p", "sProjectName", default=None,
|
|
52
|
+
help="Project name (omit if in a project directory "
|
|
53
|
+
"or only one project exists).",
|
|
54
|
+
)
|
|
55
|
+
def destroy(sProjectName):
|
|
56
|
+
"""Remove the Vaibify workspace volume and optionally the image."""
|
|
57
|
+
fnRequireDocker()
|
|
58
|
+
config = fconfigResolveProject(sProjectName)
|
|
59
|
+
sVolumeName = f"{config.sProjectName}-workspace"
|
|
60
|
+
sFullName = f"{config.sProjectName}:latest"
|
|
61
|
+
if not click.confirm(
|
|
62
|
+
f"This will remove the workspace volume "
|
|
63
|
+
f"'{sVolumeName}'. Continue?"
|
|
64
|
+
):
|
|
65
|
+
click.echo("Aborted.")
|
|
66
|
+
return
|
|
67
|
+
fnRemoveVolume(sVolumeName)
|
|
68
|
+
if click.confirm(
|
|
69
|
+
"Also remove the Docker image?", default=False
|
|
70
|
+
):
|
|
71
|
+
fnRemoveImage(sFullName)
|
|
72
|
+
click.echo("Destroy complete.")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""CLI subcommand: vaibify init."""
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from .configLoader import fsConfigPath
|
|
10
|
+
|
|
11
|
+
from vaibify.config.registryManager import fnAddProject
|
|
12
|
+
|
|
13
|
+
_sTemplatesDir = "templates"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def flistAvailableTemplates():
|
|
17
|
+
"""Return a list of template directory names shipped with the package."""
|
|
18
|
+
sPackageRoot = str(pathlib.Path(__file__).resolve().parents[2])
|
|
19
|
+
sTemplatesPath = pathlib.Path(sPackageRoot) / _sTemplatesDir
|
|
20
|
+
if not sTemplatesPath.is_dir():
|
|
21
|
+
return []
|
|
22
|
+
return sorted(
|
|
23
|
+
d.name for d in sTemplatesPath.iterdir() if d.is_dir()
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def fsTemplatePath(sTemplateName):
|
|
28
|
+
"""Return the absolute path to a named template directory."""
|
|
29
|
+
sPackageRoot = str(pathlib.Path(__file__).resolve().parents[2])
|
|
30
|
+
return str(pathlib.Path(sPackageRoot) / _sTemplatesDir / sTemplateName)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def fnPrintAvailableTemplates():
|
|
34
|
+
"""Print available template names to stdout."""
|
|
35
|
+
listTemplates = flistAvailableTemplates()
|
|
36
|
+
if not listTemplates:
|
|
37
|
+
click.echo("No templates found.")
|
|
38
|
+
return
|
|
39
|
+
click.echo("Available templates:")
|
|
40
|
+
for sName in listTemplates:
|
|
41
|
+
click.echo(f" - {sName}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def fbConfigExists():
|
|
45
|
+
"""Return True if vaibify.yml exists in the current directory."""
|
|
46
|
+
return pathlib.Path(fsConfigPath()).is_file()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def fnCopyTemplate(sTemplateName):
|
|
50
|
+
"""Copy template files into the current directory."""
|
|
51
|
+
sSourcePath = fsTemplatePath(sTemplateName)
|
|
52
|
+
if not pathlib.Path(sSourcePath).is_dir():
|
|
53
|
+
click.echo(f"Error: Template '{sTemplateName}' not found.")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
fnCopyDirectoryContents(sSourcePath, str(pathlib.Path.cwd()))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def fnCopyDirectoryContents(sSourceDir, sDestDir):
|
|
59
|
+
"""Copy all files from sSourceDir into sDestDir."""
|
|
60
|
+
sSource = pathlib.Path(sSourceDir)
|
|
61
|
+
for sItem in sSource.iterdir():
|
|
62
|
+
sDest = pathlib.Path(sDestDir) / sItem.name
|
|
63
|
+
if sItem.is_dir():
|
|
64
|
+
shutil.copytree(str(sItem), str(sDest), dirs_exist_ok=True)
|
|
65
|
+
else:
|
|
66
|
+
shutil.copy2(str(sItem), str(sDest))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def fnWriteDefaultConfig(sTemplateName):
|
|
70
|
+
"""Write a minimal vaibify.yml using the ProjectConfig defaults."""
|
|
71
|
+
from vaibify.config.projectConfig import (
|
|
72
|
+
ProjectConfig,
|
|
73
|
+
fnSaveToFile,
|
|
74
|
+
)
|
|
75
|
+
sConfigPath = fsConfigPath()
|
|
76
|
+
config = ProjectConfig(sProjectName=sTemplateName)
|
|
77
|
+
fnSaveToFile(config, sConfigPath)
|
|
78
|
+
click.echo(f"Created {sConfigPath}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def fnRegisterProject():
|
|
82
|
+
"""Register the current directory in the global project registry."""
|
|
83
|
+
try:
|
|
84
|
+
fnAddProject(str(pathlib.Path.cwd()))
|
|
85
|
+
except (ValueError, FileNotFoundError):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@click.command("init")
|
|
90
|
+
@click.option(
|
|
91
|
+
"--template",
|
|
92
|
+
"sTemplateName",
|
|
93
|
+
default=None,
|
|
94
|
+
help="Name of the project template to use.",
|
|
95
|
+
)
|
|
96
|
+
@click.option(
|
|
97
|
+
"--force",
|
|
98
|
+
"bForce",
|
|
99
|
+
is_flag=True,
|
|
100
|
+
default=False,
|
|
101
|
+
help="Overwrite existing vaibify.yml.",
|
|
102
|
+
)
|
|
103
|
+
def init(sTemplateName, bForce):
|
|
104
|
+
"""Initialize a new Vaibify project in the current directory."""
|
|
105
|
+
if sTemplateName is None:
|
|
106
|
+
fnPrintAvailableTemplates()
|
|
107
|
+
return
|
|
108
|
+
if fbConfigExists() and not bForce:
|
|
109
|
+
click.echo(
|
|
110
|
+
"Error: vaibify.yml already exists. "
|
|
111
|
+
"Use --force to overwrite."
|
|
112
|
+
)
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
fnCopyTemplate(sTemplateName)
|
|
115
|
+
fnWriteDefaultConfig(sTemplateName)
|
|
116
|
+
fnRegisterProject()
|
|
117
|
+
click.echo(f"Initialized Vaibify project with '{sTemplateName}'.")
|
vaibify/cli/commandLs.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""CLI subcommand: vaibify ls."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from .configLoader import fconfigResolveProject
|
|
8
|
+
from .commandUtilsDocker import (
|
|
9
|
+
fconnectionRequireDocker,
|
|
10
|
+
fsRequireRunningContainer,
|
|
11
|
+
fnPrintJson,
|
|
12
|
+
fbShouldOutputJson,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _fsNormalizePath(sPath):
|
|
17
|
+
"""Prepend /workspace/ if the path is not absolute."""
|
|
18
|
+
if sPath and not sPath.startswith("/"):
|
|
19
|
+
return f"/workspace/{sPath}"
|
|
20
|
+
return sPath
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _flistParseDirectoryListing(sOutput):
|
|
24
|
+
"""Parse ls output into a list of filename strings."""
|
|
25
|
+
listFiles = []
|
|
26
|
+
for sLine in sOutput.splitlines():
|
|
27
|
+
sStripped = sLine.strip()
|
|
28
|
+
if sStripped:
|
|
29
|
+
listFiles.append(sStripped)
|
|
30
|
+
return listFiles
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.command("ls")
|
|
34
|
+
@click.option(
|
|
35
|
+
"--project", "-p", "sProjectName", default=None,
|
|
36
|
+
help="Project name.",
|
|
37
|
+
)
|
|
38
|
+
@click.option(
|
|
39
|
+
"--json", "bJson", is_flag=True, default=False,
|
|
40
|
+
help="Output in JSON format.",
|
|
41
|
+
)
|
|
42
|
+
@click.argument("path", default="/workspace")
|
|
43
|
+
def ls(sProjectName, bJson, path):
|
|
44
|
+
"""List files in the container workspace."""
|
|
45
|
+
configProject = fconfigResolveProject(sProjectName)
|
|
46
|
+
connectionDocker = fconnectionRequireDocker()
|
|
47
|
+
sContainerName = fsRequireRunningContainer(configProject)
|
|
48
|
+
sNormalized = _fsNormalizePath(path)
|
|
49
|
+
iExitCode, sOutput = connectionDocker.ftResultExecuteCommand(
|
|
50
|
+
sContainerName, f"ls -1 {sNormalized}"
|
|
51
|
+
)
|
|
52
|
+
if iExitCode != 0:
|
|
53
|
+
click.echo(f"Error: {sOutput.strip()}")
|
|
54
|
+
sys.exit(2)
|
|
55
|
+
if fbShouldOutputJson(bJson):
|
|
56
|
+
listFiles = _flistParseDirectoryListing(sOutput)
|
|
57
|
+
fnPrintJson({"sPath": sNormalized, "listFiles": listFiles})
|
|
58
|
+
else:
|
|
59
|
+
click.echo(sOutput.rstrip())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""CLI subcommand group: vaibify publish (stub)."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group("publish")
|
|
7
|
+
def publish():
|
|
8
|
+
"""Publish reproducible archives and workflows."""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@publish.command("archive")
|
|
13
|
+
def publishArchive():
|
|
14
|
+
"""Create a reproducible archive of the current project."""
|
|
15
|
+
click.echo("Not yet implemented.")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@publish.command("workflow")
|
|
19
|
+
def publishWorkflow():
|
|
20
|
+
"""Publish a workflow definition for the current project."""
|
|
21
|
+
click.echo("Not yet implemented.")
|