From bd7acd91ca6e16554c38287d140fdce2543f99b7 Mon Sep 17 00:00:00 2001
From: Christopher Spinrath <christopher.spinrath@univ-grenoble-alpes.fr>
Date: Wed, 30 Apr 2025 16:53:23 +0200
Subject: [PATCH] Add experimental exp2sh converter

The converter is not feature complete!
---
 exp2sh.py  | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 run-exp.py | 20 +++++++++++
 2 files changed, 118 insertions(+)
 create mode 100755 exp2sh.py

diff --git a/exp2sh.py b/exp2sh.py
new file mode 100755
index 0000000..d9a3164
--- /dev/null
+++ b/exp2sh.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+import importlib
+import pathlib
+
+import click
+import structlog
+
+exprunner = importlib.import_module('run-exp')
+
+
+# logger = structlog.get_logger()
+def drop_log_msgs(*_1, **_2):
+    raise structlog.DropEvent
+
+
+structlog.configure(processors = [drop_log_msgs])
+
+
+def print_stmt(stmt, indent):
+    print(f"{"  " * indent}{stmt}", flush = True)
+
+
+def translate_var_init(variable, variable_map, indent):
+    sh_var_name = variable_map[variable.name].strip("$")  # remove leading $
+
+    if isinstance(variable, exprunner.SimpleVariable):
+        values = variable.evaluate(variable_map)
+        if len(values) == 1:
+            print_stmt(f"{sh_var_name}=\"{values[0]}\"", indent)
+            return False
+        else:
+            print()
+            print_stmt(f"for {sh_var_name} in {" ".join(f"\"{v}\"" for v in values)}", indent)
+            print_stmt("do", indent)
+            return True
+
+    if isinstance(variable, exprunner.RangeVariable):
+        print()
+        print_stmt(f"for {sh_var_name} in $(seq {variable.lower} {variable.step} {variable.upper})", indent)
+        print_stmt("do", indent)
+        return True
+
+    if isinstance(variable, exprunner.FileVariable):
+        directory_str = variable.directory_str.format(**variable_map)
+        print()
+        print_stmt(f"for {sh_var_name} in {directory_str}/*", indent)
+        print_stmt("do", indent)
+        if variable.basename_only:
+            print_stmt(f"{sh_var_name}=$(basename ${sh_var_name})", indent + 1)
+        return True
+
+    return False
+
+
+@click.command()
+@click.argument("config_file", type = click.Path(
+    exists = True, file_okay = True, dir_okay = False, readable = True, path_type = pathlib.Path))
+@click.option("--global-timeout", type = click.IntRange(1), default = 7200)
+@click.option("--dry-run", is_flag = True)
+def main(config_file, global_timeout, dry_run):
+    c = exprunner.Config.from_file(config_file)
+
+    print("#!/usr/bin/env bash")
+    print()
+
+    print("# WARNING: This script ignoes 'if-(not)-variable' conditions!")
+    print()
+
+    # we precompute all variable values to be able to abort early in case of a problem,
+    # for instance, if a file does not exist
+    variable_map = {v.name: f"${v.name.upper().replace('-', '_')}" for v in c.variables}
+    indent = 0
+
+    num_for_loops = 0
+    for variable in c.variables:
+        if translate_var_init(variable, variable_map, indent):
+            num_for_loops += 1
+            indent += 1
+
+    print()
+    print_stmt(f"for EXP_RUNNER_REPETITION in $(seq 0 {c.repetitions})", indent)
+    print_stmt("do", indent)
+
+    for task in c.tasks:
+        print_stmt(f"# {task.name}", indent + 1)
+        print("  " * (indent + 1), flush = True, end = '')
+        if dry_run:
+            print("echo ", flush = True, end = '')
+        task.run({k: f"\\{v}" for k, v in variable_map.items()}, global_timeout, True)
+
+    for _ in range(num_for_loops + 1):
+        print_stmt("done", indent)
+        indent -= 1
+
+
+if __name__ == "__main__":
+    main()
diff --git a/run-exp.py b/run-exp.py
index 9b1a1b0..4d62126 100755
--- a/run-exp.py
+++ b/run-exp.py
@@ -54,6 +54,18 @@ class RangeVariable(SimpleVariable):
     def __init__(self, name, lower, upper, step):
         super().__init__(name, list(range(lower, upper + 1, step)))
 
+    @property
+    def lower(self):
+        return self._lower
+
+    @property
+    def upper(self):
+        return self._upper
+
+    @property
+    def step(self):
+        return self._step
+
 
 class FileVariable(Variable):
     def __init__(self, name, directory_str, basename_only):
@@ -61,6 +73,14 @@ class FileVariable(Variable):
         self._directory_str = directory_str
         self._basename_only = basename_only
 
+    @property
+    def directory_str(self):
+        return self._directory_str
+
+    @property
+    def basename_only(self):
+        return self._basename_only
+
     def evaluate(self, variable_mapping):
         directory = pathlib.Path(self._directory_str.format(**variable_mapping))
         directory = directory.expanduser()
-- 
GitLab