1use crate::graph::save_commodity_graphs_for_model;
3use crate::input::{load_commodity_graphs, load_model};
4use crate::log;
5use crate::output::{copy_input_files, 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)]
41 pub debug_model: bool,
42
43 #[arg(long)]
45 pub no_copy_input_files: bool,
46}
47
48#[derive(Args)]
50pub struct GraphOpts {
51 #[arg(short, long)]
53 pub output_dir: Option<PathBuf>,
54 #[arg(long)]
56 pub overwrite: bool,
57}
58
59#[derive(Subcommand)]
61enum Commands {
62 Run {
64 model_dir: PathBuf,
66 #[command(flatten)]
68 opts: RunOpts,
69 },
70 Example {
72 #[command(subcommand)]
74 subcommand: ExampleSubcommands,
75 },
76 Validate {
78 model_dir: PathBuf,
80 },
81 SaveGraphs {
83 model_dir: PathBuf,
85 #[command(flatten)]
87 opts: GraphOpts,
88 },
89 Settings {
91 #[command(subcommand)]
93 subcommand: SettingsSubcommands,
94 },
95}
96
97impl Commands {
98 fn execute(self) -> Result<()> {
100 match self {
101 Self::Run { model_dir, opts } => handle_run_command(&model_dir, &opts),
102 Self::Example { subcommand } => subcommand.execute(),
103 Self::Validate { model_dir } => handle_validate_command(&model_dir),
104 Self::SaveGraphs { model_dir, opts } => handle_save_graphs_command(&model_dir, &opts),
105 Self::Settings { subcommand } => subcommand.execute(),
106 }
107 }
108}
109
110pub fn run_cli() -> Result<()> {
112 let cli = Cli::parse();
113
114 if cli.markdown_help {
116 clap_markdown::print_help_markdown::<Cli>();
117 return Ok(());
118 }
119
120 if let Some(command) = cli.command {
121 command.execute()?;
122 } else {
123 Cli::command().print_long_help()?;
125 }
126
127 Ok(())
128}
129
130pub fn handle_run_command(model_path: &Path, opts: &RunOpts) -> Result<()> {
132 let mut settings = Settings::load_or_default().context("Failed to load settings.")?;
133
134 if opts.debug_model {
136 settings.debug_model = true;
137 }
138 if opts.overwrite {
139 settings.overwrite = true;
140 }
141 if opts.no_copy_input_files {
142 settings.copy_input_files = false;
143 }
144
145 let pathbuf: PathBuf;
147 let output_path = if let Some(p) = opts.output_dir.as_deref() {
148 p
149 } else {
150 pathbuf = get_output_dir(model_path, settings.results_root)?;
151 &pathbuf
152 };
153
154 let overwrite =
155 create_output_directory(output_path, settings.overwrite).with_context(|| {
156 format!(
157 "Failed to create output directory: {}",
158 output_path.display()
159 )
160 })?;
161
162 let model_path = model_path
163 .canonicalize()
164 .context("Failed to resolve model path.")?;
165 let model_name = model_path
166 .file_name()
167 .context("Model cannot be the root directory.")?
168 .to_str()
169 .context("Invalid chars in model directory name")?;
170
171 if settings.copy_input_files {
172 copy_input_files(&model_path, output_path, model_name)
173 .context("Failed to copy input files to output directory.")?;
174 }
175 log::init(&settings.log_level, Some(output_path)).context("Failed to initialise logging.")?;
177
178 info!("Starting MUSE2 v{}", env!("CARGO_PKG_VERSION"));
179
180 let model = load_model(&model_path).context("Failed to load model.")?;
182 info!("Loaded model from {}", model_path.display());
183 info!("Output folder: {}", output_path.display());
184
185 if overwrite {
187 warn!("Output folder will be overwritten");
188 }
189
190 crate::simulation::run(&model, output_path, settings.debug_model)?;
192 info!("Simulation complete!");
193
194 Ok(())
195}
196
197pub fn handle_validate_command(model_path: &Path) -> Result<()> {
199 let settings = Settings::load_or_default().context("Failed to load settings.")?;
200
201 log::init(&settings.log_level, None).context("Failed to initialise logging.")?;
203
204 load_model(model_path).context("Failed to validate model.")?;
206 info!("Model validation successful!");
207
208 Ok(())
209}
210
211pub fn handle_save_graphs_command(model_path: &Path, opts: &GraphOpts) -> Result<()> {
213 let mut settings = Settings::load_or_default().context("Failed to load settings.")?;
214
215 if opts.overwrite {
216 settings.overwrite = true;
217 }
218
219 let pathbuf: PathBuf;
221 let output_path = if let Some(p) = opts.output_dir.as_deref() {
222 p
223 } else {
224 pathbuf = get_graphs_dir(model_path, settings.graph_results_root)?;
225 &pathbuf
226 };
227
228 let overwrite =
229 create_output_directory(output_path, settings.overwrite).with_context(|| {
230 format!(
231 "Failed to create graphs directory: {}",
232 output_path.display()
233 )
234 })?;
235
236 log::init(&settings.log_level, None).context("Failed to initialise logging.")?;
238
239 if overwrite {
241 warn!("Graphs directory will be overwritten");
242 }
243
244 let commodity_graphs = load_commodity_graphs(model_path).context("Failed to build graphs.")?;
246 save_commodity_graphs_for_model(&commodity_graphs, output_path)?;
247 info!("Graphs saved to: {}", output_path.display());
248
249 Ok(())
250}