396 lines
11 KiB
Markdown
396 lines
11 KiB
Markdown
# CLI Implementation Example (Headless Mode)
|
|
|
|
This document shows a concrete implementation of **Option 1: Headless Flag** from [cli.md](cli.md).
|
|
|
|
## Step 1: Update CLI Argument Parser
|
|
|
|
Add headless mode and CLI command options to `entry_point.cpp`:
|
|
|
|
```cpp
|
|
// entry_point.cpp - in CommandLineParser::Parse()
|
|
CLI::App app{"NodeHub"};
|
|
app.allow_extras();
|
|
|
|
app.add_option("--file", "the source path")->capture_default_str();
|
|
|
|
// CLI mode options
|
|
app.add_flag("--headless", "Run without GUI (CLI mode)");
|
|
app.add_option("--command", "Command to execute in headless mode (validate, export, execute)")
|
|
->capture_default_str()
|
|
->check(CLI::IsMember({"validate", "export", "execute", "info"}));
|
|
app.add_option("--output", "Output file path for export commands")->capture_default_str();
|
|
app.add_option("--format", "Output format (json, xml, yaml)")->capture_default_str();
|
|
```
|
|
|
|
## Step 2: Add CLI Helper Functions
|
|
|
|
Create `blueprints-cli.h`:
|
|
|
|
```cpp
|
|
// blueprints-cli.h
|
|
#pragma once
|
|
#include "application.h"
|
|
#include <string>
|
|
|
|
// Helper to extract string from ArgsMap
|
|
inline std::string GetStringArg(const ArgsMap& args, const std::string& key, const std::string& defaultValue = "")
|
|
{
|
|
auto it = args.find(key);
|
|
if (it != args.end() && it->second.Type == ArgValue::Type::String)
|
|
return it->second.String;
|
|
return defaultValue;
|
|
}
|
|
|
|
// Helper to extract bool from ArgsMap
|
|
inline bool GetBoolArg(const ArgsMap& args, const std::string& key, bool defaultValue = false)
|
|
{
|
|
auto it = args.find(key);
|
|
if (it != args.end() && it->second.Type == ArgValue::Type::Bool)
|
|
return it->second.Bool;
|
|
return defaultValue;
|
|
}
|
|
|
|
// CLI command execution
|
|
int RunCLI(const ArgsMap& args);
|
|
```
|
|
|
|
Create `blueprints-cli.cpp`:
|
|
|
|
```cpp
|
|
// blueprints-cli.cpp
|
|
#include "blueprints-cli.h"
|
|
#include "core/graph_state.h"
|
|
#include <cstdio>
|
|
|
|
static int CommandValidate(const std::string& filename)
|
|
{
|
|
printf("Validating: %s\n", filename.c_str());
|
|
|
|
GraphState graph;
|
|
if (!graph.Load(filename)) {
|
|
fprintf(stderr, "Error: Failed to load graph from %s\n", filename.c_str());
|
|
return 1;
|
|
}
|
|
|
|
// TODO: Add actual validation logic
|
|
// For now, just check if it loaded
|
|
printf("✓ Graph loaded successfully\n");
|
|
printf(" Nodes: %zu\n", graph.GetNodes().size());
|
|
printf(" Links: %zu\n", graph.GetLinks().size());
|
|
|
|
// Basic validation
|
|
bool valid = true;
|
|
|
|
// Check for disconnected pins
|
|
for (const auto& link : graph.GetLinks()) {
|
|
if (!graph.FindPin(link.StartPinID)) {
|
|
fprintf(stderr, "Error: Link references missing start pin\n");
|
|
valid = false;
|
|
}
|
|
if (!graph.FindPin(link.EndPinID)) {
|
|
fprintf(stderr, "Error: Link references missing end pin\n");
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
printf("✓ Validation passed\n");
|
|
return 0;
|
|
} else {
|
|
fprintf(stderr, "✗ Validation failed\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int CommandExport(const std::string& filename, const std::string& output, const std::string& format)
|
|
{
|
|
printf("Exporting: %s -> %s (%s)\n", filename.c_str(), output.c_str(), format.c_str());
|
|
|
|
GraphState graph;
|
|
if (!graph.Load(filename)) {
|
|
fprintf(stderr, "Error: Failed to load graph from %s\n", filename.c_str());
|
|
return 1;
|
|
}
|
|
|
|
// Export based on format
|
|
if (format == "json") {
|
|
if (graph.Save(output)) {
|
|
printf("✓ Exported to JSON: %s\n", output.c_str());
|
|
return 0;
|
|
} else {
|
|
fprintf(stderr, "Error: Failed to export to %s\n", output.c_str());
|
|
return 1;
|
|
}
|
|
} else if (format == "xml" || format == "yaml") {
|
|
fprintf(stderr, "Error: Format '%s' not yet implemented\n", format.c_str());
|
|
return 1;
|
|
} else {
|
|
fprintf(stderr, "Error: Unknown format '%s'\n", format.c_str());
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int CommandExecute(const std::string& filename)
|
|
{
|
|
printf("Executing: %s\n", filename.c_str());
|
|
|
|
GraphState graph;
|
|
if (!graph.Load(filename)) {
|
|
fprintf(stderr, "Error: Failed to load graph from %s\n", filename.c_str());
|
|
return 1;
|
|
}
|
|
|
|
// TODO: Implement graph execution
|
|
fprintf(stderr, "Error: Graph execution not yet implemented\n");
|
|
return 1;
|
|
}
|
|
|
|
static int CommandInfo(const std::string& filename)
|
|
{
|
|
printf("Graph info: %s\n", filename.c_str());
|
|
|
|
GraphState graph;
|
|
if (!graph.Load(filename)) {
|
|
fprintf(stderr, "Error: Failed to load graph from %s\n", filename.c_str());
|
|
return 1;
|
|
}
|
|
|
|
printf("Nodes: %zu\n", graph.GetNodes().size());
|
|
printf("Links: %zu\n", graph.GetLinks().size());
|
|
|
|
// Node type breakdown
|
|
std::map<std::string, int> nodeTypes;
|
|
for (const auto& node : graph.GetNodes()) {
|
|
nodeTypes[node.Type]++;
|
|
}
|
|
|
|
printf("\nNode Types:\n");
|
|
for (const auto& [type, count] : nodeTypes) {
|
|
printf(" %s: %d\n", type.c_str(), count);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int RunCLI(const ArgsMap& args)
|
|
{
|
|
std::string filename = GetStringArg(args, "file", "");
|
|
if (filename.empty()) {
|
|
fprintf(stderr, "Error: --file required in headless mode\n");
|
|
fprintf(stderr, "\nUsage:\n");
|
|
fprintf(stderr, " --headless --file <graph.json> --command <command> [options]\n");
|
|
fprintf(stderr, "\nCommands:\n");
|
|
fprintf(stderr, " validate Validate graph structure\n");
|
|
fprintf(stderr, " export Export to different format\n");
|
|
fprintf(stderr, " execute Execute graph (headless)\n");
|
|
fprintf(stderr, " info Display graph information\n");
|
|
return 1;
|
|
}
|
|
|
|
std::string command = GetStringArg(args, "command", "validate");
|
|
|
|
if (command == "validate") {
|
|
return CommandValidate(filename);
|
|
}
|
|
else if (command == "export") {
|
|
std::string output = GetStringArg(args, "output", "");
|
|
if (output.empty()) {
|
|
fprintf(stderr, "Error: --output required for export command\n");
|
|
return 1;
|
|
}
|
|
std::string format = GetStringArg(args, "format", "json");
|
|
return CommandExport(filename, output, format);
|
|
}
|
|
else if (command == "execute") {
|
|
return CommandExecute(filename);
|
|
}
|
|
else if (command == "info") {
|
|
return CommandInfo(filename);
|
|
}
|
|
else {
|
|
fprintf(stderr, "Error: Unknown command '%s'\n", command.c_str());
|
|
return 1;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Step 3: Update blueprints-example.cpp
|
|
|
|
Modify the main entry point to support headless mode:
|
|
|
|
```cpp
|
|
// blueprints-example.cpp
|
|
#include "app.h"
|
|
#include "blueprints-cli.h"
|
|
#include <map>
|
|
#include <cstdio>
|
|
|
|
int Main(const ArgsMap& args)
|
|
{
|
|
// Check for headless mode
|
|
bool headless = GetBoolArg(args, "headless", false);
|
|
|
|
if (headless) {
|
|
printf("[CLI MODE] Running in headless mode\n");
|
|
return RunCLI(args);
|
|
}
|
|
|
|
// GUI mode (existing code)
|
|
printf("[GUI MODE] Starting application\n");
|
|
|
|
App example("Blueprints", args);
|
|
|
|
if (example.Create())
|
|
return example.Run();
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
## Step 4: Update CMakeLists.txt
|
|
|
|
Add the new CLI files to the build:
|
|
|
|
```cmake
|
|
# examples/blueprints-example/CMakeLists.txt
|
|
add_example_executable(blueprints-example
|
|
blueprints-example.cpp
|
|
blueprints-cli.h
|
|
blueprints-cli.cpp # Add this
|
|
types.h
|
|
nodes.h
|
|
nodes.cpp
|
|
# ... rest of files
|
|
)
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
Once implemented, you can use it like this:
|
|
|
|
### Validate a graph
|
|
```bash
|
|
./build/bin/blueprints-example-console_d.exe --headless --file graph.json --command validate
|
|
```
|
|
|
|
Output:
|
|
```
|
|
[CLI MODE] Running in headless mode
|
|
Validating: graph.json
|
|
✓ Graph loaded successfully
|
|
Nodes: 15
|
|
Links: 12
|
|
✓ Validation passed
|
|
```
|
|
|
|
### Get graph info
|
|
```bash
|
|
./build/bin/blueprints-example-console_d.exe --headless --file graph.json --command info
|
|
```
|
|
|
|
Output:
|
|
```
|
|
[CLI MODE] Running in headless mode
|
|
Graph info: graph.json
|
|
Nodes: 15
|
|
Links: 12
|
|
|
|
Node Types:
|
|
Math: 5
|
|
Input: 3
|
|
Output: 2
|
|
Group: 5
|
|
```
|
|
|
|
### Export to different format
|
|
```bash
|
|
./build/bin/blueprints-example-console_d.exe --headless --file graph.json --command export --output converted.json --format json
|
|
```
|
|
|
|
### Batch validation
|
|
```bash
|
|
# PowerShell
|
|
Get-ChildItem *.json | ForEach-Object {
|
|
Write-Host "Processing $_"
|
|
./build/bin/blueprints-example-console_d.exe --headless --file $_.Name --command validate
|
|
}
|
|
|
|
# Bash
|
|
for file in *.json; do
|
|
echo "Processing $file"
|
|
./build/bin/blueprints-example-console_d.exe --headless --file "$file" --command validate
|
|
done
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Test GUI mode still works
|
|
```bash
|
|
./build/bin/blueprints-example_d.exe --file graph.json
|
|
# Should open normally
|
|
```
|
|
|
|
### Test CLI mode
|
|
```bash
|
|
./build/bin/blueprints-example-console_d.exe --headless --file graph.json --command validate
|
|
# Should NOT create any windows
|
|
```
|
|
|
|
### Test without file argument
|
|
```bash
|
|
./build/bin/blueprints-example-console_d.exe --headless
|
|
# Should show error and usage
|
|
```
|
|
|
|
## Benefits
|
|
|
|
✅ **No UI overhead** - Headless mode doesn't create any windows or ImGui context
|
|
✅ **Fast** - Perfect for CI/CD pipelines
|
|
✅ **Automatable** - Can be scripted and batched
|
|
✅ **Same binary** - No need to maintain separate executables initially
|
|
✅ **Reuses CLI parsing** - All argument handling already in place
|
|
|
|
## Next Steps
|
|
|
|
1. **Implement actual validation** - Add real graph validation logic
|
|
2. **Add more commands** - Convert, merge, diff, etc.
|
|
3. **Add tests** - Unit tests for CLI commands
|
|
4. **Consider refactoring** - If CLI usage grows, consider Option 3 or 4 from [cli.md](cli.md)
|
|
|
|
## Integration with CI/CD
|
|
|
|
Example GitHub Actions workflow:
|
|
|
|
```yaml
|
|
# .github/workflows/validate-graphs.yml
|
|
name: Validate Graphs
|
|
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
validate:
|
|
runs-on: windows-latest
|
|
steps:
|
|
- uses: actions/checkout@v2
|
|
|
|
- name: Build
|
|
run: |
|
|
cmake -S examples -B build
|
|
cmake --build build --config Release --target blueprints-example-console
|
|
|
|
- name: Validate all graphs
|
|
run: |
|
|
Get-ChildItem -Path test-graphs -Filter *.json | ForEach-Object {
|
|
Write-Host "Validating $_"
|
|
./build/bin/blueprints-example-console.exe --headless --file $_.FullName --command validate
|
|
if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
}
|
|
```
|
|
|
|
## See Also
|
|
|
|
- [CLI Architecture Options](cli.md) - Full architectural discussion
|
|
- [Console Variants Guide](console-variants.md) - Console vs GUI variants
|
|
- [Debugging Guide](../DEBUGGING.md) - How to debug both modes
|
|
|