1- import sys
1+ import re
2+ import shlex
23import subprocess
4+ import sys
35from pathlib import Path
46from rich .panel import Panel
57from rich .progress import Progress , SpinnerColumn , TextColumn
@@ -15,7 +17,113 @@ def _find_mkconcore_path():
1517 return None
1618
1719
18- def run_workflow (workflow_file , source , output , exec_type , auto_build , console ):
20+ def _yaml_quote (value ):
21+ return "'" + value .replace ("'" , "''" ) + "'"
22+
23+
24+ def _parse_docker_run_line (line ):
25+ text = line .strip ()
26+ if not text or text .startswith ("#" ):
27+ return None
28+
29+ if text .endswith ("&" ):
30+ text = text [:- 1 ].strip ()
31+
32+ try :
33+ tokens = shlex .split (text )
34+ except ValueError :
35+ return None
36+
37+ if "run" not in tokens :
38+ return None
39+
40+ run_index = tokens .index ("run" )
41+ args = tokens [run_index + 1 :]
42+
43+ container_name = None
44+ volumes = []
45+ image = None
46+
47+ i = 0
48+ while i < len (args ):
49+ token = args [i ]
50+ if token .startswith ("--name=" ):
51+ container_name = token .split ("=" , 1 )[1 ]
52+ elif token == "--name" and i + 1 < len (args ):
53+ container_name = args [i + 1 ]
54+ i += 1
55+ elif token in ("-v" , "--volume" ) and i + 1 < len (args ):
56+ volumes .append (args [i + 1 ])
57+ i += 1
58+ elif token .startswith ("--volume=" ):
59+ volumes .append (token .split ("=" , 1 )[1 ])
60+ elif token .startswith ("-" ):
61+ pass
62+ else :
63+ image = token
64+ break
65+ i += 1
66+
67+ if not container_name or not image :
68+ return None
69+
70+ return {
71+ "container_name" : container_name ,
72+ "volumes" : volumes ,
73+ "image" : image ,
74+ }
75+
76+
77+ def _write_docker_compose (output_path ):
78+ run_script = output_path / "run"
79+ if not run_script .exists ():
80+ return None
81+
82+ services = []
83+ for line in run_script .read_text (encoding = "utf-8" ).splitlines ():
84+ parsed = _parse_docker_run_line (line )
85+ if parsed is not None :
86+ services .append (parsed )
87+
88+ if not services :
89+ return None
90+
91+ compose_lines = ["services:" ]
92+
93+ for index , service in enumerate (services , start = 1 ):
94+ service_name = re .sub (r"[^A-Za-z0-9_.-]" , "-" , service ["container_name" ]).strip (
95+ "-."
96+ )
97+ if not service_name :
98+ service_name = f"service-{ index } "
99+ elif not service_name [0 ].isalnum ():
100+ service_name = f"service-{ service_name } "
101+
102+ compose_lines .append (f" { service_name } :" )
103+ compose_lines .append (f" image: { _yaml_quote (service ['image' ])} " )
104+ compose_lines .append (
105+ f" container_name: { _yaml_quote (service ['container_name' ])} "
106+ )
107+ if service ["volumes" ]:
108+ compose_lines .append (" volumes:" )
109+ for volume_spec in service ["volumes" ]:
110+ compose_lines .append (f" - { _yaml_quote (volume_spec )} " )
111+
112+ compose_lines .append ("" )
113+ compose_path = output_path / "docker-compose.yml"
114+ compose_path .write_text ("\n " .join (compose_lines ), encoding = "utf-8" )
115+ return compose_path
116+
117+
118+ def run_workflow (
119+ workflow_file ,
120+ source ,
121+ output ,
122+ exec_type ,
123+ auto_build ,
124+ console ,
125+ compose = False ,
126+ ):
19127 workflow_path = Path (workflow_file ).resolve ()
20128 source_path = Path (source ).resolve ()
21129 output_path = Path (output ).resolve ()
@@ -34,8 +142,13 @@ def run_workflow(workflow_file, source, output, exec_type, auto_build, console):
34142 console .print (f"[cyan]Source:[/cyan] { source_path } " )
35143 console .print (f"[cyan]Output:[/cyan] { output_path } " )
36144 console .print (f"[cyan]Type:[/cyan] { exec_type } " )
145+ if compose :
146+ console .print ("[cyan]Compose:[/cyan] enabled" )
37147 console .print ()
38148
149+ if compose and exec_type != "docker" :
150+ raise ValueError ("--compose can only be used with --type docker" )
151+
39152 mkconcore_path = _find_mkconcore_path ()
40153 if mkconcore_path is None :
41154 raise FileNotFoundError (
@@ -73,6 +186,18 @@ def run_workflow(workflow_file, source, output, exec_type, auto_build, console):
73186 console .print (
74187 f"[green]✓[/green] Workflow generated in [cyan]{ output_path } [/cyan]"
75188 )
189+
190+ if compose :
191+ compose_path = _write_docker_compose (output_path )
192+ if compose_path is not None :
193+ console .print (
194+ f"[green]✓[/green] Compose file written to [cyan]{ compose_path } [/cyan]"
195+ )
196+ else :
197+ console .print (
198+ "[yellow]Warning:[/yellow] Could not generate docker-compose.yml from run script"
199+ )
200+
76201 try :
77202 metadata_path = write_study_metadata (
78203 output_path ,
@@ -128,14 +253,18 @@ def run_workflow(workflow_file, source, output, exec_type, auto_build, console):
128253 if e .stderr :
129254 console .print (e .stderr )
130255
256+ run_command = "docker compose up" if compose else "./run"
257+ if exec_type == "windows" :
258+ run_command = "run.bat"
259+
131260 console .print ()
132261 console .print (
133262 Panel .fit (
134263 f"[green]✓[/green] Workflow ready!\n \n "
135264 f"To run your workflow:\n "
136265 f" cd { output_path } \n "
137266 f" { 'build.bat' if exec_type == 'windows' else './build' } \n "
138- f" { 'run.bat' if exec_type == 'windows' else './run' } " ,
267+ f" { run_command } " ,
139268 title = "Next Steps" ,
140269 border_style = "green" ,
141270 )
0 commit comments