From dd2f41e3cf12c673bc8d596a3757b76c2d26ea57 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Sat, 18 Jan 2025 11:22:05 -0700 Subject: [PATCH] more functional handling of Quantity --- .../peristaltic-pump/bearing_roller.toml | 10 +-- .../peristaltic-pump/peristaltic_pump.toml | 14 ++-- .../peristaltic-pump/print/bearing_hub.toml | 2 +- .../print/bearing_hub_top.toml | 2 +- .../print/pump_housing_front_untapped.toml | 2 +- .../print/pump_housing_rear_untapped.toml | 2 +- .../peristaltic-pump/print/tube_support.toml | 2 +- .../peristaltic-pump/pump_housing_front.toml | 2 +- .../pump_housing_front_with_stepper.toml | 8 +-- .../peristaltic-pump/pump_housing_rear.toml | 4 +- .../pump_housing_rear_with_stepper.toml | 8 +-- .../recipes/purchase/bearing_624.toml | 4 +- .../recipes/purchase/filament.toml | 4 +- .../recipes/purchase/m4_20_shcs.toml | 4 +- .../recipes/purchase/m4_25_bhcs.toml | 4 +- .../recipes/purchase/m4_5_shcs.toml | 4 +- modular-recipes/recipes/purchase/m4_nuts.toml | 4 +- .../recipes/purchase/m4_washer.toml | 4 +- .../recipes/purchase/silicone_tubing.toml | 4 +- .../recipes/purchase/stepper_28byj_48.toml | 4 +- tools/src/recipe.rs | 66 +++++++++++------ tools/src/repo.rs | 72 +++++++++++-------- 22 files changed, 131 insertions(+), 99 deletions(-) diff --git a/modular-recipes/recipes/peristaltic-pump/bearing_roller.toml b/modular-recipes/recipes/peristaltic-pump/bearing_roller.toml index 39e859c..068dd32 100644 --- a/modular-recipes/recipes/peristaltic-pump/bearing_roller.toml +++ b/modular-recipes/recipes/peristaltic-pump/bearing_roller.toml @@ -1,5 +1,5 @@ [outputs] -bearing_roller = {quantity=1} +bearing_roller = {quantity={amount=1}} # inputs list, similar to a cargo.toml file [inputs] @@ -9,25 +9,25 @@ bearing_hub = {version=0.1, type="printed"} # alternate representation of an element of the inputs list [inputs.m4_20_shcs] type = "M4x20 SHCS" -quantity = 3 +quantity = { amount=3 } comment = """These will hold the bearing rollers. Possibly Button Heads Cap Screws would be better? Probably either will work fine.""" [inputs.m4_washer] type = "M4 washer" -quantity = 6 +quantity = { amount=6 } dimensions = "12 mm OD, 4.4 mm ID, 1 mm thick" comment = """The upstream repo calls for 12 mm OD but i think that's too big, it hits the outer race of the bearings. Use 8mm OD instead.""" [inputs.bearing_624] type = "624 bearing" -quantity = 6 +quantity = { amount=6 } comments = "13mm OD, 4mm ID, 5mm wide" [inputs.m4_nuts] type = "M4 nuts" -quantity = 3 +quantity = { amount=3 } [dependencies] tools = [ diff --git a/modular-recipes/recipes/peristaltic-pump/peristaltic_pump.toml b/modular-recipes/recipes/peristaltic-pump/peristaltic_pump.toml index 365334b..9d49c43 100644 --- a/modular-recipes/recipes/peristaltic-pump/peristaltic_pump.toml +++ b/modular-recipes/recipes/peristaltic-pump/peristaltic_pump.toml @@ -1,24 +1,24 @@ [inputs] -pump_housing_front_with_stepper = {quantity=1} -bearing_roller = {quantity=1} -pump_housing_rear_with_stepper = {quantity=1} -tube_support = {quantity=1} +pump_housing_front_with_stepper = {quantity={amount=1}} +bearing_roller = {quantity={amount=1}} +pump_housing_rear_with_stepper = {quantity={amount=1}} +tube_support = {quantity={amount=1}} [inputs.silicone_tubing] type = "silicone tubing, 6mm OD, 4mm ID" -amount = "1 meter" +quantity = { amount = 1, unit="Meter" } comment = "PVC tube isn't flexible enough. Adjust quantity to suit your application." [inputs.m4_25_bhcs] name = "m4_25_bhcs" type = "M4x25 BHCS" -quantity = 4 +quantity = { amount = 4 } comment = """BHCS is preferred over SHCS so the screw heads sit recessed below the surface of the pump body.""" [inputs.m4_nuts] type = "M4 nuts" -quantity = 4 +quantity = { amount = 4 } [dependencies] tools = [ "3 mm hex driver" ] diff --git a/modular-recipes/recipes/peristaltic-pump/print/bearing_hub.toml b/modular-recipes/recipes/peristaltic-pump/print/bearing_hub.toml index ae94262..85b4991 100644 --- a/modular-recipes/recipes/peristaltic-pump/print/bearing_hub.toml +++ b/modular-recipes/recipes/peristaltic-pump/print/bearing_hub.toml @@ -1,5 +1,5 @@ [outputs] -bearing_hub={version=0.1, quantity=1, type="printed"} +bearing_hub={version=0.1, quantity={amount=1}, type="printed"} [inputs] filament={} diff --git a/modular-recipes/recipes/peristaltic-pump/print/bearing_hub_top.toml b/modular-recipes/recipes/peristaltic-pump/print/bearing_hub_top.toml index 8b12431..7c33796 100644 --- a/modular-recipes/recipes/peristaltic-pump/print/bearing_hub_top.toml +++ b/modular-recipes/recipes/peristaltic-pump/print/bearing_hub_top.toml @@ -1,5 +1,5 @@ [outputs] -bearing_hub_top={version=0.1, quantity=1, type="printed"} +bearing_hub_top={version=0.1, quantity={amount=1}, type="printed"} [inputs] filament={} diff --git a/modular-recipes/recipes/peristaltic-pump/print/pump_housing_front_untapped.toml b/modular-recipes/recipes/peristaltic-pump/print/pump_housing_front_untapped.toml index 7ae6b11..623beda 100644 --- a/modular-recipes/recipes/peristaltic-pump/print/pump_housing_front_untapped.toml +++ b/modular-recipes/recipes/peristaltic-pump/print/pump_housing_front_untapped.toml @@ -1,5 +1,5 @@ [outputs] -pump_housing_front_untapped={version=0.1, quantity=1, type="printed"} +pump_housing_front_untapped={version=0.1, quantity={amount=1}, type="printed"} [inputs] filament={} diff --git a/modular-recipes/recipes/peristaltic-pump/print/pump_housing_rear_untapped.toml b/modular-recipes/recipes/peristaltic-pump/print/pump_housing_rear_untapped.toml index 7009dbd..700d4b8 100644 --- a/modular-recipes/recipes/peristaltic-pump/print/pump_housing_rear_untapped.toml +++ b/modular-recipes/recipes/peristaltic-pump/print/pump_housing_rear_untapped.toml @@ -1,5 +1,5 @@ [outputs] -pump_housing_rear_untapped={version=0.1, quantity=1, type="printed"} +pump_housing_rear_untapped={version=0.1, quantity={amount=1}, type="printed"} [inputs] filament={} diff --git a/modular-recipes/recipes/peristaltic-pump/print/tube_support.toml b/modular-recipes/recipes/peristaltic-pump/print/tube_support.toml index 57c7440..7e4e72b 100644 --- a/modular-recipes/recipes/peristaltic-pump/print/tube_support.toml +++ b/modular-recipes/recipes/peristaltic-pump/print/tube_support.toml @@ -1,5 +1,5 @@ [outputs] -tube_support={version=0.1, quantity=1, type="printed"} +tube_support={version=0.1, quantity={amount=1}, type="printed"} [inputs] filament={} diff --git a/modular-recipes/recipes/peristaltic-pump/pump_housing_front.toml b/modular-recipes/recipes/peristaltic-pump/pump_housing_front.toml index 853d3ce..e47b83d 100644 --- a/modular-recipes/recipes/peristaltic-pump/pump_housing_front.toml +++ b/modular-recipes/recipes/peristaltic-pump/pump_housing_front.toml @@ -1,5 +1,5 @@ [outputs] -pump_housing_front={quantity=1} +pump_housing_front={quantity={amount=1}} [inputs] pump_housing_front_untapped={version=0.1, type="printed"} diff --git a/modular-recipes/recipes/peristaltic-pump/pump_housing_front_with_stepper.toml b/modular-recipes/recipes/peristaltic-pump/pump_housing_front_with_stepper.toml index f9c50e4..03c89fb 100644 --- a/modular-recipes/recipes/peristaltic-pump/pump_housing_front_with_stepper.toml +++ b/modular-recipes/recipes/peristaltic-pump/pump_housing_front_with_stepper.toml @@ -1,10 +1,10 @@ [outputs] -pump_housing_front_with_stepper={quantity=1} +pump_housing_front_with_stepper={quantity={amount=1}} [inputs] -pump_housing_front={version=0.1, quantity=1} -m4_5_shcs={quantity=2, description="M4x5 SHCS"} -stepper_28byj_48={quantity=1} +pump_housing_front={version=0.1, quantity={amount=1}} +m4_5_shcs={quantity={amount=2}, description="M4x5 SHCS"} +stepper_28byj_48={quantity={amount=1}} [dependencies] tools = [ "3 mm hex driver" ] diff --git a/modular-recipes/recipes/peristaltic-pump/pump_housing_rear.toml b/modular-recipes/recipes/peristaltic-pump/pump_housing_rear.toml index 6202293..5fa4504 100644 --- a/modular-recipes/recipes/peristaltic-pump/pump_housing_rear.toml +++ b/modular-recipes/recipes/peristaltic-pump/pump_housing_rear.toml @@ -1,8 +1,8 @@ [outputs] -pump_housing_rear = {quantity=1} +pump_housing_rear = {quantity={amount=1}} [inputs] -pump_housing_rear_untapped = {quantity=1} +pump_housing_rear_untapped = {quantity={amount=1}} [dependencies] tools = [ "M4 tap" ] diff --git a/modular-recipes/recipes/peristaltic-pump/pump_housing_rear_with_stepper.toml b/modular-recipes/recipes/peristaltic-pump/pump_housing_rear_with_stepper.toml index 8ed01ef..8bfe648 100644 --- a/modular-recipes/recipes/peristaltic-pump/pump_housing_rear_with_stepper.toml +++ b/modular-recipes/recipes/peristaltic-pump/pump_housing_rear_with_stepper.toml @@ -1,10 +1,10 @@ [outputs] -pump_housing_rear_with_stepper = {quantity=1} +pump_housing_rear_with_stepper = {quantity={amount=1}} [inputs] -pump_housing_rear = {version=0.1, quantity=1} -m4_5_shcs = {quantity=2} -stepper_28byj_48={quantity=1} +pump_housing_rear = {version=0.1, quantity={amount=1}} +m4_5_shcs = {quantity={amount=2}} +stepper_28byj_48={quantity={amount=1}} [dependencies] tools = [ "3 mm hex driver" ] diff --git a/modular-recipes/recipes/purchase/bearing_624.toml b/modular-recipes/recipes/purchase/bearing_624.toml index 6c7cfd8..27194ef 100644 --- a/modular-recipes/recipes/purchase/bearing_624.toml +++ b/modular-recipes/recipes/purchase/bearing_624.toml @@ -1,9 +1,9 @@ [outputs.bearing_624] -quantity=10 +quantity = { amount=10 } comments = "13mm OD, 4mm ID, 5mm wide" [inputs] -capital={amount="8.99 usd"} +capital={quantity={amount=8.99, unit="USDollar"}} [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/filament.toml b/modular-recipes/recipes/purchase/filament.toml index 684e02f..3410ffd 100644 --- a/modular-recipes/recipes/purchase/filament.toml +++ b/modular-recipes/recipes/purchase/filament.toml @@ -1,8 +1,8 @@ [outputs] -filament = {amount="1 kg"} +filament = { quantity = { amount = 1000, unit = "Gram" } } [inputs] -capital={amount="23.99 usd"} +capital={ quantity = { amount=23.99, unit="USDollar" } } [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/m4_20_shcs.toml b/modular-recipes/recipes/purchase/m4_20_shcs.toml index 657259b..c430640 100644 --- a/modular-recipes/recipes/purchase/m4_20_shcs.toml +++ b/modular-recipes/recipes/purchase/m4_20_shcs.toml @@ -1,8 +1,8 @@ [outputs] -m4_20_shcs = {quantity=100} +m4_20_shcs = {quantity={amount=100}} [inputs] -capital={amount="15.49 usd"} +capital={quantity={amount=15.49, unit="USDollar"}} [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/m4_25_bhcs.toml b/modular-recipes/recipes/purchase/m4_25_bhcs.toml index 91628ec..ffc5d3b 100644 --- a/modular-recipes/recipes/purchase/m4_25_bhcs.toml +++ b/modular-recipes/recipes/purchase/m4_25_bhcs.toml @@ -1,8 +1,8 @@ [outputs] -m4_25_bhcs = {quantity=100} +m4_25_bhcs = {quantity={amount=100}} [inputs] -capital={amount="18.45 usd"} +capital={quantity={amount=18.45, unit="USDollar"}} [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/m4_5_shcs.toml b/modular-recipes/recipes/purchase/m4_5_shcs.toml index 727355a..5c1d473 100644 --- a/modular-recipes/recipes/purchase/m4_5_shcs.toml +++ b/modular-recipes/recipes/purchase/m4_5_shcs.toml @@ -1,8 +1,8 @@ [outputs] -m4_20_shcs = {quantity=100} +m4_20_shcs = {quantity={amount=100}} [inputs] -capital={amount="13.65 usd"} +capital={quantity={amount=13.65, unit="USDollar"}} [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/m4_nuts.toml b/modular-recipes/recipes/purchase/m4_nuts.toml index 0e7087a..9ea586a 100644 --- a/modular-recipes/recipes/purchase/m4_nuts.toml +++ b/modular-recipes/recipes/purchase/m4_nuts.toml @@ -1,8 +1,8 @@ [outputs] -m4_nuts = {quantity=100} +m4_nuts = {quantity={amount=100}} [inputs] -capital={amount="1.35 usd"} +capital = { quantity = { amount = 1.35, unit = "USDollar" } } [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/m4_washer.toml b/modular-recipes/recipes/purchase/m4_washer.toml index 8f39e76..5a1150c 100644 --- a/modular-recipes/recipes/purchase/m4_washer.toml +++ b/modular-recipes/recipes/purchase/m4_washer.toml @@ -1,9 +1,9 @@ [outputs.m4_washer] -quantity=100 +quantity = { amount=100 } comments = "4.4 mm ID, 8.8 mm OD, 0.8 mm thick" [inputs] -capital={amount="7.18 usd"} +capital={quantity={amount=7.18, unit="USDollar"}} [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/silicone_tubing.toml b/modular-recipes/recipes/purchase/silicone_tubing.toml index 9f8c7b4..91c730f 100644 --- a/modular-recipes/recipes/purchase/silicone_tubing.toml +++ b/modular-recipes/recipes/purchase/silicone_tubing.toml @@ -1,8 +1,8 @@ [outputs] -silicone_tubing = {amount="3 m"} +silicone_tubing = {quantity={amount=3, unit="Meter"}} [inputs] -capital={amount="18.75 usd"} +capital={quantity={amount=18.75, unit="USDollar"}} [dependencies] operator = {skills=["vendor interaction"]} diff --git a/modular-recipes/recipes/purchase/stepper_28byj_48.toml b/modular-recipes/recipes/purchase/stepper_28byj_48.toml index cf502be..bdc5d43 100644 --- a/modular-recipes/recipes/purchase/stepper_28byj_48.toml +++ b/modular-recipes/recipes/purchase/stepper_28byj_48.toml @@ -1,8 +1,8 @@ [outputs] -stepper_28byj_48 = {quantity=1} +stepper_28byj_48 = {quantity={amount=1}} [inputs] -capital={amount="1.35 usd"} +capital={quantity={amount=1.35, unit="USDollar"}} [dependencies] operator = {skills=["vendor interaction"]} diff --git a/tools/src/recipe.rs b/tools/src/recipe.rs index 134384f..0571db1 100644 --- a/tools/src/recipe.rs +++ b/tools/src/recipe.rs @@ -4,34 +4,30 @@ // Amount(String), // } +#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize)] +pub enum Unit { + USDollar, + Meter, + Gram, +} + +/// `Quantity` measures the amount of a resource (Input or Output). +#[derive(Clone, Copy, Debug, serde::Deserialize)] +pub struct Quantity { + pub amount: f32, + pub unit: Option, +} + #[derive(Debug, serde::Deserialize)] pub struct Input { - /// Each input has either a `quantity` (for discrete things, like - /// apples or bearings) or an `amount` (for continuous things, - /// like rope or flour). - /// - /// FIXME: Is there a good way to handle the "units" of the - /// quantity/amount? Maybe the units could default to "count", but - /// be optionally overridden by the recipe to things like "meters" - /// (of tubing) or "grams" (of chromic acid). Then this struct - /// would be `quantity: usize, units: Option`. - pub quantity: Option, - pub amount: Option, + // Quantity defaults to "amount=1" if omitted. + pub quantity: Option, } #[derive(Debug, serde::Deserialize)] pub struct Output { - /// Each output has either a `quantity` (for discrete things, like - /// apples or bearings) or an `amount` (for continuous things, - /// like rope or flour). - /// - /// FIXME: Is there a good way to handle the "units" of the - /// quantity/amount? Maybe the units could default to "count", but - /// be optionally overridden by the recipe to things like "meters" - /// (of tubing) or "grams" (of chromic acid). Then this struct - /// would be `quantity: usize, units: Option`. - pub quantity: Option, - pub amount: Option, + // Quantity defaults to "amount=1" if omitted. + pub quantity: Option, } #[derive(Debug, serde::Deserialize)] @@ -77,6 +73,30 @@ pub struct Recipe { pub outputs: Option>, } +impl Input { + pub fn get_quantity(&self) -> Quantity { + match &self.quantity { + None => Quantity { + amount: 1.0, + unit: None, + }, + Some(q) => q.clone(), + } + } +} + +impl Output { + pub fn get_quantity(&self) -> Quantity { + match &self.quantity { + None => Quantity { + amount: 1.0, + unit: None, + }, + Some(q) => q.clone(), + } + } +} + impl Recipe { pub fn from_file(file: &std::path::PathBuf) -> anyhow::Result { let recipe_contents = std::fs::read_to_string(file)?; @@ -84,7 +104,9 @@ impl Recipe { // let r = recipe.validate_recipe(); Ok(recipe) } +} +impl Recipe { fn validate_recipe(self: &Self) -> anyhow::Result<()> { // if recipe.inputs.len() == 0 { // Err("recipe has no inputs!"); diff --git a/tools/src/repo.rs b/tools/src/repo.rs index 43ad28a..727cae1 100644 --- a/tools/src/repo.rs +++ b/tools/src/repo.rs @@ -85,51 +85,61 @@ impl Repo { } fn compile_inner(self: &Self, recipe: &Recipe, indent: usize) -> anyhow::Result<()> { - for (key, val) in recipe.inputs.iter() { + for (input_name, input_info) in recipe.inputs.iter() { for _ in 0..indent { print!(" "); } - if key == "capital" { + if input_name == "capital" { // The build process begins in capitalism :-( - if let Some(amount) = &val.amount { - // I'd like to divide the amount of capital by the - // quantity of the thing purchased. - // - // We can probably assume the `recipe.outputs` Option - // is Some, but inside the Some is a container with - // potentially any number of output things, which - // makes it hard to account. - // - // For now i'll just handle the common case specially. + let input_quantity = input_info.get_quantity(); - if let Some(a) = amount.split_whitespace().next() { - let cost = a.parse::().unwrap(); - if let Some(outputs) = &recipe.outputs { - if outputs.len() == 1 { - for (_k, v) in outputs.iter() { - if let Some(quantity) = &v.quantity { - println!( - "capital: {cost}/{quantity} = {:.2} each :-(", - cost / *quantity as f32 - ); - } else if let Some(amount) = &v.amount { - println!("capital: {cost}/{amount:?} :-(",); - } - } - } + let input_unit = match &input_quantity.unit { + None => { + return Err(anyhow::Error::msg(format!( + "expected quantity unit USDollar on capital input" + ))) + } + Some(unit) if *unit != crate::recipe::Unit::USDollar => { + return Err(anyhow::Error::msg(format!( + "expected quantity unit USDollar on capital input" + ))) + } + Some(unit) => unit, + }; + + let cost = input_quantity.amount; + + let outputs = match &recipe.outputs { + None => return Err(anyhow::Error::msg(format!("no outputs!"))), + Some(outputs) => outputs, + }; + + if outputs.len() == 1 { + for (_output_name, output_info) in outputs.iter() { + let output_quantity = output_info.get_quantity(); + let cost_each = cost / output_quantity.amount; + if let Some(output_unit) = output_quantity.unit { + println!( + "capital: {:.3} {:?} / {:?} :-(", + cost_each, input_unit, output_unit + ); + } else { + println!("capital: {:.3} {:?} each :-(", cost_each, input_unit); } } } else { - panic!("no amount of capital?"); + return Err(anyhow::Error::msg(format!("no output!"))); } continue; } - println!("{key:?} {val:?}"); - match self.get_recipe(key) { + println!("{input_name:?} {input_info:?}"); + match self.get_recipe(input_name) { None => { - return Err(anyhow::Error::msg(format!("recipe for {key} not found"))); + return Err(anyhow::Error::msg(format!( + "recipe for {input_name} not found" + ))); } Some(input_recipe) => { self.compile_inner(input_recipe, indent + 4)?;