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 = "completion")]
77 Completion(commands::completion::CompletionArgs),
78
79 #[command(name = "dynamic-completion", hide = true)]
81 DynamicCompletion(commands::dynamic_completion::DynamicCompletionArgs),
82}
83
84pub async fn run(
86 cli: Cli,
87 config: config::Config,
88 shutdown_token: tokio_util::sync::CancellationToken,
89) -> anyhow::Result<()> {
90 let output = output::OutputManager::new(config.output.clone());
92
93 let ctx = CommandContext {
95 config,
96 output,
97 log_level: cli.log_level,
98 json: cli.json,
99 shutdown_token: shutdown_token.clone(),
100 };
101
102 let command_future = async {
104 match &cli.command {
105 Commands::Create(args) => commands::create::execute_async(args, &ctx)
106 .await
107 .map_err(anyhow::Error::from),
108 Commands::Build(args) => commands::build::execute_async(args, &ctx)
109 .await
110 .map_err(anyhow::Error::from),
111 Commands::Spawn(args) => commands::spawn::execute_spawn(args, &ctx)
112 .await
113 .map_err(anyhow::Error::from),
114 Commands::Setup(args) => commands::setup::execute_async(args, &ctx)
115 .await
116 .map_err(anyhow::Error::from),
117 Commands::Completion(args) => commands::completion::execute_async(args, &ctx)
118 .await
119 .map_err(anyhow::Error::from),
120 Commands::DynamicCompletion(args) => {
121 commands::dynamic_completion::execute_async(args, &ctx)
122 .await
123 .map_err(anyhow::Error::from)
124 }
125 }
126 };
127
128 let result = tokio::select! {
130 result = command_future => result,
131 _ = shutdown_token.cancelled() => {
132 return Ok(());
133 }
134 };
135
136 match result {
138 Ok(()) => Ok(()),
139 Err(e) => {
140 if let Some(cli_error) = e.downcast_ref::<error::CliError>() {
142 ctx.output.error(&cli_error.user_message())?;
143 if ctx.is_verbose() {
144 eprintln!("\nDebug info: {:?}", cli_error);
145 }
146 } else {
147 ctx.output.error(&format!("Error: {}", e))?;
148 if ctx.is_verbose() {
149 eprintln!("\nDebug info: {:?}", e);
150 }
151 }
152 std::process::exit(1);
153 }
154 }
155}
156
157pub struct CommandContext {
159 pub config: config::Config,
160 pub output: output::OutputManager,
161 pub log_level: LogLevel,
162 pub json: bool,
163 pub shutdown_token: tokio_util::sync::CancellationToken,
164}
165
166impl CommandContext {
167 pub fn is_verbose(&self) -> bool {
169 matches!(self.log_level, LogLevel::Debug | LogLevel::Trace)
170 }
171}