UnitMath is a small Rust library and dependency-free CLI for unit conversions, parsing, and package math. The library focuses on core typed conversions and parsers; the CLI adds convenient one-shot commands, package math commands, and batch processing for CSV and JSON Lines data.
UnitMath currently supports:
- Weight conversions and parsing
- US liquid volume conversions and parsing
- Potency conversions and parsing
- Simple package math helpers
- One-shot CLI conversion commands
- Batch CLI processing from CSV, JSON Lines, files, and stdin
- Machine-readable CLI output as JSON, CSV-style rows, JSON Lines, or JSON arrays
UnitMath intentionally keeps the public library API focused and lightweight. Higher-level features such as universal conversion, package expression parsing, batch processing, CSV/JSON formatting, and input-format handling are currently CLI-layer features, while trait-based quantity abstractions are intentionally deferred.
- Milligram:
mg - Gram:
g,gram,grams - Kilogram:
kg - Ounce:
oz,ounce,ounces - Pound:
lb,lbs,pound,pounds
Weight conversions use grams as the canonical base unit internally.
- Milliliter:
ml,milliliter,milliliters - Liter:
l,liter,liters - US fluid ounce:
fl oz,floz,fluid ounce,fluid ounces - US cup:
cup,cups - US pint:
pint,pints - US quart:
quart,quarts - US gallon:
gallon,gallons
Volume conversions use milliliters as the canonical base unit internally.
- Percent:
%,percent,percentage - Milligrams per gram:
mg/g,mgg,mg per g,mg/g dry weight,milligrams per gram
Potency conversions use milligrams per gram as the canonical base unit internally.
use unitmath::{
calculate_total_quantity, calculate_total_units, convert_parsed_potency,
convert_parsed_volume, convert_parsed_weight, convert_potency, convert_volume,
convert_weight, parse_potency, parse_volume, parse_weight, PotencyUnit, VolumeUnit,
WeightUnit,
};
let grams = convert_weight(1000.0, WeightUnit::Milligram, WeightUnit::Gram);
assert_eq!(grams, 1.0);
let liters = convert_volume(1000.0, VolumeUnit::Milliliter, VolumeUnit::Liter);
assert_eq!(liters, 1.0);
let milligrams_per_gram =
convert_potency(22.4, PotencyUnit::Percent, PotencyUnit::MilligramsPerGram);
assert_eq!(milligrams_per_gram, 224.0);
let units = calculate_total_units(2.0, 12.0);
assert_eq!(units, 24.0);
let total_milligrams = calculate_total_quantity(24.0, 100.0);
assert_eq!(total_milligrams, 2400.0);
let parsed_weight = parse_weight(" 3.5G ")?;
assert_eq!(parsed_weight.value, 3.5);
assert_eq!(parsed_weight.unit, WeightUnit::Gram);
let parsed_volume = parse_volume("8 fl oz")?;
assert_eq!(parsed_volume.unit, VolumeUnit::FluidOunce);
let parsed_potency = parse_potency("22.4%")?;
assert_eq!(parsed_potency.unit, PotencyUnit::Percent);
let ounces = convert_parsed_weight("3.5g", WeightUnit::Ounce)?;
let cups = convert_parsed_volume("8 fl oz", VolumeUnit::Cup)?;
let percent = convert_parsed_potency("224mg/g", PotencyUnit::Percent)?;
assert!(ounces > 0.0);
assert_eq!(cups, 1.0);
assert_eq!(percent, 22.4);
# Ok::<(), unitmath::UnitMathError>(())Parsing trims whitespace, is case-insensitive, and returns UnitMathError for empty input, missing numbers, invalid numbers, missing units, and unknown units.
Default CLI output is numeric-only:
unitmath weight "1000mg" g
unitmath weight "1 lb" oz
unitmath volume "8 fl oz" cup
unitmath volume "1 gallon" ml
unitmath potency "22.4%" mg/g
unitmath potency "224mg/g" percent
unitmath convert "3.5g" oz
unitmath convert "1 gallon" ml
unitmath convert "22.4%" mg/gPackage CLI commands perform simple multiplication without unit conversion:
unitmath package total-units "2 x 12"
unitmath package total-units "2,12"
unitmath package total-quantity "10 x 3.5"
unitmath package total-quantity "24 * 100" mgAdd --precision <digits> to format output values with exactly that many digits after the decimal point. Precision supports values from 0 through 12 and affects output formatting only:
unitmath convert "3.5g" oz --precision 4
unitmath convert "3.5g" oz --json --precision 4
unitmath convert "3.5g" oz --csv --precision 4
unitmath package total-units "2 x 12" --precision 2One-shot commands support JSON and CSV-style output:
unitmath convert "3.5g" oz --json
unitmath convert "3.5g" oz --csv
unitmath convert "3.5g" oz --csv --no-header
unitmath convert "3.5g" oz --csv --include-header
unitmath convert "3.5g" oz --csv --delimiter tabOne-shot JSON output is a single object:
{"category":"weight","input":"3.5g","target_unit":"oz","value":0.12345886682353144}One-shot CSV output includes headers by default:
category,input,target_unit,value
weight,3.5g,oz,0.12345886682353144Batch commands support CSV-style rows, JSON Lines, and JSON arrays:
unitmath batch examples/conversions.csv --csv
unitmath batch examples/conversions.csv --json
unitmath batch examples/conversions.csv --json --json-array
unitmath batch examples/conversions.csv --csv --delimiter tab
unitmath batch examples/conversions.csv --csv --delimiter pipeBatch JSON output is JSON Lines by default:
{"category":"weight","input":"1000mg","target_unit":"g","value":1,"status":"ok","error":null}
{"category":"volume","input":"1 gallon","target_unit":"ml","value":3785.411784,"status":"ok","error":null}Add --json-array with batch --json to emit one JSON array instead of JSON Lines:
[{"category":"weight","input":"1000mg","target_unit":"g","value":1,"status":"ok","error":null},{"category":"volume","input":"1 gallon","target_unit":"ml","value":3785.411784,"status":"ok","error":null}]CSV-style output uses commas by default. Use --delimiter comma, --delimiter tab, or --delimiter pipe with --csv to choose the output separator. Delimiter control affects output only; batch CSV input remains comma-separated.
Batch CSV input uses these headers:
category,input,target_unit
weight,1000mg,g
volume,1 gallon,ml
potency,22.4%,mg/g
convert,8 fl oz,cup
total_units,2 x 12,units
total_quantity,24 * 100,mgSupported batch categories are weight, volume, potency, convert, total_units, and total_quantity. Surrounding whitespace in header names and category values is ignored. Package rows do simple multiplication without unit conversion; target_unit is preserved as an output label.
JSON Lines batch input uses one flat object per non-empty line with string fields for category, input, and target_unit:
{"category":"weight","input":"1000mg","target_unit":"g"}
{"category":"volume","input":"1 gallon","target_unit":"ml"}
{"category":"potency","input":"22.4%","target_unit":"mg/g"}
{"category":"convert","input":"8 fl oz","target_unit":"cup"}
{"category":"total_units","input":"2 x 12","target_unit":"units"}
{"category":"total_quantity","input":"24 * 100","target_unit":"mg"}Copy-paste batch commands:
unitmath batch examples/conversions.csv --csv
unitmath batch examples/conversions.csv --json
unitmath batch examples/conversions.csv --json --json-array
unitmath batch examples/conversions.jsonl --json
unitmath batch examples/conversions.jsonl --csv
unitmath batch examples/conversions.csv --csv --precision 2Omit the file path to read from stdin. Stdin defaults to CSV unless --input-format jsonl or --input-json is provided:
cat examples/conversions.csv | unitmath batch --csv
cat examples/conversions.csv | unitmath batch --json
cat examples/conversions.jsonl | unitmath batch --input-format jsonl --csv
cat examples/conversions.jsonl | unitmath batch --input-format jsonl --jsonBatch file input is auto-detected from the file extension:
.csvreads CSV input.jsonlreads JSON Lines input.ndjsonreads JSON Lines input
Extension matching is case-insensitive. Files with unknown or missing extensions require --input-format csv or --input-format jsonl:
unitmath batch examples/conversions.csv --input-format csv --csv
unitmath batch examples/conversions.jsonl --input-format jsonl --json
unitmath batch examples/conversions.jsonl --input-format jsonl --json --json-arrayThe older --input-json flag remains available as a compatibility alias for --input-format jsonl:
unitmath batch examples/conversions.jsonl --input-json --json
cat examples/conversions.jsonl | unitmath batch --input-json --jsonUse --out <path> to write batch results to a file instead of stdout:
unitmath batch examples/conversions.csv --csv --out results.csv
unitmath batch examples/conversions.csv --json --out results.jsonl
unitmath batch examples/conversions.csv --json --json-array --out results.json
unitmath batch examples/conversions.csv --csv --delimiter tab --out results.tsv
cat examples/conversions.csv | unitmath batch --csv --out results.csvRows with conversion errors are included in batch output with status set to error; batch processing continues after row-level failures. Use filters and summaries to triage mixed-quality files:
unitmath batch examples/conversions.csv --csv --errors-only
unitmath batch examples/conversions.csv --json --errors-only
unitmath batch examples/conversions.csv --csv --ok-only
unitmath batch examples/conversions.csv --csv --summary
unitmath batch examples/conversions.csv --csv --errors-only --summary
unitmath batch examples/conversions.csv --csv --errors-only --out errors.csv
unitmath batch examples/conversions.csv --json --ok-only --out clean.jsonl--summary writes counts to stderr and never changes stdout or output file schemas:
summary: processed=6 ok=6 errors=0 emitted=6
summary: processed=6 ok=5 errors=1 emitted=1 output=errors.csv
Summary counts are:
processed: all parsed batch result rows before filteringok: successful rows before filteringerrors: error rows before filteringemitted: rows emitted after--errors-onlyor--ok-only
Library examples:
cargo run --example basic_weight
cargo run --example basic_volume
cargo run --example basic_potencyBatch sample files:
examples/conversions.csvexamples/conversions.jsonl
- Additional parsers
- More unit families
- Richer package parsing as a library API
- Broader CLI ergonomics
- Trait-based quantity abstractions when the core API shape is clear