use clap::{Parser, Subcommand};
use rust_config_tree::cli::{ConfigCommand, handle_config_command};
use rust_config_tree::config::write_config_templates_with_schema;
use rust_supervisor::config::configurable::SupervisorConfig;
use rust_supervisor::config::factory_schema::supervisor_schema_targets_with_factory_registry;
use rust_supervisor::spec::child::TaskKind;
use rust_supervisor::task::factory::{TaskResult, service_fn};
use rust_supervisor::task::factory_registry::{TaskFactoryDescriptor, TaskFactoryRegistry};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
const DEFAULT_CONFIG_PATH: &str = "examples/config/supervisor.yaml";
const DEFAULT_TEMPLATE_OUTPUT: &str = "config/supervisor_config/supervisor_config.example.yaml";
const DEFAULT_SCHEMA_OUTPUT: &str = "config/supervisor_config/supervisor_config.schema.json";
type CliResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[derive(Debug, Parser)]
#[command(name = "rust-tokio-supervisor")]
struct Cli {
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Subcommand)]
enum Command {
Run {
#[arg(long)]
config: Option<PathBuf>,
},
#[command(flatten)]
Config(ConfigCommand),
}
fn main() -> CliResult<()> {
if std::env::args_os().len() == 1 {
let program = std::env::args_os()
.next()
.unwrap_or_else(|| "rust-tokio-supervisor".into());
let _ = Cli::parse_from([program, "--help".into()]);
return Ok(());
}
let cli = Cli::parse();
let default_config_path = PathBuf::from(DEFAULT_CONFIG_PATH);
match cli.command.unwrap_or(Command::Run { config: None }) {
Command::Run { config } => {
let config_path = config.unwrap_or(default_config_path);
let config = rust_config_tree::config::load_config::<SupervisorConfig>(&config_path)?;
println!("config path: {}", config_path.display());
println!("strategy: {:?}", config.supervisor.strategy);
println!("children: {}", config.children.len());
}
Command::Config(command) => {
handle_supervisor_config_command(command, &default_config_path)?;
}
}
Ok(())
}
fn handle_supervisor_config_command(
command: ConfigCommand,
default_config_path: &Path,
) -> CliResult<()> {
match command {
ConfigCommand::GenerateTemplate { output, schema } => {
let output = output.unwrap_or_else(|| PathBuf::from(DEFAULT_TEMPLATE_OUTPUT));
let schema = schema.unwrap_or_else(|| PathBuf::from(DEFAULT_SCHEMA_OUTPUT));
let registry = default_task_factory_registry()?;
write_supervisor_config_schemas(&schema, ®istry)?;
write_config_templates_with_schema::<SupervisorConfig>(
default_config_path,
output,
schema,
)?;
Ok(())
}
ConfigCommand::GenerateSchema { output } => {
let output = output.unwrap_or_else(|| PathBuf::from(DEFAULT_SCHEMA_OUTPUT));
let registry = default_task_factory_registry()?;
write_supervisor_config_schemas(&output, ®istry)
}
other => {
handle_config_command::<Cli, SupervisorConfig>(other, default_config_path)?;
Ok(())
}
}
}
fn write_supervisor_config_schemas(
output_path: &Path,
registry: &TaskFactoryRegistry,
) -> CliResult<()> {
for target in supervisor_schema_targets_with_factory_registry(output_path, registry)? {
if let Some(parent) = target
.path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
fs::create_dir_all(parent)?;
}
fs::write(&target.path, target.content)?;
}
Ok(())
}
fn default_task_factory_registry() -> CliResult<TaskFactoryRegistry> {
let mut registry = TaskFactoryRegistry::new();
registry.register(TaskFactoryDescriptor::new(
"api_server",
"API Server",
"Runs the API service.",
[TaskKind::AsyncWorker],
Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
))?;
registry.register(TaskFactoryDescriptor::new(
"report_exporter",
"Report Exporter",
"Runs blocking export work.",
[TaskKind::BlockingWorker],
Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
))?;
registry.register(TaskFactoryDescriptor::new(
"worker_factory",
"Worker Factory",
"Runs a generic asynchronous worker.",
[TaskKind::AsyncWorker],
Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
))?;
Ok(registry)
}