Skip to content

Commit f4918b6

Browse files
drodileldruin
authored andcommitted
Add support to create Volume from string
Again behind the from_str feature. Tests included.
1 parent be66d17 commit f4918b6

File tree

1 file changed

+278
-1
lines changed

1 file changed

+278
-1
lines changed

src/volume.rs

Lines changed: 278 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
//! Types and constants for handling volumes (that is, three-dimensional space, not loudness).
22
33
use super::measurement::*;
4+
#[cfg(feature = "from_str")]
5+
use regex::Regex;
6+
#[cfg(feature = "from_str")]
7+
use std::str::FromStr;
48

59
/// The `Volume` struct can be used to deal with volumes in a common way.
610
///
@@ -317,12 +321,59 @@ impl Measurement for Volume {
317321
}
318322
}
319323

324+
#[cfg(feature = "from_str")]
325+
impl FromStr for Volume {
326+
type Err = std::num::ParseFloatError;
327+
328+
/// Create a new Volume from a string
329+
/// Plain numbers in string are considered to be Liters. Defaults for units with US
330+
/// and UK variants are considered to be in US without specific "imp" prefix.
331+
fn from_str(val: &str) -> Result<Self, Self::Err> {
332+
if val.is_empty() {
333+
return Ok(Volume::from_liters(0.0));
334+
}
335+
336+
let re = Regex::new(r"(?i)\s*([0-9.]*)\s?([a-z .]{1,10}[0-9³]{0,1})\s*$").unwrap();
337+
if let Some(caps) = re.captures(val) {
338+
let float_val = caps.get(1).unwrap().as_str();
339+
return Ok(
340+
match caps.get(2).unwrap().as_str().to_lowercase().as_str() {
341+
"cm3" | "cm\u{00b3}" => {
342+
Volume::from_cubic_centimeters(float_val.parse::<f64>()?)
343+
}
344+
"ft3" | "ft\u{00b3}" => Volume::from_cubic_feet(float_val.parse::<f64>()?),
345+
"yd3" | "yd\u{00b3}" => Volume::from_cubic_yards(float_val.parse::<f64>()?),
346+
"in3" | "in\u{00b3}" => Volume::from_cubic_inches(float_val.parse::<f64>()?),
347+
"gal" | "us gal" => Volume::from_gallons(float_val.parse::<f64>()?),
348+
"imp gal" => Volume::from_gallons_uk(float_val.parse::<f64>()?),
349+
"cup" => Volume::from_cups(float_val.parse::<f64>()?),
350+
"tsp" => Volume::from_teaspoons(float_val.parse::<f64>()?),
351+
"tbsp" | "t." => Volume::from_tablespoons(float_val.parse::<f64>()?),
352+
"ml" => Volume::from_milliliters(float_val.parse::<f64>()?),
353+
"us fl oz" | "fl oz" => Volume::from_fluid_ounces(float_val.parse::<f64>()?),
354+
"imp fl oz" => Volume::from_fluid_ounces_uk(float_val.parse::<f64>()?),
355+
"m3" | "m\u{00b3}" => Volume::from_cubic_meters(float_val.parse::<f64>()?),
356+
"gt" | "gtt" => Volume::from_drops(float_val.parse::<f64>()?),
357+
"dr" => Volume::from_drams(float_val.parse::<f64>()?),
358+
"l" => Volume::from_litres(float_val.parse::<f64>()?),
359+
"qt" => Volume::from_quarts(float_val.parse::<f64>()?),
360+
"us pt" | "us p" | "p" | "pt" => Volume::from_pints(float_val.parse::<f64>()?),
361+
"imp pt" | "imp p" => Volume::from_pints_uk(float_val.parse::<f64>()?),
362+
_ => Volume::from_litres(val.parse::<f64>()?),
363+
},
364+
);
365+
}
366+
367+
Ok(Volume::from_liters(val.parse::<f64>()?))
368+
}
369+
}
370+
320371
implement_measurement! { Volume }
321372

