1pub mod commands;
2pub mod config;
3pub mod error;
4pub mod output;
5pub mod templates;
6pub mod utils;
7
8use clap::{Parser, Subcommand, ValueEnum};
9
10#[derive(Debug, Clone, Copy, ValueEnum, Default)]
12pub enum LogLevel {
13 Error,
15 #[default]
17 Warn,
18 Info,
20 Debug,
22 Trace,
24}
25
26impl From<LogLevel> for tracing::Level {
27 fn from(level: LogLevel) -> Self {
28 match level {
29 LogLevel::Error => tracing::Level::ERROR,
30 LogLevel::Warn => tracing::Level::WARN,
31 LogLevel::Info => tracing::Level::INFO,
32 LogLevel::Debug => tracing::Level::DEBUG,
33 LogLevel::Trace => tracing::Level::TRACE,
34 }
35 }
36}
37
38#[derive(Debug, Parser)]
41#[command(name = "theater")]
42#[command(author, version, about, long_about = None)]
43pub struct Cli {
44 #[arg(short, long, global = true, value_enum, default_value = "warn")]
46 pub log_level: LogLevel,
47
48 #[arg(long, global = true)]
50 pub json: bool,
51
52 #[command(subcommand)]
53 pub command: Commands,
54}
55
56#[derive(Debug, Subcommand)]
57pub enum Commands {
58 #[command(name = "create")]
60 Create(commands::create::CreateArgs),
61
62 #[command(name = "build")]
64 Build(commands::build::BuildArgs),
65
66 #[command(name = "spawn")]
68 Spawn(commands::spawn::SpawnArgs),
69
70 #[command(name = "setup")]
73 Setup(commands::setup::SetupArgs),
74
75 #[command(name = "chains")]
77 Chains(commands::chains::ChainsArgs),
78
79 #[command(name = "completion")]
81 Completion(commands::completion::CompletionArgs),
82
83 #[command(name = "dynamic-completion", hide = true)]
85 DynamicCompletion(commands::dynamic_completion::DynamicCompletionArgs),
86}
87
88pub async fn run(
90 cli: Cli,
91 config: config::Config,
92 shutdown_token: tokio_util::sync::CancellationToken,
93) -> anyhow::Result<()> {
94 let output = output::OutputManager::new(config.output.clone());
96
97 let ctx = CommandContext {
99 config,
100 output,
101 log_level: cli.log_level,
102 json: cli.json,
103 shutdown_token: shutdown_token.clone(),
104 };
105
106 let command_future = async {
108 match &cli.command {
109 Commands::Create(args) => commands::create::execute_async(args, &ctx)
110 .await
111 .map_err(anyhow::Error::from),
112 Commands::Build(args) => commands::build::execute_async(args, &ctx)
113 .await
114 .map_err(anyhow::Error::from),
115 Commands::Spawn(args) => commands::spawn::execute_spawn(args, &ctx)
116 .await
117 .map_err(anyhow::Error::from),
118 Commands::Setup(args) => commands::setup::execute_async(args, &ctx)
119 .await
120 .map_err(anyhow::Error::from),
121 Commands::Chains(args) => commands::chains::execute_async(args, &ctx)
122 .await
123 .map_err(anyhow::Error::from),
124 Commands::Completion(args) => commands::completion::execute_async(args, &ctx)
125 .await
126 .map_err(anyhow::Error::from),
127 Commands::DynamicCompletion(args) => {
128 commands::dynamic_completion::execute_async(args, &ctx)
129 .await
130 .map_err(anyhow::Error::from)
131 }
132 }
133 };
134
135 let result = tokio::select! {
137 result = command_future => result,
138 _ = shutdown_token.cancelled() => {
139 return Ok(());
140 }
141 };
142
143 match result {
145 Ok(()) => Ok(()),
146 Err(e) => {
147 if let Some(cli_error) = e.downcast_ref::<error::CliError>() {
149 ctx.output.error(&cli_error.user_message())?;
150 if ctx.is_verbose() {
151 eprintln!("\nDebug info: {:?}", cli_error);
152 }
153 } else {
154 ctx.output.error(&format!("Error: {}", e))?;
155 if ctx.is_verbose() {
156 eprintln!("\nDebug info: {:?}", e);
157 }
158 }
159 std::process::exit(1);
160 }
161 }
162}
163
164pub struct CommandContext {
166 pub config: config::Config,
167 pub output: output::OutputManager,
168 pub log_level: LogLevel,
169 pub json: bool,
170 pub shutdown_token: tokio_util::sync::CancellationToken,
171}
172
173impl CommandContext {
174 pub fn is_verbose(&self) -> bool {
176 matches!(self.log_level, LogLevel::Debug | LogLevel::Trace)
177 }
178}