Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions DEV-GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Concore Developer Guide

## `concore init --interactive`

The interactive init wizard lets users scaffold a new multi-language concore project without writing any boilerplate by hand.

### Usage

```bash
concore init <project-name> --interactive
# or shorthand
concore init <project-name> -i
```

The wizard prompts for each supported language with a `y/n` question (default: yes):

```
Select the node types to include (Enter = yes)

Include Python node? [Y/n]
Include C++ node? [Y/n]
Include Octave/MATLAB node? [Y/n]
Include Verilog node? [Y/n]
Include Java node? [Y/n]
```

### What gets generated

For each selected language, the wizard:

1. Creates a **source stub** in `src/` with the correct concore API calls for that language.
2. Adds an **unconnected node** to `workflow.graphml`, colour-coded and vertically positioned for easy viewing in yEd.

Example output for Python + Java selected:

```
my_project/
├── workflow.graphml ← 2 unconnected nodes
├── src/
│ ├── script.py ← Python stub
│ └── Script.java ← Java stub
├── README.md
└── STUDY.json
```

### Architecture

The feature lives entirely in `concore_cli/commands/init.py`:

| Symbol | Role |
|---|---|
| `LANGUAGE_NODES` | Dict mapping language key → `label`, `filename`, node `color`, source `stub` |
| `GRAPHML_HEADER` | XML template for the GraphML wrapper; `project_name` is escaped via `xml.sax.saxutils.quoteattr` |
| `GRAPHML_NODE` | XML template for a single node block |
| `run_wizard()` | Prompts y/n for each language; returns list of selected keys |
| `_build_graphml()` | Assembles the full GraphML string from selected languages |
| `init_project_interactive()` | Orchestrates directory creation, file writes, and success output |

---

## Adding a New Language

Adding Julia (or any other language) to the interactive wizard is a **one-file change** — just add an entry to the `LANGUAGE_NODES` dictionary in `concore_cli/commands/init.py`.

### Step-by-step: adding Julia

**1. Add the entry to `LANGUAGE_NODES`** in `concore_cli/commands/init.py`:

```python
"julia": {
"label": "Julia",
"filename": "script.jl",
"color": "#9558b2", # Julia purple
"stub": (
"using Concore\n\n"
"Concore.state.delay = 0.02\n"
"Concore.state.inpath = \"./in\"\n"
"Concore.state.outpath = \"./out\"\n\n"
"maxtime = default_maxtime(100.0)\n"
'init_val = "[0.0, 0.0]"\n\n'
"while Concore.state.simtime < maxtime\n"
" while unchanged()\n"
' val = Float64.(concore_read(1, "data", init_val))\n'
" end\n"
" # TODO: process val\n"
" result = val .* 2\n"
' concore_write(1, "result", result, 0.0)\n'
"end\n"
"println(\"retry=$(Concore.state.retrycount)\")\n"
),
},
```

Key Julia API points (based on real concore Julia scripts):

| Element | Julia equivalent |
|---|---|
| Import | `using Concore` |
| Setup | `Concore.state.delay`, `.inpath`, `.outpath` |
| Max time | `default_maxtime(100.0)` — returns the value |
| Sim time | `Concore.state.simtime` |
| Unchanged check | `unchanged()` — no module prefix |
| Read | `concore_read(port, name, initstr)` — snake\_case, no prefix |
| Write | `concore_write(port, name, val, delta)` — snake\_case, no prefix |
| Type cast | `Float64.(concore_read(...))` to ensure numeric type |

**2. That's it.** No other files need to change — the wizard, GraphML builder, and file writer all iterate over `LANGUAGE_NODES` dynamically.

### Node colours used

| Language | Hex colour |
|---|---|
| Python | `#ffcc00` (yellow) |
| C++ | `#ae85ca` (purple) |
| Octave/MATLAB | `#6db3f2` (blue) |
| Verilog | `#f28c8c` (red) |
| Java | `#a8d8a8` (green) |
| Julia *(proposed)* | `#9558b2` (Julia purple) |

---
28 changes: 24 additions & 4 deletions concore_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import sys

from .commands.init import init_project
from .commands.init import init_project, init_project_interactive, run_wizard
from .commands.run import run_workflow
from .commands.validate import validate_workflow
from .commands.status import show_status
Expand All @@ -24,12 +24,32 @@ def cli():


@cli.command()
@click.argument("name", required=True)
@click.argument("name", required=False, default=None)
@click.option("--template", default="basic", help="Template type to use")
def init(name, template):
@click.option(
"--interactive",
"-i",
is_flag=True,
help="Launch guided wizard to select node types",
)
def init(name, template, interactive):
"""Create a new concore project"""
try:
init_project(name, template, console)
if interactive:
if not name:
name = console.input("[cyan]Project name:[/cyan] ").strip()
if not name:
console.print("[red]Error:[/red] Project name is required.")
sys.exit(1)
selected = run_wizard(console)
init_project_interactive(name, selected, console)
else:
if not name:
console.print(
"[red]Error:[/red] Provide a project name or use --interactive."
)
sys.exit(1)
init_project(name, template, console)
Comment on lines +27 to +52
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing CLI tests (e.g., tests/test_cli.py::test_init_command), but the new init --interactive/-i behavior isn’t covered. This introduces multiple new branches (prompting for missing name, language selection, multi-node GraphML generation) that can regress without tests.

Please add tests that exercise: (1) init -i <name> with a fixed selection via CliRunner.invoke(..., input=...) and assert that the expected stub files + node labels exist, and (2) init -i with empty name input returning a non-zero exit code and the expected error message.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can write the tests separately, will ask mentor

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, tests are an "easy" contribution. Can be a subsequent PR.

except Exception as e:
console.print(f"[red]Error:[/red] {str(e)}")
sys.exit(1)
Expand Down
Loading
Loading