use std::error::Error;
use clap::{Args, Subcommand};
use libprintcalc::model::filament::{MaterialKind, MaterialProfile};
use crate::interactive;
use crate::ops;
#[derive(Args)]
pub struct FilamentArgs {
#[command(subcommand)]
pub command: FilamentCommand,
}
#[derive(Args)]
pub struct FilamentFields {
#[arg(short, long)]
kind: Option<String>,
#[arg(long)]
price: Option<f64>,
#[arg(long = "efficiency-factor")]
efficiency_factor: Option<f64>,
#[arg(long)]
color: Option<String>,
#[arg(long)]
vendor: Option<String>,
}
#[derive(Subcommand)]
pub enum FilamentCommand {
Add {
#[arg(short, long)]
name: Option<String>,
#[command(flatten)]
fields: FilamentFields,
},
Edit {
name: String,
#[command(flatten)]
fields: FilamentFields,
},
#[command(visible_alias = "rm")]
Remove {
name: String,
},
List,
Show {
name: String,
},
}
pub fn run(cmd: FilamentCommand, interactive_mode: bool) -> Result<(), Box<dyn Error>> {
match cmd {
FilamentCommand::Add { name, fields } => {
let name =
interactive::require_string(name, "name", "Filament name", interactive_mode)?;
let kind = parse_kind(fields.kind.as_deref())?.unwrap_or(MaterialKind::Filament);
let base = MaterialProfile::new(name.clone(), kind);
let profile = resolve_profile(&fields, base, interactive_mode)?;
ops::add_filament(&profile)?;
println!("saved filament {name:?}");
Ok(())
}
FilamentCommand::Edit { name, fields } => {
let mut current = ops::show_filament(&name)?;
let shadows_builtin = !ops::list_saved_filaments()?.contains(&name);
if let Some(kind) = parse_kind(fields.kind.as_deref())? {
current.kind = kind;
}
let profile = resolve_profile(&fields, current, interactive_mode)?;
ops::add_filament(&profile)?;
if shadows_builtin {
println!("saved filament {name:?} (shadows the built-in preset)");
} else {
println!("updated filament {name:?}");
}
Ok(())
}
FilamentCommand::Remove { name } => {
ops::remove_filament(&name)?;
println!("removed filament {name:?}");
Ok(())
}
FilamentCommand::List => {
for name in ops::list_filaments()? {
println!("{name}");
}
Ok(())
}
FilamentCommand::Show { name } => {
let profile = ops::show_filament(&name)?;
print_filament(&profile);
Ok(())
}
}
}
fn parse_kind(kind: Option<&str>) -> Result<Option<MaterialKind>, Box<dyn Error>> {
match kind {
None => Ok(None),
Some("filament") => Ok(Some(MaterialKind::Filament)),
Some("resin") => Ok(Some(MaterialKind::Resin)),
Some(other) => {
Err(format!("invalid --kind {other:?} (expected \"filament\" or \"resin\")").into())
}
}
}
fn resolve_profile(
fields: &FilamentFields,
mut base: MaterialProfile,
interactive_mode: bool,
) -> Result<MaterialProfile, Box<dyn Error>> {
base.price = interactive::defaulted_f64(
fields.price,
&format!("Price per {}", base.kind.price_unit()),
base.price,
interactive_mode,
)?;
base.efficiency_factor = interactive::defaulted_f64(
fields.efficiency_factor,
"Efficiency factor",
base.efficiency_factor,
interactive_mode,
)?;
if fields.color.is_some() {
base.color = fields.color.clone();
}
if fields.vendor.is_some() {
base.vendor = fields.vendor.clone();
}
Ok(base)
}
fn print_filament(f: &MaterialProfile) {
println!("name: {}", f.name);
println!("kind: {:?}", f.kind);
let price_unit = f.kind.price_unit();
println!("price: {} per {price_unit}", f.price);
println!("efficiency_factor: {}", f.efficiency_factor);
if let Some(c) = &f.color {
println!("color: {c}");
}
if let Some(v) = &f.vendor {
println!("vendor: {v}");
}
}