schmfy/
lib.rs

1//! schmfy - a library to schmfy everything
2use wasm_bindgen::prelude::*;
3
4#[derive(PartialEq, Copy, Clone)]
5enum CaseType {
6    /// the whole word is in lowercase [default]
7    Lcase,
8    /// the whole word is in uppercase
9    Ucase,
10    /// the first letter us uppercase, the rest is lowercase
11    FstUcase,
12}
13
14/// Returns the case type of a str
15fn get_case(txt: &str) -> CaseType {
16    let mut cnt_lcase: usize = 0;
17    let mut cnt_ucase: usize = 0;
18
19    let alph = txt
20        .chars()
21        .filter(|c| c.is_alphabetic())
22        .collect::<Vec<char>>();
23
24    alph.iter().for_each(|c| {
25        if c.is_uppercase() {
26            cnt_ucase += 1;
27        }
28        if c.is_lowercase() {
29            cnt_lcase += 1;
30        }
31    });
32
33    if alph.len() == 0 {
34        return CaseType::Lcase; // default
35    } else if cnt_lcase > 0 && cnt_ucase == 0 {
36        return CaseType::Lcase;
37    } else if cnt_lcase == 0 && cnt_ucase > 0 {
38        return CaseType::Ucase;
39    } else if alph[0].is_uppercase() && alph[1].is_lowercase() {
40        // atleast 2 entries
41        return CaseType::FstUcase;
42    }
43
44    CaseType::Lcase
45}
46
47fn restore_case(txt: String, case: CaseType) -> String {
48    match case {
49        CaseType::FstUcase => txt
50            .to_lowercase()
51            .chars()
52            .enumerate()
53            .map(|(pos, c)| {
54                if pos == 0 {
55                    c.to_ascii_uppercase()
56                } else {
57                    c.to_ascii_lowercase()
58                }
59            })
60            .collect::<String>(),
61        CaseType::Lcase => txt.to_lowercase(),
62        CaseType::Ucase => txt.to_uppercase(),
63    }
64}
65
66/// Schmfies any str, preserving case and everything non-alphabetical
67#[wasm_bindgen]
68pub fn schmfy(source: &str) -> String {
69    // instantly return if input is non-alphabetic single char
70    if source.len() == 1 && !source.chars().next().unwrap().is_alphabetic() {
71        return String::from(source);
72    }
73
74    let case = get_case(source);
75
76    // already schmfied
77    if source.to_lowercase().starts_with("schm") {
78        return String::from(source);
79    }
80
81    // empty
82    if source.len() == 0 {
83        return String::from(source);
84    }
85
86    // Schmfy each substring separately
87    let mut current_substr: Vec<char> = vec![];
88    let mut substrings: Vec<String> = vec![];
89    source.chars().for_each(|c| {
90        if c.is_alphabetic() {
91            current_substr.push(c)
92        } else {
93            if current_substr.len() > 0 {
94                substrings.push(current_substr.iter().collect::<String>());
95                current_substr.clear();
96            }
97            substrings.push(c.to_string())
98        }
99    });
100    if current_substr.len() > 0 {
101        substrings.push(current_substr.iter().collect::<String>());
102    }
103
104    if substrings.len() > 1 {
105        return substrings
106            .iter()
107            .map(|txt| schmfy(txt))
108            .collect::<Vec<String>>()
109            .join("");
110    }
111
112    // substrings now has to contain exactly one element
113    let source = substrings[0].to_lowercase();
114
115    if !source.chars().next().unwrap().is_alphabetic() {
116        return String::from(source);
117    }
118
119    // schmfy first char if word is no longer than 3
120    if source.len() <= 3 && case != CaseType::FstUcase {
121        let first_c_size = source.chars().next().unwrap().len_utf8();
122        let (prefix, suffix) = source.split_at(first_c_size);
123        let c = prefix.chars().next().unwrap_or('-');
124        return restore_case(schmfy_char(c) + suffix, case);
125    }
126
127    // Normal words - replace prefix before first vocal
128    // with "schm"
129    let vok_pos = source.find(|c| "aeiouäöü".contains(c)).unwrap_or(0);
130
131    let (_, suffix) = source.split_at(vok_pos);
132
133    restore_case(String::from("schm") + suffix, case)
134}
135
136/// Schmfies single char
137fn schmfy_char(c: char) -> String {
138    let mut ret = String::from("schm");
139    match c {
140        'a' | 'e' | 'i' | 'o' | 'u' | 'ä' | 'ö' | 'ü' => {
141            ret.push(c);
142        }
143        'b' | 'c' | 'd' | 'g' | 'p' | 't' | 'w' => ret.push('e'),
144        'f' | 'l' | 'm' | 'n' | 'r' | 's' => {
145            ret.push('e');
146            ret.push(c)
147        }
148        'h' | 'k' => ret.push('a'),
149        'j' => {
150            ret.push('o');
151            ret.push('t')
152        }
153        'q' => ret.push('u'),
154        'v' => {
155            ret.push('a');
156            ret.push('u')
157        }
158        'x' => {
159            ret.push('i');
160            ret.push('x')
161        }
162        'y' => ret.push(c),
163        'z' => {
164            ret.push('e');
165            ret.push('t')
166        }
167        _ => ret.push(c),
168    }
169
170    ret
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn schmfy_plaintext_tests() {
179        assert_eq!(schmfy("test"), "schmest");
180        assert_eq!(schmfy("Hello"), "Schmello");
181        assert_eq!(schmfy("HELLO"), "SCHMELLO");
182        assert_eq!(schmfy("hello"), "schmello");
183        assert_eq!(schmfy("Bar"), "Schmar");
184    }
185
186    #[test]
187    fn schmfy_mixtext_tests() {
188        assert_eq!(schmfy(">Test"), ">Schmest");
189        assert_eq!(schmfy(">tesT"), ">schmest");
190        assert_eq!(schmfy("One&Two"), "Schmone&Schmo");
191        assert_eq!(
192            schmfy("<span>Entry<br></span>"),
193            "<schman>Schmentry<schmer></schman>"
194        );
195        assert_eq!(schmfy("foo/bar/baz"), "schmefoo/schmear/schmeaz");
196        assert_eq!(
197            schmfy("long/Longer/LONGESTTT"),
198            "schmong/Schmonger/SCHMONGESTTT"
199        );
200    }
201
202    #[test]
203    fn schmfy_sentences_tests() {
204        assert_eq!(
205            schmfy("Today I am VERY tired."),
206            "Schmoday SCHMI schmam SCHMERY schmired."
207        );
208        assert_eq!(
209            schmfy("Lorem ipsum dolor sit amet, consetetur sadipscing elitr"),
210            "Schmorem schmipsum schmolor schmesit schmamet, schmonsetetur schmadipscing schmelitr"
211        );
212    }
213
214    #[test]
215    fn schmfy_code_tests() {
216        assert_eq!(
217            schmfy(
218                "#include <stdio.h>
219#include <sys/types.h>
220
221int main()
222{
223    while(1)
224        fork();
225    return 0;
226}"
227            ),
228            "#schminclude <schmio.schma>
229#schminclude <schmesys/schmes.schma>
230
231schmint schmain()
232{
233    schmile(1)
234        schmork();
235    schmeturn 0;
236}"
237        );
238
239        assert_eq!(
240            schmfy(
241                "
242```
243This is a Markdown codebox
244```
245| This | is |
246|---|---|
247| a | Markdown |
248| table | ! |"
249            ),
250            "
251```
252Schmis schmis schma Schmarkdown schmodebox
253```
254| Schmis | schmis |
255|---|---|
256| schma | Schmarkdown |
257| schmable | ! |"
258        )
259    }
260}