33
44from .metadata import write_study_metadata
55
6+ # ---------------------------------------------------------------------------
7+ # GraphML templates
8+ # ---------------------------------------------------------------------------
9+
10+ GRAPHML_HEADER = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
11+ <graphml xmlns="http://graphml.graphdrawing.org/xmlns"
12+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
13+ xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd"
14+ xmlns:y="http://www.yworks.com/xml/graphml">
15+ <key for="node" id="d6" yfiles.type="nodegraphics"/>
16+ <key for="edge" id="d10" yfiles.type="edgegraphics"/>
17+ <graph edgedefault="directed" id="1" projectName="{project_name}">
18+ {nodes}
19+ </graph>
20+ </graphml>
21+ """
22+
23+ GRAPHML_NODE = """ <node id="n{idx}">
24+ <data key="d6">
25+ <y:ShapeNode>
26+ <y:Geometry height="50" width="150" x="100" y="{y}"/>
27+ <y:Fill color="{color}" opacity="1"/>
28+ <y:BorderStyle color="#000000" width="1"/>
29+ <y:NodeLabel>N{idx}:{filename}</y:NodeLabel>
30+ <y:Shape type="rectangle"/>
31+ </y:ShapeNode>
32+ </data>
33+ </node>"""
34+
35+ # Single-node fallback used by non-interactive init
636SAMPLE_GRAPHML = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
737<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd" xmlns:y="http://www.yworks.com/xml/graphml">
838 <key for="node" id="d6" yfiles.type="nodegraphics"/>
2353</graphml>
2454"""
2555
26- SAMPLE_PYTHON = """import concore
27-
28- concore.default_maxtime(100)
29- concore.delay = 0.02
30-
31- init_simtime_val = "[0.0, 0.0]"
32- val = concore.initval(init_simtime_val)
56+ # ---------------------------------------------------------------------------
57+ # Per-language metadata: label, filename, node colour, source stub
58+ # ---------------------------------------------------------------------------
3359
34- while(concore.simtime<concore.maxtime):
35- while concore.unchanged():
36- val = concore.read(1,"data",init_simtime_val)
37- result = [v * 2 for v in val]
38- concore.write(1,"result",result,delta=0)
39- """
60+ LANGUAGE_NODES = {
61+ "python" : {
62+ "label" : "Python" ,
63+ "filename" : "script.py" ,
64+ "color" : "#ffcc00" ,
65+ "stub" : (
66+ "import concore\n \n "
67+ "concore.default_maxtime(100)\n "
68+ "concore.delay = 0.02\n \n "
69+ 'init_val = "[0.0, 0.0]"\n '
70+ "val = concore.initval(init_val)\n \n "
71+ "while concore.simtime < concore.maxtime:\n "
72+ " while concore.unchanged():\n "
73+ ' val = concore.read(1, "data", init_val)\n '
74+ " result = [v * 2 for v in val]\n "
75+ ' concore.write(1, "result", result, delta=0)\n '
76+ ),
77+ },
78+ "cpp" : {
79+ "label" : "C++" ,
80+ "filename" : "script.cpp" ,
81+ "color" : "#ae85ca" ,
82+ "stub" : (
83+ '#include "concore.hpp"\n '
84+ "#include <vector>\n \n "
85+ "int main() {\n "
86+ " Concore concore;\n "
87+ " concore.default_maxtime(100);\n "
88+ " concore.delay = 0.02;\n \n "
89+ ' std::string init_val = "[0.0, 0.0]";\n '
90+ " std::vector<double> val = concore.initval(init_val);\n \n "
91+ " while (concore.simtime < concore.maxtime) {\n "
92+ " while (concore.unchanged()) {\n "
93+ ' val = concore.read(1, "data", init_val);\n '
94+ " }\n "
95+ ' concore.write(1, "result", val, 0);\n '
96+ " }\n "
97+ " return 0;\n "
98+ "}\n "
99+ ),
100+ },
101+ "octave" : {
102+ "label" : "Octave/MATLAB" ,
103+ "filename" : "script.m" ,
104+ "color" : "#6db3f2" ,
105+ "stub" : (
106+ "global concore;\n "
107+ "import_concore;\n \n "
108+ "concore.delay = 0.02;\n "
109+ "concore_default_maxtime(100);\n \n "
110+ "init_val = '[0.0, 0.0]';\n "
111+ "val = concore_initval(init_val);\n \n "
112+ "while concore.simtime < concore.maxtime\n "
113+ " while concore_unchanged()\n "
114+ " val = concore_read(1, 'data', init_val);\n "
115+ " end\n "
116+ " result = val * 2;\n "
117+ " concore_write(1, 'result', result, 0);\n "
118+ "end\n "
119+ ),
120+ },
121+ "verilog" : {
122+ "label" : "Verilog" ,
123+ "filename" : "script.v" ,
124+ "color" : "#f28c8c" ,
125+ "stub" : (
126+ '`include "concore.v"\n \n '
127+ "module script;\n "
128+ " // concore module provides: simtime, maxtime, readdata, writedata, unchanged\n "
129+ " // data[] and datasize are global arrays filled by readdata\n \n "
130+ " real init_val[1:0]; // [simtime, value]\n "
131+ " integer i;\n \n "
132+ " initial begin\n "
133+ " concore.simtime = 0;\n "
134+ " // set your maxtime (or let concore.maxtime file override)\n \n "
135+ " while (concore.simtime < 100) begin\n "
136+ ' while (concore.unchanged(0)) begin\n '
137+ " // readdata fills concore.data[] and updates concore.simtime\n "
138+ ' concore.readdata(1, "data", "[0.0,0.0]");\n '
139+ " end\n "
140+ " // TODO: process concore.data[0..datasize-1]\n "
141+ " concore.data[0] = concore.data[0] * 2;\n "
142+ " concore.datasize = 1;\n "
143+ ' concore.writedata(1, "result", 0); // delta=0\n '
144+ " end\n "
145+ " $finish;\n "
146+ " end\n "
147+ "endmodule\n "
148+ ),
149+ },
150+ "java" : {
151+ "label" : "Java" ,
152+ "filename" : "Script.java" ,
153+ "color" : "#a8d8a8" ,
154+ "stub" : (
155+ "public class Script {\n "
156+ " public static void main(String[] args) throws Exception {\n "
157+ " concoredocker cd = new concoredocker();\n "
158+ " double maxtime = 100;\n "
159+ " double delay = 0.02;\n "
160+ ' String init_val = "[0.0, 0.0]";\n \n '
161+ " String val = cd.initval(init_val);\n "
162+ " while (cd.simtime() < maxtime) {\n "
163+ " while (cd.unchanged()) {\n "
164+ ' val = cd.read(1, "data", init_val);\n '
165+ " }\n "
166+ " // TODO: process val\n "
167+ ' cd.write(1, "result", val, 0);\n '
168+ " }\n "
169+ " }\n "
170+ "}\n "
171+ ),
172+ },
173+ }
40174
41175README_TEMPLATE = """# {project_name}
42176
59193
60194## Next Steps
61195
62- - Modify `workflow.graphml` to define your processing pipeline
63- - Add Python/C++/MATLAB scripts to `src/`
196+ - Open `workflow.graphml` in yEd and connect the nodes with edges
64197- Use `concore validate workflow.graphml` to check your workflow
65198- Use `concore status` to monitor running processes
66199"""
67200
68201
202+ # ---------------------------------------------------------------------------
203+ # Interactive wizard
204+ # ---------------------------------------------------------------------------
205+
206+ def run_wizard (console ):
207+ """Ask y/n for each supported language. Returns list of selected lang keys."""
208+ console .print ()
209+ console .print (
210+ "[bold cyan]Select the node types to include[/bold cyan] "
211+ "[dim](Enter = yes)[/dim]"
212+ )
213+ console .print ()
214+
215+ selected = []
216+ for key , info in LANGUAGE_NODES .items ():
217+ raw = console .input (
218+ f" Include [bold]{ info ['label' ]} [/bold] node? [Y/n] "
219+ ).strip ().lower ()
220+ if raw in ("" , "y" , "yes" ):
221+ selected .append (key )
222+
223+ return selected
224+
225+
226+ # ---------------------------------------------------------------------------
227+ # GraphML builder
228+ # ---------------------------------------------------------------------------
229+
230+ def _build_graphml (project_name , selected_langs ):
231+ """Return a GraphML string with one unconnected node per selected language."""
232+ node_blocks = []
233+ for idx , lang_key in enumerate (selected_langs , start = 1 ):
234+ info = LANGUAGE_NODES [lang_key ]
235+ node_blocks .append (
236+ GRAPHML_NODE .format (
237+ idx = idx ,
238+ y = 100 + (idx - 1 ) * 100 , # stack vertically, 100 px apart
239+ color = info ["color" ],
240+ filename = info ["filename" ],
241+ )
242+ )
243+ return GRAPHML_HEADER .format (
244+ project_name = project_name ,
245+ nodes = "\n " .join (node_blocks ),
246+ )
247+
248+
249+ # ---------------------------------------------------------------------------
250+ # Public entry points
251+ # ---------------------------------------------------------------------------
252+
253+ def init_project_interactive (name , selected_langs , console ):
254+ """Create a project with one node per selected language (no edges)."""
255+ project_path = Path (name )
256+
257+ if project_path .exists ():
258+ raise FileExistsError (f"Directory '{ name } ' already exists" )
259+
260+ if not selected_langs :
261+ console .print ("[yellow]No languages selected — nothing to create.[/yellow]" )
262+ return
263+
264+ console .print ()
265+ console .print (f"[cyan]Creating project:[/cyan] { name } " )
266+
267+ project_path .mkdir ()
268+ src_path = project_path / "src"
269+ src_path .mkdir ()
270+
271+ # workflow.graphml
272+ workflow_file = project_path / "workflow.graphml"
273+ workflow_file .write_text (_build_graphml (name , selected_langs ))
274+
275+ # one source stub per selected language
276+ for lang_key in selected_langs :
277+ info = LANGUAGE_NODES [lang_key ]
278+ (src_path / info ["filename" ]).write_text (info ["stub" ])
279+
280+ # README
281+ (project_path / "README.md" ).write_text (
282+ README_TEMPLATE .format (project_name = name )
283+ )
284+
285+ # Metadata
286+ metadata_info = ""
287+ try :
288+ metadata_path = write_study_metadata (
289+ project_path ,
290+ generated_by = "concore init --interactive" ,
291+ workflow_file = workflow_file ,
292+ )
293+ metadata_info = f"Metadata:\n { metadata_path .name } \n \n "
294+ except Exception as exc :
295+ console .print (
296+ f"[yellow]Warning:[/yellow] Failed to write study metadata: { exc } "
297+ )
298+
299+ node_lines = "\n " .join (
300+ f" N{ i } : { LANGUAGE_NODES [k ]['filename' ]} "
301+ for i , k in enumerate (selected_langs , 1 )
302+ )
303+
304+ console .print ()
305+ console .print (
306+ Panel .fit (
307+ f"[green]✓[/green] Project created with { len (selected_langs )} node(s)!\n \n "
308+ f"{ metadata_info } "
309+ f"Nodes (unconnected — connect them in yEd):\n { node_lines } \n \n "
310+ f"Next steps:\n "
311+ f" cd { name } \n "
312+ f" concore validate workflow.graphml\n "
313+ f" concore run workflow.graphml" ,
314+ title = "Success" ,
315+ border_style = "green" ,
316+ )
317+ )
318+
319+
69320def init_project (name , template , console ):
321+ """Non-interactive init — single Python node skeleton."""
70322 project_path = Path (name )
71323
72324 if project_path .exists ():
@@ -81,13 +333,13 @@ def init_project(name, template, console):
81333 with open (workflow_file , "w" ) as f :
82334 f .write (SAMPLE_GRAPHML )
83335
84- sample_script = project_path / "src" / "script.py"
85- with open ( sample_script , "w" ) as f :
86- f . write ( SAMPLE_PYTHON )
336+ ( project_path / "src" / "script.py" ). write_text (
337+ LANGUAGE_NODES [ "python" ][ "stub" ]
338+ )
87339
88- readme_file = project_path / "README.md"
89- with open ( readme_file , "w" ) as f :
90- f . write ( README_TEMPLATE . format ( project_name = name ) )
340+ ( project_path / "README.md" ). write_text (
341+ README_TEMPLATE . format ( project_name = name )
342+ )
91343
92344 metadata_info = ""
93345 try :
0 commit comments