Skip to content

Commit ca5aee9

Browse files
committed
Polish UnitMath v1.18.0
1 parent b3b44e5 commit ca5aee9

6 files changed

Lines changed: 3640 additions & 289 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "unitmath"
3-
version = "1.4.0"
3+
version = "1.18.0"
44
edition = "2021"
55
description = "A small Rust library and CLI for unit conversions, parsing, and package math."
66
license = "MIT"

README.md

Lines changed: 161 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
# UnitMath
22

3-
UnitMath is a small Rust library and dependency-free CLI for unit conversions, parsing, and package math.
3+
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.
44

5-
Version 1.4.0 is focused on:
5+
## Current Scope
66

7-
- Weight conversions
8-
- US liquid volume conversions
9-
- Potency conversions
7+
UnitMath currently supports:
8+
9+
- Weight conversions and parsing
10+
- US liquid volume conversions and parsing
11+
- Potency conversions and parsing
1012
- Simple package math helpers
11-
- Basic string parsing for weight, volume, and potency quantities
12-
- Parsed conversion helpers
13-
- A minimal CLI
14-
- Optional JSON CLI output
15-
- Optional CSV CLI output
16-
- Universal CLI conversion with category inference
17-
- Batch CSV input mode with CSV or JSON Lines output
13+
- One-shot CLI conversion commands
14+
- Batch CLI processing from CSV, JSON Lines, files, and stdin
15+
- Machine-readable CLI output as JSON, CSV-style rows, JSON Lines, or JSON arrays
1816

19-
UnitMath intentionally does not include universal library conversion, package parsing, or trait-based quantity abstractions yet.
17+
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.
2018

2119
## Supported Units
2220

@@ -72,8 +70,8 @@ assert_eq!(milligrams_per_gram, 224.0);
7270
let units = calculate_total_units(2.0, 12.0);
7371
assert_eq!(units, 24.0);
7472

75-
let total_grams = calculate_total_quantity(10.0, 3.5);
76-
assert_eq!(total_grams, 35.0);
73+
let total_milligrams = calculate_total_quantity(24.0, 100.0);
74+
assert_eq!(total_milligrams, 2400.0);
7775

7876
let parsed_weight = parse_weight(" 3.5G ")?;
7977
assert_eq!(parsed_weight.value, 3.5);
@@ -85,59 +83,20 @@ assert_eq!(parsed_volume.unit, VolumeUnit::FluidOunce);
8583
let parsed_potency = parse_potency("22.4%")?;
8684
assert_eq!(parsed_potency.unit, PotencyUnit::Percent);
8785

88-
let grams = convert_parsed_weight("1000mg", WeightUnit::Gram)?;
89-
assert_eq!(grams, 1.0);
90-
86+
let ounces = convert_parsed_weight("3.5g", WeightUnit::Ounce)?;
9187
let cups = convert_parsed_volume("8 fl oz", VolumeUnit::Cup)?;
92-
assert_eq!(cups, 1.0);
88+
let percent = convert_parsed_potency("224mg/g", PotencyUnit::Percent)?;
9389

94-
let milligrams_per_gram =
95-
convert_parsed_potency("22.4%", PotencyUnit::MilligramsPerGram)?;
96-
assert_eq!(milligrams_per_gram, 224.0);
97-
98-
# Ok::<(), unitmath::UnitMathError>(())
99-
```
100-
101-
## Parsing Examples
102-
103-
```rust
104-
use unitmath::{parse_potency, parse_volume, parse_weight, PotencyUnit, VolumeUnit, WeightUnit};
105-
106-
assert_eq!(parse_weight("1000 mg")?.unit, WeightUnit::Milligram);
107-
assert_eq!(parse_weight("1000mg")?.unit, WeightUnit::Milligram);
108-
assert_eq!(parse_weight(".5 g")?.value, 0.5);
109-
110-
assert_eq!(parse_volume("1000 ml")?.unit, VolumeUnit::Milliliter);
111-
assert_eq!(parse_volume("8floz")?.unit, VolumeUnit::FluidOunce);
112-
assert_eq!(parse_volume("2 gallons")?.unit, VolumeUnit::Gallon);
113-
114-
assert_eq!(parse_potency("22.4 %")?.unit, PotencyUnit::Percent);
115-
assert_eq!(parse_potency("224mg/g")?.unit, PotencyUnit::MilligramsPerGram);
116-
assert_eq!(
117-
parse_potency("224 milligrams per gram")?.unit,
118-
PotencyUnit::MilligramsPerGram
119-
);
90+
assert!(ounces > 0.0);
91+
assert_eq!(cups, 1.0);
92+
assert_eq!(percent, 22.4);
12093

