1use crate::graph::save_commodity_graphs_for_model;
3use crate::input::{load_commodity_graphs, load_model};
4use crate::log;
5use crate::output::{create_output_directory, get_graphs_dir, get_output_dir};
6use crate::settings::Settings;
7use ::log::{info, warn};
8use anyhow::{Context, Result};
9use clap::{Args, CommandFactory, Parser, Subcommand};
10use std::path::{Path, PathBuf};
11
12pub mod example;
13use example::ExampleSubcommands;
14
15pub mod settings;
16use settings::SettingsSubcommands;
17
18#[derive(Parser)]
20#[command(version, about)]
21struct Cli {
22 #[command(subcommand)]
24 command: Option<Commands>,
25 #[arg(long, hide = true)]
27 markdown_help: bool,
28}
29
30#[derive(Args)]
32pub struct RunOpts {
33 #[arg(short, long)]
35 pub output_dir: Option<PathBuf>,
36 #[arg(long)]
38 pub overwrite: bool,
39 #[arg(long, value_name = "BOOL", num_args = 0..=1, default_missing_value = "true")]
41 pub debug_model: Option<bool>,
42}
43
44#[derive(Args)]
46pub struct GraphOpts {
47 #[arg(short, long)]
49 pub output_dir: Option<PathBuf>,
50 #[arg(long)]
52 pub overwrite: bool,
53}
54
55#[derive(Subcommand)]
57enum Commands {
58 Run {
60 model_dir: PathBuf,
62 #[command(flatten)]
64 opts: RunOpts,
65 },
66 Example {
68 #[command(subcommand)]
70 subcommand: ExampleSubcommands,
71 },
72 Validate {
74 model_dir: PathBuf,
76 },
77 SaveGraphs {
79 model_dir: PathBuf,
81 #[command(flatten)]
83 opts: GraphOpts,
84 },
85 Settings {
87 #[command(subcommand)]
89 subcommand: SettingsSubcommands,
90 },
91}
92
93impl Commands {
94 fn execute(self) -> Result<()> {
96 match self {
97 Self::Run { model_dir, opts } => handle_run_command(&model_dir, &opts, None),
98 Self::Example { subcommand } => subcommand.execute(),
99 Self::Validate { model_dir } => handle_validate_command(&model_dir, None),
100 Self::SaveGraphs { model_dir, opts } => {
101 handle_save_graphs_command(&model_dir, &opts, None)
102 }
103 Self::Settings { subcommand } => subcommand.execute(),
104 }
105 }
106}
107
108pub fn run_cli() -> Result<()> {
110 let cli = Cli::parse();
111
112 if cli.markdown_help {
114 clap_markdown::print_help_markdown::<Cli>();
115 return Ok(());
116 }
117
118 if let Some(command) = cli.command {
119 command.execute()?;
120 } else {
121 Cli::command().print_long_help()?;
123 }
124
125 Ok(())
126}
127
128pub fn handle_run_command(
130 model_path: &Path,
131 opts: &RunOpts,
132 settings: Option<Settings>,
133) -> Result<()> {
134 let mut settings = if let Some(settings) = settings {
136 settings
137 } else {
138 Settings::load().context("Failed to load settings.")?
139 };
140
141 if let Some(opt) = opts.debug_model {
143 settings.debug_model = opt;
144 }
145 if opts.overwrite {
146 settings.overwrite = true;
147 }
148
149 let pathbuf: PathBuf;
151 let output_path = if let Some(p) = opts.output_dir.as_deref() {
152 p
153 } else {
154 pathbuf = get_output_dir(model_path)?;
155 &pathbuf
156 };
157
158 let overwrite =
159 create_output_directory(output_path, settings.overwrite).with_context(|| {
160 format!(
161 "Failed to create output directory: {}",
162 output_path.display()
163 )
164 })?;
165
166 log::init(&settings.log_level, Some(output_path)).context("Failed to initialise logging.")?;
168
169 info!("Starting MUSE2 v{}", env!("CARGO_PKG_VERSION"));
170
171 let (model, assets) = load_model(model_path).context("Failed to load model.")?;
173 info!("Loaded model from {}", model_path.display());
174 info!("Output folder: {}", output_path.display());
175
176 if overwrite {
178 warn!("Output folder will be overwritten");
179 }
180
181 crate::simulation::run(&model, assets, output_path, settings.debug_model)?;
183 info!("Simulation complete!");
184
185 Ok(())
186}
187
188pub fn handle_validate_command(model_path: &Path, settings: Option<Settings>) -> Result<()> {
190 let settings = if let Some(settings) = settings {
192 settings
193 } else {
194 Settings::load().context("Failed to load settings.")?
195 };
196
197 log::init(&settings.log_level, None).context("Failed to initialise logging.")?;
199
200 load_model(model_path).context("Failed to validate model.")?;
202 info!("Model validation successful!");
203
204 Ok(())
205}
206
207pub fn handle_save_graphs_command(
209 model_path: &Path,
210 opts: &GraphOpts,
211 settings: Option<Settings>,
212) -> Result<()> {
213 let settings = if let Some(settings) = settings {
215 settings
216 } else {
217 Settings::load().context("Failed to load settings.")?
218 };
219
220 let pathbuf: PathBuf;
222 let output_path = if let Some(p) = opts.output_dir.as_deref() {
223 p
224 } else {
225 pathbuf = get_graphs_dir(model_path)?;
226 &pathbuf
227 };
228
229 let overwrite =
230 create_output_directory(output_path, settings.overwrite).with_context(|| {
231 format!(
232 "Failed to create graphs directory: {}",
233 output_path.display()
234 )
235 })?;
236
237 log::init(&settings.log_level, None).context("Failed to initialise logging.")?;
239
240 if overwrite {
242 warn!("Graphs directory will be overwritten");
243 }
244
245 let commodity_graphs = load_commodity_graphs(model_path).context("Failed to build graphs.")?;
247 save_commodity_graphs_for_model(&commodity_graphs, output_path)?;
248 info!("Graphs saved to: {}", output_path.display());
249
250 Ok(())
251}