1use std::fmt;
3use std::str::FromStr;
4
5use thiserror::Error;
6
7#[derive(Error, Debug)]
8pub enum GenderError {
9 #[error("could not parse gender from string")]
10 #[allow(dead_code)]
11 ParseError,
12}
13
14#[derive(Debug, PartialEq, Eq, Hash, Clone)]
16pub enum Gender {
17 Unknown,
18 Ambiguous,
19 Female,
20 Male,
21 Open(String),
22}
23
24#[derive(Default, Debug)]
26pub struct GenderBag {
27 size: usize,
28 mask: u32,
29 resolved: Gender,
30}
31
32impl Default for Gender {
33 fn default() -> Self {
34 Gender::Unknown
35 }
36}
37
38impl FromStr for Gender {
39 type Err = GenderError;
40
41 fn from_str(s: &str) -> Result<Gender, GenderError> {
42 Ok(s.into())
43 }
44}
45
46impl From<&str> for Gender {
47 fn from(s: &str) -> Gender {
48 let sg = s.trim().to_lowercase();
49 match sg.as_str() {
50 "unknown" => Gender::Unknown,
51 "ambiguous" => Gender::Ambiguous,
52 "female" => Gender::Female,
53 "male" => Gender::Male,
54 _ => Gender::Open(sg),
55 }
56 }
57}
58
59impl From<String> for Gender {
60 fn from(s: String) -> Gender {
61 s.as_str().into()
62 }
63}
64
65impl<T> From<&T> for Gender
66where
67 T: Into<Gender> + Clone,
68{
69 fn from(obj: &T) -> Gender {
70 obj.clone().into()
71 }
72}
73
74impl fmt::Display for Gender {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Gender::Unknown => f.write_str("unknown"),
78 Gender::Ambiguous => f.write_str("ambiguous"),
79 Gender::Female => f.write_str("female"),
80 Gender::Male => f.write_str("male"),
81 Gender::Open(s) => f.write_str(s.as_str()),
82 }
83 }
84}
85
86impl Gender {
87 pub fn merge(&self, other: &Gender) -> Gender {
90 match (self, other) {
91 (Gender::Unknown, g) => g.clone(),
92 (g, Gender::Unknown) => g.clone(),
93 (g1, g2) if g1 == g2 => g2.clone(),
94 _ => Gender::Ambiguous,
95 }
96 }
97
98 fn mask_val(&self) -> u32 {
100 match self {
101 Gender::Unknown => 1,
102 Gender::Ambiguous => 2,
103 Gender::Female => 4,
104 Gender::Male => 8,
105 Gender::Open(_) => 0x8000_0000,
106 }
107 }
108}
109
110impl GenderBag {
111 pub fn add(&mut self, gender: Gender) {
113 self.size += 1;
114 self.mask |= gender.mask_val();
115 self.resolved = self.resolved.merge(&gender);
116 }
117
118 pub fn merge_from(&mut self, bag: &GenderBag) {
120 self.size += bag.size;
121 self.mask |= bag.mask;
122 self.resolved = self.resolved.merge(&bag.resolved);
123 }
124
125 #[allow(dead_code)]
127 pub fn len(&self) -> usize {
128 self.size
129 }
130
131 pub fn is_empty(&self) -> bool {
133 self.size == 0
134 }
135
136 #[allow(dead_code)]
138 pub fn maybe_gender(&self) -> Option<&Gender> {
139 if self.is_empty() {
140 None
141 } else {
142 Some(&self.resolved)
143 }
144 }
145
146 pub fn to_gender(&self) -> &Gender {
148 &self.resolved
149 }
150}
151
152#[test]
153pub fn test_bag_empty() {
154 let bag = GenderBag::default();
155 assert_eq!(bag.to_gender(), &Gender::Unknown);
156}
157
158#[test]
159pub fn test_bag_female() {
160 let mut bag = GenderBag::default();
161 bag.add("female".into());
162 assert_eq!(bag.to_gender(), &Gender::Female);
163}
164
165#[test]
166pub fn test_bag_male() {
167 let mut bag = GenderBag::default();
168 bag.add("male".into());
169 assert_eq!(bag.to_gender(), &Gender::Male);
170}
171
172#[test]
173pub fn test_bag_mf() {
174 let mut bag = GenderBag::default();
175 bag.add("male".into());
176 bag.add("female".into());
177 assert_eq!(bag.to_gender(), &Gender::Ambiguous);
178}
179
180#[test]
181pub fn test_bag_ff() {
182 let mut bag = GenderBag::default();
183 bag.add("female".into());
184 bag.add("female".into());
185 assert_eq!(bag.to_gender(), &Gender::Female);
186}