109 lines
3.1 KiB
Rust
109 lines
3.1 KiB
Rust
mod conversions;
|
|
mod format;
|
|
mod parse;
|
|
mod units;
|
|
|
|
use conversions::convert;
|
|
use format::format;
|
|
use parse::{parse, ParseError};
|
|
use units::{MetricQuantity, NonMetric};
|
|
|
|
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());
|
|
}
|
|
};
|
|
|
|
if non_metric.len() == 0 {
|
|
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
|
|
// This is the case if the units after conversion are the same
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
let amount = metric.into_iter().map(|quantity| { quantity.amount }).sum();
|
|
|
|
let quantity = MetricQuantity {
|
|
amount: amount,
|
|
unit: metric_unit.expect("we must have at least one quantity by this point"),
|
|
};
|
|
|
|
Ok(format(quantity))
|
|
}
|
|
|
|
fn unit_to_name(unit: NonMetric) -> &'static str {
|
|
match unit {
|
|
// Length
|
|
NonMetric::Inch => "inches",
|
|
NonMetric::Foot => "feet",
|
|
NonMetric::Yard => "yards",
|
|
NonMetric::Mile => "miles",
|
|
// Weight
|
|
NonMetric::Ounce => "ounces",
|
|
NonMetric::Pound => "pounds",
|
|
NonMetric::Stone => "stones",
|
|
// Temperature
|
|
NonMetric::Fahrenheit => "degrees Fahrenheit",
|
|
}
|
|
}
|
|
|
|
#[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("6 ft 1 lbs"), Err("Incompatible units: feet, pounds".to_string()));
|
|
}
|
|
|
|
#[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()));
|
|
// Weight
|
|
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()));
|
|
// 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()));
|
|
}
|
|
}
|