12194
# Ok::<(), unitmath::UnitMathError>(())
12295
```
12396

12497
Parsing trims whitespace, is case-insensitive, and returns `UnitMathError` for empty input, missing numbers, invalid numbers, missing units, and unknown units.
12598

126-
## Package Helpers
127-
128-
```rust
129-
use unitmath::{calculate_total_quantity, calculate_total_units};
130-
131-
let units = calculate_total_units(5.0, 24.0);
132-
assert_eq!(units, 120.0);
133-
134-
let total_milligrams = calculate_total_quantity(24.0, 100.0);
135-
assert_eq!(total_milligrams, 2400.0);
136-
```
137-
138-
Package helpers only multiply counts by per-unit quantities. They do not parse package strings or perform unit conversion.
139-
140-
## CLI Usage
99+
## CLI Examples
141100

142101
Default CLI output is numeric-only:
143102

@@ -153,90 +112,199 @@ unitmath convert "1 gallon" ml
153112
unitmath convert "22.4%" mg/g
154113
```
155114

156-
Add `--json` at the end to emit structured output:
115+
Package CLI commands perform simple multiplication without unit conversion:
157116

158117
```sh
159-
unitmath weight "3.5g" oz --json
160-
unitmath volume "8 fl oz" cup --json
161-
unitmath potency "22.4%" mg/g --json
162-
unitmath convert "3.5g" oz --json
118+
unitmath package total-units "2 x 12"
119+
unitmath package total-units "2,12"
120+
unitmath package total-quantity "10 x 3.5"
121+
unitmath package total-quantity "24 * 100" mg
163122
```
164123

165-
Example JSON output:
124+
Add `--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:
166125

167-
```json
168-
{"category":"weight","input":"3.5g","target_unit":"oz","value":0.12345886682353144}
126+
```sh
127+
unitmath convert "3.5g" oz --precision 4
128+
unitmath convert "3.5g" oz --json --precision 4
129+
unitmath convert "3.5g" oz --csv --precision 4
130+
unitmath package total-units "2 x 12" --precision 2
169131
```
170132

171-
Add `--csv` at the end to emit a header row and one data row:
133+
## Machine-Readable Output
134+
135+
One-shot commands support JSON and CSV-style output:
172136

173137
```sh
174-
unitmath weight "3.5g" oz --csv
175-
unitmath volume "8 fl oz" cup --csv
176-
unitmath potency "22.4%" mg/g --csv
138+
unitmath convert "3.5g" oz --json
177139
unitmath convert "3.5g" oz --csv
140+
unitmath convert "3.5g" oz --csv --no-header
141+
unitmath convert "3.5g" oz --csv --include-header
142+
unitmath convert "3.5g" oz --csv --delimiter tab
143+
```
144+
145+
One-shot JSON output is a single object:
146+
147+
```json
148+
{"category":"weight","input":"3.5g","target_unit":"oz","value":0.12345886682353144}
178149
```
179150

180-
Example CSV output:
151+
One-shot CSV output includes headers by default:
181152

182153
```csv
183154
category,input,target_unit,value
184155
weight,3.5g,oz,0.12345886682353144
185156
```
186157

187-
Errors are written to stderr with usage guidance.
158+
Batch commands support CSV-style rows, JSON Lines, and JSON arrays:
188159

