rust_store_core/naming/
canonical.rs1#[inline]
5fn is_sep(c: char) -> bool {
6 matches!(c, '_' | '-' | '.' | ' ')
7}
8
9fn flush(cur: &mut String, out: &mut Vec<String>) {
10 if !cur.is_empty() {
11 out.push(std::mem::take(cur).to_lowercase());
12 }
13}
14
15pub fn canonical(s: &str) -> Vec<String> {
24 let chars: Vec<char> = s.chars().collect();
25 let mut out: Vec<String> = Vec::new();
26 let mut cur = String::new();
27 for i in 0..chars.len() {
28 let c = chars[i];
29 if is_sep(c) || !c.is_ascii_alphanumeric() {
30 flush(&mut cur, &mut out);
31 continue;
32 }
33 if cur.is_empty() {
34 cur.push(c);
35 continue;
36 }
37 let prev = cur.chars().last().unwrap();
38 if c.is_ascii_uppercase() {
39 let prev_lower_or_digit = prev.is_ascii_lowercase() || prev.is_ascii_digit();
40 let next_lower = chars
41 .get(i + 1)
42 .map(|n| n.is_ascii_lowercase())
43 .unwrap_or(false);
44 if prev_lower_or_digit || (prev.is_ascii_uppercase() && next_lower) {
46 flush(&mut cur, &mut out);
47 }
48 cur.push(c);
49 } else {
50 cur.push(c);
51 }
52 }
53 flush(&mut cur, &mut out);
54 out
55}
56
57fn cap(s: &str) -> String {
58 let mut it = s.chars();
59 match it.next() {
60 Some(first) => first.to_ascii_uppercase().to_string() + it.as_str(),
61 None => String::new(),
62 }
63}
64
65pub fn to_snake(tokens: &[String]) -> String {
67 tokens.join("_")
68}
69
70pub fn to_camel(tokens: &[String]) -> String {
72 let mut out = String::new();
73 for (i, t) in tokens.iter().enumerate() {
74 if i == 0 {
75 out.push_str(t);
76 } else {
77 out.push_str(&cap(t));
78 }
79 }
80 out
81}
82
83pub fn to_pascal(tokens: &[String]) -> String {
85 tokens.iter().map(|t| cap(t)).collect()
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 fn v(xs: &[&str]) -> Vec<String> {
93 xs.iter().map(|s| s.to_string()).collect()
94 }
95
96 #[test]
98 fn canonical_design_vectors() {
99 assert_eq!(canonical("orderTotal"), v(&["order", "total"]));
100 assert_eq!(canonical("order_total"), v(&["order", "total"]));
101 assert_eq!(canonical("Order.Total"), v(&["order", "total"]));
102 assert_eq!(canonical("order2Items"), v(&["order2", "items"]));
103 assert_eq!(canonical("HTTPServer"), v(&["http", "server"]));
104 assert_eq!(canonical("userID"), v(&["user", "id"]));
105 }
106
107 #[test]
108 fn canonical_edges() {
109 assert_eq!(canonical(""), v(&[]));
110 assert_eq!(canonical("order-Total"), v(&["order", "total"]));
111 assert_eq!(canonical("order total"), v(&["order", "total"])); assert_eq!(canonical("_id"), v(&["id"])); assert_eq!(canonical("HTTP"), v(&["http"])); }
115
116 #[test]
118 fn reassemble_vectors() {
119 let t = v(&["order", "total"]);
120 assert_eq!(to_snake(&t), "order_total");
121 assert_eq!(to_camel(&t), "orderTotal");
122 assert_eq!(to_pascal(&t), "OrderTotal");
123 }
124
125 #[test]
127 fn roundtrip_stable() {
128 for s in [
129 "orderTotal",
130 "order_total",
131 "Order.Total",
132 "userID",
133 "HTTPServer",
134 ] {
135 let t = canonical(s);
136 assert_eq!(canonical(&to_snake(&t)), t);
137 assert_eq!(canonical(&to_camel(&t)), t);
138 assert_eq!(canonical(&to_pascal(&t)), t);
139 }
140 }
141}