|
1 | 1 | # UnitMath |
2 | 2 |
|
3 | | -UnitMath is a small Rust library for pure unit conversion utilities. |
| 3 | +UnitMath is a small Rust library and dependency-free CLI for unit conversions, parsing, and package math. |
4 | 4 |
|
5 | | -Version 0.8.0 supports weight, US liquid volume, potency conversions, package math helpers, basic string parsing, and parsed conversion helpers. |
| 5 | +Version 1.0.0 is a stable MVP focused on: |
| 6 | + |
| 7 | +- Weight conversions |
| 8 | +- US liquid volume conversions |
| 9 | +- Potency conversions |
| 10 | +- Simple package math helpers |
| 11 | +- Basic string parsing for weight, volume, and potency quantities |
| 12 | +- Parsed conversion helpers |
| 13 | +- A minimal CLI |
| 14 | + |
| 15 | +UnitMath intentionally does not include universal conversion, package parsing, JSON output, or trait-based quantity abstractions yet. |
6 | 16 |
|
7 | 17 | ## Supported Units |
8 | 18 |
|
9 | 19 | ### Weight |
10 | 20 |
|
11 | | -- Milligram |
12 | | -- Gram |
13 | | -- Kilogram |
14 | | -- Ounce |
15 | | -- Pound |
| 21 | +- Milligram: `mg` |
| 22 | +- Gram: `g`, `gram`, `grams` |
| 23 | +- Kilogram: `kg` |
| 24 | +- Ounce: `oz`, `ounce`, `ounces` |
| 25 | +- Pound: `lb`, `lbs`, `pound`, `pounds` |
16 | 26 |
|
17 | 27 | Weight conversions use grams as the canonical base unit internally. |
18 | 28 |
|
19 | 29 | ### Volume |
20 | 30 |
|
21 | | -- Milliliter |
22 | | -- Liter |
23 | | -- FluidOunce |
24 | | -- Cup |
25 | | -- Pint |
26 | | -- Quart |
27 | | -- Gallon |
| 31 | +- Milliliter: `ml`, `milliliter`, `milliliters` |
| 32 | +- Liter: `l`, `liter`, `liters` |
| 33 | +- US fluid ounce: `fl oz`, `floz`, `fluid ounce`, `fluid ounces` |
| 34 | +- US cup: `cup`, `cups` |
| 35 | +- US pint: `pint`, `pints` |
| 36 | +- US quart: `quart`, `quarts` |
| 37 | +- US gallon: `gallon`, `gallons` |
28 | 38 |
|
29 | 39 | Volume conversions use milliliters as the canonical base unit internally. |
30 | 40 |
|
31 | 41 | ### Potency |
32 | 42 |
|
33 | | -- Percent |
34 | | -- MilligramsPerGram |
| 43 | +- Percent: `%`, `percent`, `percentage` |
| 44 | +- Milligrams per gram: `mg/g`, `mgg`, `mg per g`, `mg/g dry weight`, `milligrams per gram` |
35 | 45 |
|
36 | 46 | Potency conversions use milligrams per gram as the canonical base unit internally. |
37 | 47 |
|
38 | | -### Package Math |
39 | | - |
40 | | -- `calculate_total_units(container_count, units_per_container)` |
41 | | -- `calculate_total_quantity(unit_count, quantity_per_unit)` |
| 48 | +## Library Usage |
42 | 49 |
|
43 | | -Package math helpers multiply counts by per-unit quantities without applying unit conversion. |
| 50 | +```rust |
| 51 | +use unitmath::{ |
| 52 | + calculate_total_quantity, calculate_total_units, convert_parsed_potency, |
| 53 | + convert_parsed_volume, convert_parsed_weight, convert_potency, convert_volume, |
| 54 | + convert_weight, parse_potency, parse_volume, parse_weight, PotencyUnit, VolumeUnit, |
| 55 | + WeightUnit, |
| 56 | +}; |
44 | 57 |
|
45 | | -### Weight Parsing |
| 58 | +let grams = convert_weight(1000.0, WeightUnit::Milligram, WeightUnit::Gram); |
| 59 | +assert_eq!(grams, 1.0); |
46 | 60 |
|
47 | | -- `parse_weight("1000 mg")` |
48 | | -- `parse_weight("1000mg")` |
49 | | -- `parse_weight("3.5 g")` |
50 | | -- `parse_weight("2 pounds")` |
| 61 | +let liters = convert_volume(1000.0, VolumeUnit::Milliliter, VolumeUnit::Liter); |
| 62 | +assert_eq!(liters, 1.0); |
51 | 63 |
|
52 | | -Weight parsing is case-insensitive and returns a `ParsedQuantity<WeightUnit>`. |
| 64 | +let milligrams_per_gram = |
| 65 | + convert_potency(22.4, PotencyUnit::Percent, PotencyUnit::MilligramsPerGram); |
| 66 | +assert_eq!(milligrams_per_gram, 224.0); |
53 | 67 |
|
54 | | -### Volume Parsing |
| 68 | +let units = calculate_total_units(2.0, 12.0); |
| 69 | +assert_eq!(units, 24.0); |
55 | 70 |
|
56 | | -- `parse_volume("1000 ml")` |
57 | | -- `parse_volume("1000ml")` |
58 | | -- `parse_volume("8 fl oz")` |
59 | | -- `parse_volume("2 gallons")` |
| 71 | +let total_grams = calculate_total_quantity(10.0, 3.5); |
| 72 | +assert_eq!(total_grams, 35.0); |
60 | 73 |
|
61 | | -Volume parsing is case-insensitive and returns a `ParsedQuantity<VolumeUnit>`. |
| 74 | +let parsed_weight = parse_weight(" 3.5G ")?; |
| 75 | +assert_eq!(parsed_weight.value, 3.5); |
| 76 | +assert_eq!(parsed_weight.unit, WeightUnit::Gram); |
62 | 77 |
|
63 | | -### Potency Parsing |
| 78 | +let parsed_volume = parse_volume("8 fl oz")?; |
| 79 | +assert_eq!(parsed_volume.unit, VolumeUnit::FluidOunce); |
64 | 80 |
|
65 | | -- `parse_potency("22.4%")` |
66 | | -- `parse_potency("22.4 percent")` |
67 | | -- `parse_potency("224 mg/g")` |
68 | | -- `parse_potency("224 milligrams per gram")` |
| 81 | +let parsed_potency = parse_potency("22.4%")?; |
| 82 | +assert_eq!(parsed_potency.unit, PotencyUnit::Percent); |
69 | 83 |
|
70 | | -Potency parsing is case-insensitive and returns a `ParsedQuantity<PotencyUnit>`. |
| 84 | +let grams = convert_parsed_weight("1000mg", WeightUnit::Gram)?; |
| 85 | +assert_eq!(grams, 1.0); |
71 | 86 |
|
72 | | -### Parsed Conversion Helpers |
| 87 | +let cups = convert_parsed_volume("8 fl oz", VolumeUnit::Cup)?; |
| 88 | +assert_eq!(cups, 1.0); |
73 | 89 |
|
74 | | -- `convert_parsed_weight("1000mg", WeightUnit::Gram)` |
75 | | -- `convert_parsed_volume("8 fl oz", VolumeUnit::Cup)` |
76 | | -- `convert_parsed_potency("22.4%", PotencyUnit::MilligramsPerGram)` |
| 90 | +let milligrams_per_gram = |
| 91 | + convert_parsed_potency("22.4%", PotencyUnit::MilligramsPerGram)?; |
| 92 | +assert_eq!(milligrams_per_gram, 224.0); |
77 | 93 |
|
78 | | -Parsed conversion helpers parse a quantity string and convert it to a target unit in one step. |
| 94 | +# Ok::<(), unitmath::UnitMathError>(()) |
| 95 | +``` |
79 | 96 |
|
80 | | -## Usage |
| 97 | +## Parsing Examples |
81 | 98 |
|
82 | 99 | ```rust |
83 | | -use unitmath::{ |
84 | | - calculate_total_quantity, calculate_total_units, convert_potency, convert_volume, |
85 | | - convert_parsed_potency, convert_parsed_volume, convert_parsed_weight, convert_weight, |
86 | | - parse_potency, parse_volume, parse_weight, PotencyUnit, VolumeUnit, WeightUnit, |
87 | | -}; |
| 100 | +use unitmath::{parse_potency, parse_volume, parse_weight, PotencyUnit, VolumeUnit, WeightUnit}; |
88 | 101 |
|
89 | | -let grams = convert_weight(1000.0, WeightUnit::Milligram, WeightUnit::Gram); |
90 | | -assert_eq!(grams, 1.0); |
| 102 | +assert_eq!(parse_weight("1000 mg")?.unit, WeightUnit::Milligram); |
| 103 | +assert_eq!(parse_weight("1000mg")?.unit, WeightUnit::Milligram); |
| 104 | +assert_eq!(parse_weight(".5 g")?.value, 0.5); |
91 | 105 |
|
92 | | -let pounds = convert_weight(16.0, WeightUnit::Ounce, WeightUnit::Pound); |
93 | | -assert!((pounds - 1.0).abs() < 1e-12); |
| 106 | +assert_eq!(parse_volume("1000 ml")?.unit, VolumeUnit::Milliliter); |
| 107 | +assert_eq!(parse_volume("8floz")?.unit, VolumeUnit::FluidOunce); |
| 108 | +assert_eq!(parse_volume("2 gallons")?.unit, VolumeUnit::Gallon); |
94 | 109 |
|
95 | | -let liters = convert_volume(1000.0, VolumeUnit::Milliliter, VolumeUnit::Liter); |
96 | | -assert!((liters - 1.0).abs() < 1e-12); |
| 110 | +assert_eq!(parse_potency("22.4 %")?.unit, PotencyUnit::Percent); |
| 111 | +assert_eq!(parse_potency("224mg/g")?.unit, PotencyUnit::MilligramsPerGram); |
| 112 | +assert_eq!( |
| 113 | + parse_potency("224 milligrams per gram")?.unit, |
| 114 | + PotencyUnit::MilligramsPerGram |
| 115 | +); |
97 | 116 |
|
98 | | -let milligrams_per_gram = convert_potency(22.4, PotencyUnit::Percent, PotencyUnit::MilligramsPerGram); |
99 | | -assert!((milligrams_per_gram - 224.0).abs() < 1e-12); |
| 117 | +# Ok::<(), unitmath::UnitMathError>(()) |
| 118 | +``` |
100 | 119 |
|
101 | | -let units = calculate_total_units(2.0, 12.0); |
102 | | -assert_eq!(units, 24.0); |
| 120 | +Parsing trims whitespace, is case-insensitive, and returns `UnitMathError` for empty input, missing numbers, invalid numbers, missing units, and unknown units. |
103 | 121 |
|
104 | | -let grams = calculate_total_quantity(10.0, 3.5); |
105 | | -assert_eq!(grams, 35.0); |
| 122 | +## Package Helpers |
106 | 123 |
|
107 | | -let parsed = parse_weight("3.5 g").unwrap(); |
108 | | -assert_eq!(parsed.value, 3.5); |
109 | | -assert_eq!(parsed.unit, WeightUnit::Gram); |
| 124 | +```rust |
| 125 | +use unitmath::{calculate_total_quantity, calculate_total_units}; |
110 | 126 |
|
111 | | -let parsed = parse_volume("8 fl oz").unwrap(); |
112 | | -assert_eq!(parsed.value, 8.0); |
113 | | -assert_eq!(parsed.unit, VolumeUnit::FluidOunce); |
| 127 | +let units = calculate_total_units(5.0, 24.0); |
| 128 | +assert_eq!(units, 120.0); |
114 | 129 |
|
115 | | -let parsed = parse_potency("22.4%").unwrap(); |
116 | | -assert_eq!(parsed.value, 22.4); |
117 | | -assert_eq!(parsed.unit, PotencyUnit::Percent); |
| 130 | +let total_milligrams = calculate_total_quantity(24.0, 100.0); |
| 131 | +assert_eq!(total_milligrams, 2400.0); |
| 132 | +``` |
118 | 133 |
|
119 | | -let grams = convert_parsed_weight("1000mg", WeightUnit::Gram).unwrap(); |
120 | | -assert_eq!(grams, 1.0); |
| 134 | +Package helpers only multiply counts by per-unit quantities. They do not parse package strings or perform unit conversion. |
121 | 135 |
|
122 | | -let cups = convert_parsed_volume("8 fl oz", VolumeUnit::Cup).unwrap(); |
123 | | -assert!((cups - 1.0).abs() < 1e-12); |
| 136 | +## CLI Usage |
124 | 137 |
|
125 | | -let milligrams_per_gram = |
126 | | - convert_parsed_potency("22.4%", PotencyUnit::MilligramsPerGram).unwrap(); |
127 | | -assert_eq!(milligrams_per_gram, 224.0); |
| 138 | +```sh |
| 139 | +unitmath weight "1000mg" g |
| 140 | +unitmath weight "1 lb" oz |
| 141 | +unitmath volume "8 fl oz" cup |
| 142 | +unitmath volume "1 gallon" ml |
| 143 | +unitmath potency "22.4%" mg/g |
| 144 | +unitmath potency "224mg/g" percent |
| 145 | +``` |
| 146 | + |
| 147 | +Each command prints only the numeric converted value on success. Errors are written to stderr with usage guidance. |
| 148 | + |
| 149 | +## Examples |
| 150 | + |
| 151 | +```sh |
| 152 | +cargo run --example basic_weight |
| 153 | +cargo run --example basic_volume |
| 154 | +cargo run --example basic_potency |
128 | 155 | ``` |
129 | 156 |
|
130 | | -## Roadmap |
| 157 | +## Roadmap After v1.0.0 |
131 | 158 |
|
132 | 159 | - Additional parsers |
133 | | -- CLI |
| 160 | +- Package parsing |
| 161 | +- More unit families |
| 162 | +- Optional structured output modes |
| 163 | +- Broader CLI ergonomics |
0 commit comments