metrify/src/lib.rs

166 lines
5.8 KiB
Rust
Raw Permalink Normal View History

2023-05-13 14:50:18 +00:00
mod conversions;
2023-05-14 01:23:39 +00:00
mod format;
2023-05-14 00:04:47 +00:00
mod parse;
mod units;
2023-05-13 14:50:18 +00:00
2023-05-14 00:40:21 +00:00
use conversions::convert;
2023-05-14 01:23:39 +00:00
use format::format;
2023-05-14 00:40:21 +00:00
use parse::{parse, ParseError};
2023-05-29 18:39:48 +00:00
use units::{Metric, MetricQuantity, NonMetric};
2023-05-14 00:40:21 +00:00
pub fn run(input: &str) -> Result<String, String> {
let non_metric = match parse(input) {
Ok(non_metric) => non_metric,
Err(ParseError::NotValidNumber(string)) => {
return Err(format!("Not a valid number: {string}"));
}
Err(ParseError::UnexpectedUnit(unit)) => {
return Err(format!("Unexpected unit: {unit}"));
}
Err(ParseError::UnknownUnit(unit)) => {
return Err(format!("Unknown unit: {unit}"));
}
Err(ParseError::ExpectedUnit) => {
return Err("Expected a unit".to_string());
}
Err(ParseError::AmbiguousUnit(unit, prefix1, prefix2)) => {
return Err(format!("Ambiguous unit '{unit}', use either '{prefix1} {unit}' or '{prefix2} {unit}'"));
}
2023-05-14 00:40:21 +00:00
};
2023-06-01 16:55:09 +00:00
if non_metric.is_empty() {
2023-05-14 00:40:21 +00:00
return Err("Expected quantity or quantities to convert".to_string());
}
let metric: Vec<MetricQuantity> = non_metric.clone().into_iter().map(convert).collect();
// Make sure the results of the conversions can be summed together
2023-05-29 18:39:48 +00:00
// This is the case if the units after conversion are the same and
// they are not temperature units (i.e. degrees Celsius)
2023-05-14 00:40:21 +00:00
let mut metric_unit = None;
for index in 0..metric.len() {
match metric_unit {
Some(metric_unit) => {
if metric[index].unit != metric_unit {
let first_unit_name = unit_to_name(non_metric[0].unit);
let unit_name = unit_to_name(non_metric[index].unit);
return Err(format!("Incompatible units: {first_unit_name}, {unit_name}"));
}
}
None => {
metric_unit = Some(metric[index].unit);
}
}
}
2023-05-29 18:39:48 +00:00
if metric.len() > 1 && metric_unit == Some(Metric::Celsius) {
return Err("Cannot sum together temperatures".to_string());
}
2023-05-14 00:40:21 +00:00
let amount = metric.into_iter().map(|quantity| { quantity.amount }).sum();
let quantity = MetricQuantity {
2023-06-01 16:55:09 +00:00
amount,
2023-05-14 00:40:21 +00:00
unit: metric_unit.expect("we must have at least one quantity by this point"),
};
2023-05-14 01:23:39 +00:00
Ok(format(quantity))
2023-05-14 00:40:21 +00:00
}
fn unit_to_name(unit: NonMetric) -> &'static str {
match unit {
// Length
NonMetric::Inch => "inches",
NonMetric::Foot => "feet",
NonMetric::Yard => "yards",
NonMetric::Mile => "miles",
2023-06-01 00:00:10 +00:00
// Mass
2023-05-14 00:40:21 +00:00
NonMetric::Ounce => "ounces",
NonMetric::Pound => "pounds",
NonMetric::Stone => "stones",
2023-05-31 23:38:49 +00:00
NonMetric::ShortTon => "short tons",
2023-05-31 23:48:34 +00:00
NonMetric::LongTon => "long tons",
2023-05-28 17:32:31 +00:00
// Temperature
NonMetric::Fahrenheit => "degrees Fahrenheit",
2023-05-28 23:55:43 +00:00
// Area
NonMetric::SquareInch => "square inches",
2023-05-29 00:01:56 +00:00
NonMetric::SquareFoot => "square feet",
2023-06-01 16:24:11 +00:00
NonMetric::SquareYard => "square yards",
2023-05-29 00:18:30 +00:00
NonMetric::Acre => "acres",
2023-05-29 00:27:55 +00:00
NonMetric::SquareMile => "square miles",
2023-05-29 19:29:10 +00:00
// Volume
NonMetric::CubicInch => "cubic inches",
2023-05-29 19:40:03 +00:00
NonMetric::CubicFoot => "cubic feet",
2023-06-01 16:31:55 +00:00
NonMetric::CubicYard => "cubic yards",
2023-05-30 17:01:35 +00:00
// Fluid volume
2023-05-30 17:34:38 +00:00
NonMetric::ImperialFluidOunce => "imperial fluid ounces",
NonMetric::ImperialPint => "imperial pints",
NonMetric::ImperialQuart => "imperial quarts",
NonMetric::ImperialGallon => "imperial gallons",
2023-05-31 19:38:50 +00:00
NonMetric::USTeaspoon => "US teaspoons",
2023-05-31 19:29:58 +00:00
NonMetric::USTablespoon => "US tablespoons",
2023-05-31 19:19:55 +00:00
NonMetric::USFluidOunce => "US fluid ounces",
2023-05-31 19:10:49 +00:00
NonMetric::USCup => "US cups",
2023-05-31 14:49:24 +00:00
NonMetric::USLiquidPint => "US liquid pints",
2023-05-31 14:42:18 +00:00
NonMetric::USLiquidQuart => "US liquid quarts",
2023-05-31 14:35:19 +00:00
NonMetric::USGallon => "US gallons",
2023-05-14 00:40:21 +00:00
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn errors() {
assert_eq!(run("1.0."), Err("Not a valid number: 1.0.".to_string()));
assert_eq!(run("ft"), Err("Unexpected unit: ft".to_string()));
assert_eq!(run("1 tf"), Err("Unknown unit: tf".to_string()));
assert_eq!(run("1"), Err("Expected a unit".to_string()));
assert_eq!(run(""), Err("Expected quantity or quantities to convert".to_string()));
assert_eq!(run("1 fl oz"), Err("Ambiguous unit 'fl oz', use either 'imp fl oz' or 'US fl oz'".to_string()));
2023-05-14 00:40:21 +00:00
assert_eq!(run("6 ft 1 lbs"), Err("Incompatible units: feet, pounds".to_string()));
2023-05-29 18:39:48 +00:00
assert_eq!(run("0 °F 0 °F"), Err("Cannot sum together temperatures".to_string()));
2023-05-14 00:40:21 +00:00
}
2023-05-28 17:02:35 +00:00
#[test]
fn conversions() {
// Length
assert_eq!(run("1 in"), Ok("2.54 cm".to_string()));
assert_eq!(run("1 ft"), Ok("30.48 cm".to_string()));
assert_eq!(run("1 yard"), Ok("91.44 cm".to_string()));
assert_eq!(run("1 mile"), Ok("1.609 km".to_string()));
2023-06-01 00:00:10 +00:00
// Mass
2023-05-28 17:02:35 +00:00
assert_eq!(run("1 oz"), Ok("28.35 g".to_string()));
assert_eq!(run("1 lb"), Ok("453.6 g".to_string()));
assert_eq!(run("1 st"), Ok("6.35 kg".to_string()));
2023-05-31 23:38:49 +00:00
assert_eq!(run("1 short ton"), Ok("907.2 kg".to_string()));
2023-05-31 23:48:34 +00:00
assert_eq!(run("1 long ton"), Ok("1 016 kg".to_string()));
2023-05-28 17:32:31 +00:00
// Temperature
assert_eq!(run("-40 °F"), Ok("-40 °C".to_string()));
assert_eq!(run("0 °F"), Ok("-17.78 °C".to_string()));
assert_eq!(run("32 °F"), Ok("0 °C".to_string()));
2023-05-28 23:55:43 +00:00
// Area
assert_eq!(run("1 in²"), Ok("6.452 cm²".to_string()));
2023-05-29 00:01:56 +00:00
assert_eq!(run("1 ft²"), Ok("929 cm²".to_string()));
2023-06-01 16:24:11 +00:00
assert_eq!(run("1 yd²"), Ok("8 361 cm²".to_string()));
2023-05-29 00:18:30 +00:00
assert_eq!(run("1 acre"), Ok("4 047 m²".to_string()));
2023-05-29 00:27:55 +00:00
assert_eq!(run("1 mi²"), Ok("2.59 km²".to_string()));
2023-05-29 19:29:10 +00:00
// Volume
assert_eq!(run("1 in³"), Ok("16.39 cm³".to_string()));
2023-05-29 19:40:03 +00:00
assert_eq!(run("1 ft³"), Ok("28 317 cm³".to_string()));
2023-06-01 16:31:55 +00:00
assert_eq!(run("1 yd³"), Ok("764 555 cm³".to_string()));
2023-05-30 17:01:35 +00:00
// Fluid volume
2023-05-30 17:34:38 +00:00
assert_eq!(run("1 imp fl oz"), Ok("2.841 cl".to_string()));
assert_eq!(run("1 imp pt"), Ok("5.683 dl".to_string()));
assert_eq!(run("1 imp qt"), Ok("1.137 l".to_string()));
assert_eq!(run("1 imp gal"), Ok("4.546 l".to_string()));
2023-05-31 19:38:50 +00:00
assert_eq!(run("1 tsp"), Ok("4.929 ml".to_string()));
2023-05-31 19:29:58 +00:00
assert_eq!(run("1 Tbsp"), Ok("1.479 cl".to_string()));
2023-05-31 19:19:55 +00:00
assert_eq!(run("1 US fl oz"), Ok("2.957 cl".to_string()));
2023-05-31 19:10:49 +00:00
assert_eq!(run("1 US cup"), Ok("2.366 dl".to_string()));
2023-05-31 14:49:24 +00:00
assert_eq!(run("1 US pt"), Ok("4.732 dl".to_string()));
2023-05-31 14:42:18 +00:00
assert_eq!(run("1 US qt"), Ok("9.464 dl".to_string()));
2023-05-31 14:35:19 +00:00
assert_eq!(run("1 US gal"), Ok("3.785 l".to_string()));
2023-05-28 17:02:35 +00:00
}
2023-05-14 00:40:21 +00:00
}