1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use regex::Regex;
use std::{error, result};
use unicode_titlecase::StrTitleCase;

#[cfg(feature = "luamodule")]
use mlua::prelude::*;

#[cfg(feature = "luamodule")]
#[mlua::lua_module]
fn decasify(lua: &Lua) -> LuaResult<LuaTable> {
    let exports = lua.create_table().unwrap();
    let titlecase = lua.create_function(titlecase)?;
    exports.set("titlecase", titlecase).unwrap();
    Ok(exports)
}

#[cfg(feature = "luamodule")]
fn titlecase<'a>(
    lua: &'a Lua,
    (input, locale): (LuaString<'a>, LuaString<'a>),
) -> LuaResult<LuaString<'a>> {
    let input = input.to_string_lossy();
    let locale = locale.to_string_lossy();
    let output = to_titlecase(&input, &locale);
    lua.create_string(&output)
}

#[cfg(feature = "cli")]
pub mod cli;

pub type Result<T> = result::Result<T, Box<dyn error::Error>>;

/// Convert a string to title case following typestting conventions for a target locale
pub fn to_titlecase(string: &str, locale: &str) -> String {
    let words: Vec<&str> = string.split_whitespace().collect();
    match locale {
        "tr" => to_titlecase_tr(words),
        "tr_TR" => to_titlecase_tr(words),
        _ => to_titlecase_en(words),
    }
}

fn to_titlecase_en(words: Vec<&str>) -> String {
    let mut words = words.iter();
    let mut output: Vec<String> = Vec::new();
    let first = words.next().unwrap();
    output.push(first.to_titlecase_lower_rest());
    for word in words {
        match is_reserved_en(word.to_string()) {
            true => output.push(word.to_string().to_lowercase()),
            false => {
                output.push(word.to_titlecase_lower_rest());
            }
        }
    }
    output.join(" ")
}

fn to_titlecase_tr(words: Vec<&str>) -> String {
    let mut words = words.iter();
    let mut output: Vec<String> = Vec::new();
    let first = words.next().unwrap();
    output.push(first.to_titlecase_tr_or_az_lower_rest());
    for word in words {
        match is_reserved_tr(word.to_string()) {
            true => output.push(word.to_string().to_lowercase()),
            false => {
                output.push(word.to_titlecase_tr_or_az_lower_rest());
            }
        }
    }
    output.join(" ")
}

fn is_reserved_en(word: String) -> bool {
    let word = word.to_lowercase();
    let congunction = Regex::new(r"^(and|or)$").unwrap();
    congunction.is_match(word.as_str())
}

fn is_reserved_tr(word: String) -> bool {
    let baglac = Regex::new(
        r"^([Vv][Ee]|[İi][Ll][Ee]|[Yy][Aa]|[Vv][Ee]|[Yy][Aa][Hh][Uu][Tt]|[Kk][İi]|[Dd][AaEe])$",
    )
    .unwrap();
    let soruek = Regex::new(r"^([Mm][İiIıUuÜü])").unwrap();
    let word = word.as_str();
    baglac.is_match(word) || soruek.is_match(word)
}