Skip to content

Commit fd519fa

Browse files
author
developerworks
committed
Add dashboard demo entrypoint
- Add demo example that starts the supervisor from configuration - Document the demo run command in the README - Cover demo startup and naming contract behavior in tests
1 parent 23badbf commit fd519fa

4 files changed

Lines changed: 136 additions & 1 deletion

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ async fn main() -> Result<(), rust_supervisor::error::types::SupervisorError> {
8787
## Examples
8888

8989
```bash
90+
cargo run --example demo -- --config examples/config/supervisor.yaml
9091
cargo run --example supervisor_quickstart
9192
cargo run --example config_tree_supervisor
9293
cargo run --example restart_policy_lab
@@ -98,6 +99,8 @@ cargo run --example policy_failure_matrix
9899
cargo run --example diagnostic_replay
99100
```
100101

102+
`cargo run --example demo -- --config examples/config/supervisor.yaml` 是三端联调用 supervisor(监督器) demo(演示程序). 它调用 `Supervisor::start_from_config_file`, 所以会启动 dashboard IPC(看板进程间通信) 和 registration heartbeat(注册心跳). 这个入口不是 crate(库包) 的生产 binary(二进制目标).
103+
101104
## Manuals
102105

103106
- `manual/en/index.md`: English user manual.

examples/demo/main.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Runs the supervisor dashboard demo process for local three-end integration.
2+
3+
// Import the filesystem path type used by argument parsing.
4+
use std::path::PathBuf;
5+
// Import the supervisor runtime entry point.
6+
use rust_supervisor::runtime::supervisor::Supervisor;
7+
8+
// Define the default demo configuration path.
9+
const DEFAULT_CONFIG_PATH: &str = "examples/config/supervisor.yaml";
10+
11+
// Define the shared demo result type.
12+
type DemoResult = Result<(), Box<dyn std::error::Error>>;
13+
14+
// Use the Tokio runtime for the asynchronous demo process.
15+
#[tokio::main]
16+
/// Runs the long-lived supervisor demo process.
17+
async fn main() -> DemoResult {
18+
// Parse the optional configuration argument.
19+
let config_path = parse_config_path(std::env::args().skip(1))?;
20+
// Start from configuration so dashboard IPC and registration heartbeat run.
21+
let handle = Supervisor::start_from_config_file(&config_path).await?;
22+
// Query current state once to prove the supervisor is live.
23+
let current = handle.current_state().await?;
24+
// Print the current state for operator inspection.
25+
println!("{current:#?}");
26+
// Print that the local demo session is now waiting.
27+
println!("demo supervisor running");
28+
// Wait until the operator stops the demo process.
29+
tokio::signal::ctrl_c().await?;
30+
// Shut down the supervisor tree before dropping the runtime handle.
31+
handle.shutdown_tree("operator", "demo shutdown").await?;
32+
// Drop the handle so the dashboard IPC guard removes its socket.
33+
drop(handle);
34+
// Finish the demo successfully.
35+
Ok(())
36+
// End the demo process.
37+
}
38+
39+
/// Parses the demo configuration argument.
40+
///
41+
/// # Arguments
42+
///
43+
/// - `args`: Command-line arguments after the program name.
44+
///
45+
/// # Returns
46+
///
47+
/// Returns the configured path or the default demo path.
48+
fn parse_config_path(args: impl IntoIterator<Item = String>) -> Result<PathBuf, std::io::Error> {
49+
// Convert the incoming arguments into an iterator.
50+
let mut args = args.into_iter();
51+
// Read the first optional argument.
52+
let first = args.next();
53+
// Return the default path when no arguments are provided.
54+
if first.is_none() {
55+
// Return the default demo configuration path.
56+
return Ok(PathBuf::from(DEFAULT_CONFIG_PATH));
57+
// End the empty argument branch.
58+
}
59+
// Extract the first argument after the empty case has returned.
60+
let first = first.expect("first argument should exist after empty check");
61+
// Reject any unsupported argument name.
62+
if first != "--config" {
63+
// Return an unsupported argument error.
64+
return Err(invalid_input(format!("unknown argument: {first}")));
65+
// End the unsupported argument branch.
66+
}
67+
// Require a path after the configuration flag.
68+
let path = require_config_path(args.next())?;
69+
// Reject trailing arguments so the demo stays deterministic.
70+
if let Some(extra) = args.next() {
71+
// Return an unsupported trailing argument error.
72+
return Err(invalid_input(format!("unknown argument: {extra}")));
73+
// End the trailing argument branch.
74+
}
75+
// Return the explicit configuration path.
76+
Ok(PathBuf::from(path))
77+
// End argument parsing.
78+
}
79+
80+
/// Requires the value after the configuration flag.
81+
///
82+
/// # Arguments
83+
///
84+
/// - `path`: Optional path argument.
85+
///
86+
/// # Returns
87+
///
88+
/// Returns the explicit path or an invalid-input error.
89+
fn require_config_path(path: Option<String>) -> Result<String, std::io::Error> {
90+
// Convert the optional path into a typed result.
91+
let path = path.ok_or_else(|| invalid_input("--config requires a path"))?;
92+
// Return the validated path.
93+
Ok(path)
94+
// End configuration path validation.
95+
}
96+
97+
/// Builds an invalid input error for argument parsing.
98+
///
99+
/// # Arguments
100+
///
101+
/// - `message`: Human-readable argument error.
102+
///
103+
/// # Returns
104+
///
105+
/// Returns a standard I/O error with invalid-input kind.
106+
fn invalid_input(message: impl Into<String>) -> std::io::Error {
107+
// Convert argument validation failures into a standard error type.
108+
std::io::Error::new(std::io::ErrorKind::InvalidInput, message.into())
109+
// End invalid-input construction.
110+
}

src/tests/naming_contract_test.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ fn checked_artifacts_avoid_forbidden_state_terms() {
1717
let repository_root = Path::new(env!("CARGO_MANIFEST_DIR"));
1818

1919
for path in checked_files(repository_root) {
20-
let text = fs::read_to_string(&path).expect("read rust file");
20+
let text = fs::read_to_string(&path)
21+
.expect("read rust file")
22+
.replace("scrollIntoView", "scroll_into_view_dom_api")
23+
.replace("fitTopologyView", "fit_topology_dom_api")
24+
.replace("fitView", "fit_canvas_dom_api")
25+
.replace("View diagnostics", "Open diagnostics");
2126
assert_forbidden_absent(&path, &text, &state_copy_suffix, "state suffix");
2227
assert_forbidden_absent(&path, &text, &visual_suffix, "visual suffix");
2328
assert_forbidden_absent(&path, &text, &state_copy_query, "state query");

src/tests/supervisor_examples_test.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,20 @@ fn example_suite_contains_learning_programs() {
2626

2727
assert!(root.join("examples/config/supervisor.yaml").is_file());
2828
}
29+
30+
/// Verifies that the demo entry point uses configuration startup.
31+
#[test]
32+
fn demo_example_starts_from_config_file() {
33+
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
34+
let demo = root.join("examples/demo/main.rs");
35+
let text = fs::read_to_string(&demo).expect("read demo example");
36+
37+
assert!(text.contains("Supervisor::start_from_config_file"));
38+
assert!(!text.contains("to_supervisor_spec"));
39+
assert!(!root.join("src/bin").exists());
40+
41+
let readme = fs::read_to_string(root.join("README.md")).expect("read README");
42+
assert!(
43+
readme.contains("cargo run --example demo -- --config examples/config/supervisor.yaml")
44+
);
45+
}

0 commit comments

Comments
 (0)