From a53eb41389121fae34e23eccb64dc58fe7398a06 Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Sun, 6 Sep 2026 21:23:16 -0500 Subject: [PATCH 1/2] UoM can be parsed by Clap Allow units-of-measure types (e.g. volume, length) to be used by `clap` for deserializing command line args from strings into those enums. --- Cargo.lock | 9 +++++---- modeling-cmds/Cargo.toml | 2 ++ modeling-cmds/src/units.rs | 7 +++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c992aa6..8e218746 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -741,9 +741,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.4" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -751,9 +751,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -2605,6 +2605,7 @@ dependencies = [ "bon", "bson", "chrono", + "clap", "cxx", "data-encoding", "dropshot", diff --git a/modeling-cmds/Cargo.toml b/modeling-cmds/Cargo.toml index 70857489..f8854a2b 100644 --- a/modeling-cmds/Cargo.toml +++ b/modeling-cmds/Cargo.toml @@ -13,6 +13,7 @@ rust-version = "1.96" all-features = true [features] +clap = ["dep:clap"] default = [] derive-jsonschema-on-enums = [] tabled = ["dep:tabled"] @@ -31,6 +32,7 @@ anyhow = "1.0.101" arbitrary = { version = "1", features = ["derive"], optional = true } bon = "3.9.1" chrono = { version = "0.4.43", features = ["serde"] } +clap = { version = "4.6.6", optional = true, default-features = false, features = ["derive"] } cxx = { version = "1.0", optional = true } data-encoding = "2.11.0" enum-iterator = "2.3.0" diff --git a/modeling-cmds/src/units.rs b/modeling-cmds/src/units.rs index b3aaa85d..a91cc2b9 100644 --- a/modeling-cmds/src/units.rs +++ b/modeling-cmds/src/units.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "clap")] +use clap::ValueEnum; use kittycad_unit_conversion_derive::UnitConversion; use parse_display_derive::{Display, FromStr}; use schemars::JsonSchema; @@ -24,6 +26,7 @@ use crate::impl_extern_type; UnitConversion, Hash, )] +#[cfg_attr(feature = "clap", derive(ValueEnum))] #[cfg_attr(feature = "tabled", derive(tabled::Tabled))] #[display(style = "snake_case")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] @@ -100,6 +103,7 @@ impl_extern_type! { UnitConversion, Hash, )] +#[cfg_attr(feature = "clap", derive(ValueEnum))] #[cfg_attr(feature = "tabled", derive(tabled::Tabled))] #[serde(rename_all = "snake_case")] #[display(style = "snake_case")] @@ -139,6 +143,7 @@ pub enum UnitAngle { Default, Hash, )] +#[cfg_attr(feature = "clap", derive(ValueEnum))] #[cfg_attr(feature = "tabled", derive(tabled::Tabled))] #[serde(rename_all = "snake_case")] #[display(style = "snake_case")] @@ -219,6 +224,7 @@ impl UnitArea { UnitConversion, Hash, )] +#[cfg_attr(feature = "clap", derive(ValueEnum))] #[cfg_attr(feature = "tabled", derive(tabled::Tabled))] #[display(style = "snake_case")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] @@ -282,6 +288,7 @@ impl UnitDensity { UnitConversion, Hash, )] +#[cfg_attr(feature = "clap", derive(ValueEnum))] #[cfg_attr(feature = "tabled", derive(tabled::Tabled))] #[serde(rename_all = "snake_case")] #[display(style = "snake_case")] From 52ecdb59580435964776ad458e2036b73a4ef7d7 Mon Sep 17 00:00:00 2001 From: Jordan Noone Date: Sun, 6 Sep 2026 20:32:15 -0700 Subject: [PATCH 2/2] Preserve unit spellings in Clap parsers --- modeling-cmds/Cargo.toml | 2 +- modeling-cmds/src/units.rs | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/modeling-cmds/Cargo.toml b/modeling-cmds/Cargo.toml index f8854a2b..1cf494ea 100644 --- a/modeling-cmds/Cargo.toml +++ b/modeling-cmds/Cargo.toml @@ -32,7 +32,7 @@ anyhow = "1.0.101" arbitrary = { version = "1", features = ["derive"], optional = true } bon = "3.9.1" chrono = { version = "0.4.43", features = ["serde"] } -clap = { version = "4.6.6", optional = true, default-features = false, features = ["derive"] } +clap = { version = "4.6.6", optional = true, default-features = false, features = ["derive", "std"] } cxx = { version = "1.0", optional = true } data-encoding = "2.11.0" enum-iterator = "2.3.0" diff --git a/modeling-cmds/src/units.rs b/modeling-cmds/src/units.rs index a91cc2b9..e2cf1cf0 100644 --- a/modeling-cmds/src/units.rs +++ b/modeling-cmds/src/units.rs @@ -41,27 +41,33 @@ pub enum UnitLength { /// Centimeters #[serde(rename = "cm")] #[display("cm")] + #[cfg_attr(feature = "clap", value(name = "cm"))] Centimeters, /// Feet #[serde(rename = "ft")] #[display("ft")] + #[cfg_attr(feature = "clap", value(name = "ft"))] Feet, /// Inches #[serde(rename = "in")] #[display("in")] + #[cfg_attr(feature = "clap", value(name = "in"))] Inches, /// Meters #[default] #[serde(rename = "m")] #[display("m")] + #[cfg_attr(feature = "clap", value(name = "m"))] Meters, /// Millimeters #[serde(rename = "mm")] #[display("mm")] + #[cfg_attr(feature = "clap", value(name = "mm"))] Millimeters, /// Yards #[serde(rename = "yd")] #[display("yd")] + #[cfg_attr(feature = "clap", value(name = "yd"))] Yards, } @@ -119,9 +125,11 @@ pub enum UnitAngle { /// Degrees #[default] #[display("deg")] + #[cfg_attr(feature = "clap", value(name = "deg"))] Degrees, /// Radians #[display("rad")] + #[cfg_attr(feature = "clap", value(name = "rad"))] Radians, } @@ -159,35 +167,43 @@ pub enum UnitArea { /// Square centimeters #[serde(rename = "cm2")] #[display("cm2")] + #[cfg_attr(feature = "clap", value(name = "cm2"))] SquareCentimeters, /// Square decimeters #[serde(rename = "dm2")] #[display("dm2")] + #[cfg_attr(feature = "clap", value(name = "dm2"))] SquareDecimeters, /// Square feet #[serde(rename = "ft2")] #[display("ft2")] + #[cfg_attr(feature = "clap", value(name = "ft2"))] SquareFeet, /// Square inches #[serde(rename = "in2")] #[display("in2")] + #[cfg_attr(feature = "clap", value(name = "in2"))] SquareInches, /// Square kilometers #[serde(rename = "km2")] #[display("km2")] + #[cfg_attr(feature = "clap", value(name = "km2"))] SquareKilometers, /// Square meters #[default] #[serde(rename = "m2")] #[display("m2")] + #[cfg_attr(feature = "clap", value(name = "m2"))] SquareMeters, /// Square millimeters #[serde(rename = "mm2")] #[display("mm2")] + #[cfg_attr(feature = "clap", value(name = "mm2"))] SquareMillimeters, /// Square yards #[serde(rename = "yd2")] #[display("yd2")] + #[cfg_attr(feature = "clap", value(name = "yd2"))] SquareYards, } @@ -239,12 +255,14 @@ pub enum UnitDensity { /// Pounds per cubic feet. #[serde(rename = "lb:ft3")] #[display("lb:ft3")] + #[cfg_attr(feature = "clap", value(name = "lb:ft3", aliases = ["lbft3", "lb-ft3"]))] PoundsPerCubicFeet, /// Kilograms per cubic meter. #[default] #[serde(rename = "kg:m3")] #[display("kg:m3")] + #[cfg_attr(feature = "clap", value(name = "kg:m3", aliases = ["kgm3", "kg-m3"]))] KilogramsPerCubicMeter, } @@ -305,14 +323,17 @@ pub enum UnitMass { #[default] #[serde(rename = "g")] #[display("g")] + #[cfg_attr(feature = "clap", value(name = "g"))] Grams, /// Kilograms #[serde(rename = "kg")] #[display("kg")] + #[cfg_attr(feature = "clap", value(name = "kg"))] Kilograms, /// Pounds #[serde(rename = "lb")] #[display("lb")] + #[cfg_attr(feature = "clap", value(name = "lb"))] Pounds, } @@ -345,6 +366,7 @@ impl UnitMass { UnitConversion, Hash, )] +#[cfg_attr(feature = "clap", derive(ValueEnum))] #[cfg_attr(feature = "tabled", derive(tabled::Tabled))] #[display(style = "snake_case")] #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] @@ -359,43 +381,53 @@ pub enum UnitVolume { /// Cubic millimeters (mm³) #[serde(rename = "mm3")] #[display("mm3")] + #[cfg_attr(feature = "clap", value(name = "mm3"))] CubicMillimeters, /// Cubic centimeters (cc or cm³) #[serde(rename = "cm3")] #[display("cm3")] + #[cfg_attr(feature = "clap", value(name = "cm3"))] CubicCentimeters, /// Cubic feet (ft³) #[serde(rename = "ft3")] #[display("ft3")] + #[cfg_attr(feature = "clap", value(name = "ft3"))] CubicFeet, /// Cubic inches (cu in or in³) #[serde(rename = "in3")] #[display("in3")] + #[cfg_attr(feature = "clap", value(name = "in3"))] CubicInches, /// Cubic meters (m³) #[default] #[serde(rename = "m3")] #[display("m3")] + #[cfg_attr(feature = "clap", value(name = "m3"))] CubicMeters, /// Cubic yards (yd³) #[serde(rename = "yd3")] #[display("yd3")] + #[cfg_attr(feature = "clap", value(name = "yd3"))] CubicYards, /// US Fluid Ounces (fl oz) #[serde(rename = "usfloz")] #[display("usfloz")] + #[cfg_attr(feature = "clap", value(name = "usfloz"))] FluidOunces, /// US Gallons (gal US) #[serde(rename = "usgal")] #[display("usgal")] + #[cfg_attr(feature = "clap", value(name = "usgal"))] Gallons, /// Liters (l) #[serde(rename = "l")] #[display("l")] + #[cfg_attr(feature = "clap", value(name = "l"))] Liters, /// Milliliters (ml) #[serde(rename = "ml")] #[display("ml")] + #[cfg_attr(feature = "clap", value(name = "ml"))] Milliliters, } @@ -416,3 +448,36 @@ impl UnitVolume { } } } + +#[cfg(all(test, feature = "clap"))] +mod clap_tests { + use super::*; + + fn assert_clap_spellings() { + for unit in T::value_variants() { + let spelling = unit.to_string(); + assert_eq!(unit.to_possible_value().unwrap().get_name(), spelling); + assert_eq!(&T::from_str(&spelling, false).unwrap(), unit); + } + } + + #[test] + fn clap_unit_spellings_match_display() { + assert_clap_spellings::(); + assert_clap_spellings::(); + assert_clap_spellings::(); + assert_clap_spellings::(); + assert_clap_spellings::(); + assert_clap_spellings::(); + } + + #[test] + fn clap_preserves_density_aliases() { + for spelling in ["lbft3", "lb:ft3", "lb-ft3", "kgm3", "kg:m3", "kg-m3"] { + assert_eq!( + ::from_str(spelling, false).unwrap(), + ::from_str(spelling).unwrap() + ); + } + } +}