189-
The `convert` command infers the category from the input and target unit. It succeeds only when exactly one category matches.
160+
```sh
161+
unitmath batch examples/conversions.csv --csv
162+
unitmath batch examples/conversions.csv --json
163+
unitmath batch examples/conversions.csv --json --json-array
164+
unitmath batch examples/conversions.csv --csv --delimiter tab
165+
unitmath batch examples/conversions.csv --csv --delimiter pipe
166+
```
190167

191-
## Batch Mode
168+
Batch JSON output is JSON Lines by default:
192169

193-
Batch mode reads a CSV file with these headers:
170+
```jsonl
171+
{"category":"weight","input":"1000mg","target_unit":"g","value":1,"status":"ok","error":null}
172+
{"category":"volume","input":"1 gallon","target_unit":"ml","value":3785.411784,"status":"ok","error":null}
173+
```
174+
175+
Add `--json-array` with batch `--json` to emit one JSON array instead of JSON Lines:
176+
177+
```json
178+
[{"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}]
179+
```
180+
181+
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.
182+
183+
## Batch Examples
184+
185+
Batch CSV input uses these headers:
194186

195187
```csv
196188
category,input,target_unit
197189
weight,1000mg,g
198190
volume,1 gallon,ml
199191
potency,22.4%,mg/g
200192
convert,8 fl oz,cup
193+
total_units,2 x 12,units
194+
total_quantity,24 * 100,mg
201195
```
202196

203-
Supported batch categories are `weight`, `volume`, `potency`, and `convert`. Surrounding whitespace in header names and category values is ignored.
197+
Supported 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.
204198

205-
Batch mode requires an explicit output format:
199+
JSON Lines batch input uses one flat object per non-empty line with string fields for `category`, `input`, and `target_unit`:
200+
201+
```jsonl
202+
{"category":"weight","input":"1000mg","target_unit":"g"}
203+
{"category":"volume","input":"1 gallon","target_unit":"ml"}
204+
{"category":"potency","input":"22.4%","target_unit":"mg/g"}
205+
{"category":"convert","input":"8 fl oz","target_unit":"cup"}
206+
{"category":"total_units","input":"2 x 12","target_unit":"units"}
207+
{"category":"total_quantity","input":"24 * 100","target_unit":"mg"}
208+
```
209+
210+
Copy-paste batch commands:
206211

207212
```sh
208-
unitmath batch conversions.csv --csv
209-
unitmath batch conversions.csv --json
213+
unitmath batch examples/conversions.csv --csv
214+
unitmath batch examples/conversions.csv --json
215+
unitmath batch examples/conversions.csv --json --json-array
216+
unitmath batch examples/conversions.jsonl --json
217+
unitmath batch examples/conversions.jsonl --csv
218+
unitmath batch examples/conversions.csv --csv --precision 2
210219
```
211220

212-
CSV batch output includes one result row per input row:
221+
Omit the file path to read from stdin. Stdin defaults to CSV unless `--input-format jsonl` or `--input-json` is provided:
213222

214-
```csv
215-
category,input,target_unit,value,status,error
216-
weight,1000mg,g,1,ok,
217-
volume,1 gallon,ml,3785.411784,ok,
223+
```sh
224+
cat examples/conversions.csv | unitmath batch --csv
225+
cat examples/conversions.csv | unitmath batch --json
226+
cat examples/conversions.jsonl | unitmath batch --input-format jsonl --csv
227+
cat examples/conversions.jsonl | unitmath batch --input-format jsonl --json
218228
```
219229

220-
JSON batch output is JSON Lines:
230+
Batch file input is auto-detected from the file extension:
221231

222-
```jsonl
223-
{"category":"weight","input":"1000mg","target_unit":"g","value":1,"status":"ok","error":null}
224-
{"category":"volume","input":"1 gallon","target_unit":"ml","value":3785.411784,"status":"ok","error":null}
232+
- `.csv` reads CSV input
233+
- `.jsonl` reads JSON Lines input
234+
- `.ndjson` reads JSON Lines input
235+
236+
Extension matching is case-insensitive. Files with unknown or missing extensions require `--input-format csv` or `--input-format jsonl`:
237+
238+
```sh
239+
unitmath batch examples/conversions.csv --input-format csv --csv
240+
unitmath batch examples/conversions.jsonl --input-format jsonl --json
241+
unitmath batch examples/conversions.jsonl --input-format jsonl --json --json-array
225242
```
226243