322373
#[cfg(test)]
323374
mod test {
324-
use volume::*;
325375
use test_utils::assert_almost_eq;
376+
use volume::*;
326377

327378
// Volume Units
328379
// Metric
@@ -594,4 +645,230 @@ mod test {
594645
assert_eq!(a >= b, false);
595646
}
596647

648+
#[test]
649+
#[cfg(feature = "from_str")]
650+
fn empty_val_from_str() {
651+
let v = Volume::from_str("");
652+
assert!(v.is_ok());
653+
assert_eq!(0.0, v.unwrap().as_litres());
654+
}
655+
656+
#[test]
657+
#[cfg(feature = "from_str")]
658+
fn cubic_centimeters_from_str() {
659+
let v = Volume::from_str(" 10cm3");
660+
assert!(v.is_ok());
661+
assert_almost_eq(10.0, v.unwrap().as_cubic_centimeters());
662+
663+
let v2 = Volume::from_str("10 cm³ ");
664+
assert!(v2.is_ok());
665+
assert_almost_eq(10.0, v2.unwrap().as_cubic_centimetres());
666+
}
667+
668+
#[test]
669+
#[cfg(feature = "from_str")]
670+
fn cubic_feet_from_str() {
671+
let v = Volume::from_str(" 10 ft3");
672+
assert!(v.is_ok());
673+
assert_almost_eq(10.0, v.unwrap().as_cubic_feet());
674+
675+
let v2 = Volume::from_str(" 10ft³ ");
676+
assert!(v2.is_ok());
677+
assert_almost_eq(10.0, v2.unwrap().as_cubic_feet());
678+
}
679+
680+
#[test]
681+
#[cfg(feature = "from_str")]
682+
fn cubic_yard_from_str() {
683+
let v = Volume::from_str(" 10 yd3");
684+
assert!(v.is_ok());
685+
assert_almost_eq(10.0, v.unwrap().as_cubic_yards());
686+
687+
let v2 = Volume::from_str(" 10yd³ ");
688+
assert!(v2.is_ok());
689+
assert_almost_eq(10.0, v2.unwrap().as_cubic_yards());
690+
}
691+
692+
#[test]
693+
#[cfg(feature = "from_str")]
694+
fn cubic_inches_from_str() {
695+
let v = Volume::from_str(" 10 in3");
696+
assert!(v.is_ok());
697+
assert_almost_eq(10.0, v.unwrap().as_cubic_inches());
698+
699+
let v2 = Volume::from_str("10in³ ");
700+
assert!(v2.is_ok());
701+
assert_almost_eq(10.0, v2.unwrap().as_cubic_inches());
702+
}
703+
704+
#[test]
705+
#[cfg(feature = "from_str")]
706+
fn gallons_from_str() {
707+
let v = Volume::from_str(" 10 gal");
708+
assert!(v.is_ok());
709+
assert_almost_eq(10.0, v.unwrap().as_gallons());
710+
711+
let v2 = Volume::from_str("10 US gal");
712+
assert!(v2.is_ok());
713+
assert_almost_eq(10.0, v2.unwrap().as_gallons());
714+
}
715+
716+
#[test]
717+
#[cfg(feature = "from_str")]
718+
fn uk_gallons_from_str() {
719+
let v = Volume::from_str(" 10 imp gal");
720+
assert!(v.is_ok());
721+
assert_almost_eq(10.0, v.unwrap().as_gallons_uk());
722+
}
723+
724+
#[test]
725+
#[cfg(feature = "from_str")]
726+
fn cups_from_str() {
727+
let v = Volume::from_str("10cup");
728+
assert!(v.is_ok());
729+
assert_almost_eq(10.0, v.unwrap().as_cups());
730+
}
731+
732+
#[test]
733+
#[cfg(feature = "from_str")]
734+
fn teaspoons_from_str() {
735+
let v = Volume::from_str("10tsp");
736+
assert!(v.is_ok());
737+
assert_almost_eq(10.0, v.unwrap().as_teaspoons());
738+
}
739+
740+
#[test]
741+
#[cfg(feature = "from_str")]
742+
fn tablespoons_from_str() {
743+
let v = Volume::from_str("10 tbsp");
744+
assert!(v.is_ok());
745+
assert_almost_eq(10.0, v.unwrap().as_tablespoons());
746+
747+
let v2 = Volume::from_str("10T.");
748+
assert!(v2.is_ok());
749+
assert_almost_eq(10.0, v2.unwrap().as_tablespoons());
750+
}
751+
752+
#[test]
753+
#[cfg(feature = "from_str")]
754+
fn milliliters_from_str() {
755+
let v = Volume::from_str("10ml");
756+
assert!(v.is_ok());
757+
assert_almost_eq(10.0, v.unwrap().as_milliliters());
758+
}
759+
760+
#[test]
761+
#[cfg(feature = "from_str")]
762+
fn fluid_ounces_from_str() {
763+
let v = Volume::from_str("10 US fl oz");
764+
assert!(v.is_ok());
765+
assert_almost_eq(10.0, v.unwrap().as_fluid_ounces());
766+
767+
let v2 = Volume::from_str("10 fl oz");
768+
assert!(v2.is_ok());
769+
assert_almost_eq(10.0, v2.unwrap().as_fluid_ounces());
770+
}
771+
772+
#[test]
773+
#[cfg(feature = "from_str")]
774+
fn fluid_ounces_uk_from_str() {
775+
let v = Volume::from_str("10imp fl oz");
776+
assert!(v.is_ok());
777+
assert_almost_eq(10.0, v.unwrap().as_fluid_ounces_uk());
778+
}
779+
780+
#[test]
781+
#[cfg(feature = "from_str")]
782+
fn cubic_meters_from_str() {
783+
let v = Volume::from_str("10 m3");
784+
assert!(v.is_ok());
785+
assert_almost_eq(10.0, v.unwrap().as_cubic_meters());
786+
787+
let v2 = Volume::from_str("10m³");
788+
assert!(v2.is_ok());
789+
assert_almost_eq(10.0, v2.unwrap().as_cubic_meters());
790+
}
791+
792+
#[test]
793+
#[cfg(feature = "from_str")]
794+
fn drops_from_str() {
795+
let v = Volume::from_str("10 gt");
796+
assert!(v.is_ok());
797+
assert_almost_eq(10.0, v.unwrap().as_drops());
798+
799+
let v2 = Volume::from_str("10gtt");
800+
assert!(v2.is_ok());
801+
assert_almost_eq(10.0, v2.unwrap().as_drops());
802+
}
803+
804+
#[test]
805+
#[cfg(feature = "from_str")]
806+
fn drams_from_str() {
807+
let v = Volume::from_str("10dr");
808+
assert!(v.is_ok());
809+
assert_almost_eq(10.0, v.unwrap().as_drams());
810+
}
811+
812+
#[test]
813+
#[cfg(feature = "from_str")]
814+
fn liters_from_str() {
815+
let v = Volume::from_str("10L");
816+
assert!(v.is_ok());
817+
assert_almost_eq(10.0, v.unwrap().as_liters());
818+
}
819+
820+
#[test]
821+
#[cfg(feature = "from_str")]
822+
fn quarts_from_str() {
823+
let v = Volume::from_str("10qt");
824+
assert!(v.is_ok());
825+
assert_almost_eq(10.0, v.unwrap().as_quarts());
826+
}
827+
828+
#[test]
829+
#[cfg(feature = "from_str")]
830+
fn pints_from_str() {
831+
let v = Volume::from_str("10 US pt");
832+
assert!(v.is_ok());
833+
assert_almost_eq(10.0, v.unwrap().as_pints());
834+
835+
let v2 = Volume::from_str("10 US p");
836+
assert!(v2.is_ok());
837+
assert_almost_eq(10.0, v2.unwrap().as_pints());
838+
839+
let v3 = Volume::from_str("10p");
840+
assert!(v3.is_ok());
841+
assert_almost_eq(10.0, v3.unwrap().as_pints());
842+
843+
let v4 = Volume::from_str("10pt");
844+
assert!(v4.is_ok());
845+
assert_almost_eq(10.0, v4.unwrap().as_pints());
846+
}
847+
848+
#[test]
849+
#[cfg(feature = "from_str")]
850+
fn pints_uk_from_str() {
851+
let v = Volume::from_str("10 imp pt");
852+
assert!(v.is_ok());
853+
assert_almost_eq(10.0, v.unwrap().as_pints_uk());
854+
855+
let v2 = Volume::from_str("10 imp p");
856+
assert!(v2.is_ok());
857+
assert_almost_eq(10.0, v2.unwrap().as_pints_uk());
858+
}
859+
860+
#[test]
861+
#[cfg(feature = "from_str")]
862+
fn default_from_str() {
863+
let v = Volume::from_str("10");
864+
assert!(v.is_ok());
865+
assert_almost_eq(10.0, v.unwrap().as_liters());
866+
}
867+
868+
#[test]
869+
#[cfg(feature = "from_str")]
870+
fn invalid_from_str() {
871+
let v = Volume::from_str("10abcd");
872+
assert_eq!(false, v.is_ok());
873+
}
597874
}

0 commit comments

Comments
 (0)