chemstring/
lib.rs

1//! A library for parsing strings into chemical symbols permutations.
2//! <br>
3//!
4//! # Example
5//! ```
6//! use chemstring::ChemString;
7//!
8//! let chem_string = ChemString::parse("seal").unwrap();
9//! assert_eq!(chem_string.results(), vec!["Se Al"]);
10//!
11//! let chem_string = ChemString::parse("bichon").unwrap();
12//! let possible_permutation = "Bi C H O N".to_string();
13//! assert!(chem_string.results().contains(&possible_permutation));
14//!
15//! ````
16
17pub mod errors;
18pub mod parsing_utils;
19pub mod symbols;
20
21use errors::ChemStringErrors;
22use parsing_utils::parse_string;
23
24/// A `ChemString` is a string that can be written using only chemical symbols.
25/// The `ChemString` struct has a single field, `0`, which is a vector of all the possible
26/// chemical symbols permutations that can be formed from the string.
27pub struct ChemString(Vec<String>);
28
29impl ChemString {
30    /// Attemtps to parse a string and returns a `Result` containing a `ChemString` if the
31    /// string is valid, and an error otherwise. The function is case-insensitive and will
32    /// return an error if the input string is empty or contains any non-alphabetic characters.
33    pub fn parse(s: impl Into<String>) -> Result<Self, ChemStringErrors> {
34        let s: String = s.into();
35        if s.is_empty() {
36            return Err(ChemStringErrors::EmptyString);
37        }
38        let s = s.split_whitespace().collect::<String>().to_lowercase();
39
40        if s.chars().any(|c| !c.is_ascii_alphabetic()) {
41            return Err(ChemStringErrors::InvalidCharacter);
42        }
43
44        let result = parse_string(s);
45
46        Ok(ChemString(result))
47    }
48
49    /// Returns a vector of all the possible chemical symbols permutations that can be formed
50    /// from the string.
51    pub fn results(&self) -> Vec<String> {
52        self.0.clone()
53    }
54
55    /// Returns the number of possible chemical symbols permutations that can be formed from the string.
56    pub fn count(&self) -> usize {
57        self.0.len()
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn valid_chem_string_with_one_possible_result_is_parsed() {
67        let chem_string = "seal";
68
69        let result = ChemString::parse(chem_string).unwrap();
70
71        assert_eq!(result.0, vec!["Se Al"]);
72    }
73
74    #[test]
75    fn valid_chem_string_with_multiple_possible_results_is_parsed() {
76        let chem_string = "bichon";
77        let mut possible_results = ["Bi C H O N", "B I C H O N", "Bi C Ho N", "B I C Ho N"];
78
79        let mut result = ChemString::parse(chem_string).unwrap();
80        possible_results.sort();
81        result.0.sort();
82
83        assert_eq!(result.0, possible_results);
84    }
85
86    #[test]
87    fn invalid_chem_strings_are_not_parsed() {
88        let invalid_chem_strings = vec![
89            "",
90            "123",
91            "bichon123",
92            "bichon 123",
93            "?#!$@",
94            "bichon??",
95            "bichon ??",
96        ];
97
98        for invalid_chem_string in invalid_chem_strings {
99            let result = ChemString::parse(invalid_chem_string);
100
101            assert!(result.is_err());
102        }
103    }
104}