227-
Rows with errors are included in the output with `status` set to `error`; batch processing continues after row-level failures.
244+
The older `--input-json` flag remains available as a compatibility alias for `--input-format jsonl`:
245+
246+
```sh
247+
unitmath batch examples/conversions.jsonl --input-json --json
248+
cat examples/conversions.jsonl | unitmath batch --input-json --json
249+
```
250+
251+
Use `--out <path>` to write batch results to a file instead of stdout:
252+
253+
```sh
254+
unitmath batch examples/conversions.csv --csv --out results.csv
255+
unitmath batch examples/conversions.csv --json --out results.jsonl
256+
unitmath batch examples/conversions.csv --json --json-array --out results.json
257+
unitmath batch examples/conversions.csv --csv --delimiter tab --out results.tsv
258+
cat examples/conversions.csv | unitmath batch --csv --out results.csv
259+
```
260+
261+
## Dirty Data Workflows
262+
263+
Rows 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:
264+
265+
```sh
266+
unitmath batch examples/conversions.csv --csv --errors-only
267+
unitmath batch examples/conversions.csv --json --errors-only
268+
unitmath batch examples/conversions.csv --csv --ok-only
269+
unitmath batch examples/conversions.csv --csv --summary
270+
unitmath batch examples/conversions.csv --csv --errors-only --summary
271+
unitmath batch examples/conversions.csv --csv --errors-only --out errors.csv
272+
unitmath batch examples/conversions.csv --json --ok-only --out clean.jsonl
273+
```
274+
275+
`--summary` writes counts to stderr and never changes stdout or output file schemas:
276+
277+
```text
278+
summary: processed=6 ok=6 errors=0 emitted=6
279+
summary: processed=6 ok=5 errors=1 emitted=1 output=errors.csv
280+
```
281+
282+
Summary counts are:
283+
284+
- `processed`: all parsed batch result rows before filtering
285+
- `ok`: successful rows before filtering
286+
- `errors`: error rows before filtering
287+
- `emitted`: rows emitted after `--errors-only` or `--ok-only`
228288

229289
## Examples
230290

291+
Library examples:
292+
231293
```sh
232294
cargo run --example basic_weight
233295
cargo run --example basic_volume
234296
cargo run --example basic_potency
235297
```
236298

237-
## Roadmap After v1.0.0
299+
Batch sample files:
300+
301+
- `examples/conversions.csv`
302+
- `examples/conversions.jsonl`
303+
304+
## Roadmap
238305

239306
- Additional parsers
240-
- Package parsing
241307
- More unit families
308+
- Richer package parsing as a library API
242309
- Broader CLI ergonomics
310+
- Trait-based quantity abstractions when the core API shape is clear

examples/conversions.csv

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
category,input,target_unit
2+
weight,1000mg,g
3+
volume,1 gallon,ml
4+
potency,22.4%,mg/g
5+
convert,8 fl oz,cup
6+
total_units,2 x 12,units
7+
total_quantity,24 * 100,mg

examples/conversions.jsonl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{"category":"weight","input":"1000mg","target_unit":"g"}
2+
{"category":"volume","input":"1 gallon","target_unit":"ml"}
3+
{"category":"potency","input":"22.4%","target_unit":"mg/g"}
4+
{"category":"convert","input":"8 fl oz","target_unit":"cup"}
5+
{"category":"total_units","input":"2 x 12","target_unit":"units"}
6+
{"category":"total_quantity","input":"24 * 100","target_unit":"mg"}

0 commit comments

Comments
 (0)