|
1 | 1 | //! Types and constants for handling volumes (that is, three-dimensional space, not loudness).
|
2 | 2 |
|
3 | 3 | use super::measurement::*;
|
| 4 | +#[cfg(feature = "from_str")] |
| 5 | +use regex::Regex; |
| 6 | +#[cfg(feature = "from_str")] |
| 7 | +use std::str::FromStr; |
4 | 8 |
|
5 | 9 | /// The `Volume` struct can be used to deal with volumes in a common way.
|
6 | 10 | ///
|
@@ -317,12 +321,59 @@ impl Measurement for Volume {
|
317 | 321 | }
|
318 | 322 | }
|
319 | 323 |
|
| 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 | + |
320 | 371 | implement_measurement! { Volume }
|
321 | 372 |
|
322 | 373 | #[cfg(test)]
|
323 | 374 | mod test {
|
324 |
| - use volume::*; |
325 | 375 | use test_utils::assert_almost_eq;
|
| 376 | + use volume::*; |
326 | 377 |
|
327 | 378 | // Volume Units
|
328 | 379 | // Metric
|
@@ -594,4 +645,230 @@ mod test {
|
594 | 645 | assert_eq!(a >= b, false);
|
595 | 646 | }
|
596 | 647 |
|
| 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 | + } |
597 | 874 | }
|
0 commit comments