use std::path::{Path, PathBuf};
use std::process::ExitCode;
use anyhow::{Context as _, bail};
use clap::{Parser, Subcommand};
use crate::kinds::ModelKind;
use crate::{fmu_wrap, import, inspect, run, scaffold, tui};
#[derive(Debug, Parser)]
#[command(name = "taktwerk", version, about)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Check {
project: PathBuf,
},
Run {
project: PathBuf,
},
Inspect {
model: PathBuf,
#[arg(long, value_enum)]
kind: ModelKind,
},
New {
model: PathBuf,
#[arg(long, value_enum)]
kind: ModelKind,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
write: bool,
#[arg(long)]
force: bool,
#[arg(long, default_value_t = 10.0)]
tick_ms: f64,
#[arg(long, default_value_t = 4840)]
port: u16,
},
ImportHeader(import::Args),
FmuWrap(fmu_wrap::Args),
Tui {
endpoint: String,
#[arg(long, default_value = "urn:taktwerk")]
namespace: String,
#[arg(long, default_value = "taktwerk")]
prefix: String,
},
}
pub fn dispatch(command: Command) -> anyhow::Result<ExitCode> {
match command {
Command::Check { project } => run::check(&project),
Command::Run { project } => run::run(&project),
Command::Inspect { model, kind } => {
let adapter = kind.load(&model).map_err(anyhow::Error::msg)?;
print!("{}", inspect::interface(kind.as_str(), adapter.interface()));
Ok(ExitCode::SUCCESS)
}
Command::New {
model,
kind,
output,
write,
force,
tick_ms,
port,
} => new(&model, kind, output, write, force, tick_ms, port),
Command::ImportHeader(args) => import::run(&args),
Command::FmuWrap(args) => fmu_wrap::run(&args),
Command::Tui {
endpoint,
namespace,
prefix,
} => tui::run(&endpoint, &namespace, &prefix),
}
}
fn new(
model: &Path,
kind: ModelKind,
output: Option<PathBuf>,
write: bool,
force: bool,
tick_ms: f64,
port: u16,
) -> anyhow::Result<ExitCode> {
if !(tick_ms.is_finite() && tick_ms > 0.0) {
bail!("--tick-ms must be a positive number");
}
let adapter = kind.load(model).map_err(anyhow::Error::msg)?;
let iface = adapter.interface();
let target = match (output, write) {
(Some(path), _) => Some(path),
(None, true) => Some(PathBuf::from("taktwerk.toml")),
(None, false) => None,
};
let base = target
.as_deref()
.and_then(Path::parent)
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
let path = scaffold::relative(&std::path::absolute(model)?, &std::path::absolute(base)?);
let stem = model
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or_default();
let id = scaffold::id_from(if iface.name.is_empty() {
stem
} else {
&iface.name
});
let text = scaffold::project(&scaffold::Scaffold {
id,
kind: kind.as_str(),
path: path.to_string_lossy().into_owned(),
interface: iface,
tick_ms,
port,
});
match target {
None => print!("{text}"),
Some(file) => {
if file.exists() && !force {
bail!("{} exists; pass --force to overwrite", file.display());
}
std::fs::write(&file, text).with_context(|| format!("write {}", file.display()))?;
eprintln!("wrote {}", file.display());
}
}
Ok(ExitCode::SUCCESS)
}