decasify 0.11.3

A CLI utility and library to cast strings to title-case according to locale specific style guides including Turkish support
Documentation
// SPDX-FileCopyrightText: © 2023 Caleb Maclennan <caleb@alerque.com>
// SPDX-License-Identifier: LGPL-3.0-only

use regex::Regex;
use std::{borrow::Cow, fmt, fmt::Display, str::FromStr};
use unicode_titlecase::StrTitleCase;

use crate::types::{Error, Result, Word};

#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Chunk {
    pub segments: Vec<Segment>,
}

#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Segment {
    Separator(String),
    Word(Word),
}

fn split_chunk(s: &str) -> Chunk {
    let mut segments: Vec<Segment> = Vec::new();
    let captures = Regex::new(r"(?<separator>\p{Whitespace}+)|(?<word>\P{Whitespace}+)").unwrap();
    for capture in captures.captures_iter(s) {
        if let Some(m) = capture.name("separator") {
            segments.push(Segment::Separator(m.as_str().to_string()));
        } else if let Some(m) = capture.name("word") {
            segments.push(Segment::Word(Word {
                word: m.as_str().to_owned(),
            }));
        }
    }
    Chunk { segments }
}

impl From<String> for Chunk {
    fn from(s: String) -> Self {
        split_chunk(s.as_ref())
    }
}

impl From<&String> for Chunk {
    fn from(s: &String) -> Self {
        split_chunk(s.as_ref())
    }
}

impl From<&str> for Chunk {
    fn from(s: &str) -> Self {
        split_chunk(s)
    }
}

impl From<&Cow<'_, str>> for Chunk {
    fn from(s: &Cow<'_, str>) -> Self {
        split_chunk(s)
    }
}

impl FromStr for Chunk {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self> {
        Ok(split_chunk(s))
    }
}

impl From<Chunk> for String {
    fn from(c: Chunk) -> Self {
        let mut s = String::new();
        for segment in c.segments {
            s.push_str(segment.to_string().as_ref());
        }
        s
    }
}

impl Display for Chunk {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for segment in &self.segments {
            fmt.write_str(segment.to_string().as_ref())?;
        }
        Ok(())
    }
}

impl Display for Segment {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Segment::Separator(string) => fmt.write_str(string)?,
            Segment::Word(word) => fmt.write_str(word.to_string().as_ref())?,
        };
        Ok(())
    }
}

impl Word {
    pub fn to_lowercase(&self) -> String {
        self.word.to_lowercase()
    }
    pub fn to_uppercase(&self) -> String {
        self.word.to_uppercase()
    }
}

impl From<&str> for Word {
    fn from(word: &str) -> Self {
        Self {
            word: word.to_string(),
        }
    }
}

impl From<&std::string::String> for Word {
    fn from(word: &std::string::String) -> Self {
        Self {
            word: word.to_string(),
        }
    }
}

impl From<String> for Word {
    fn from(word: String) -> Self {
        Self { word }
    }
}

impl StrTitleCase for Word {
    fn to_titlecase(&self) -> String {
        self.word.to_titlecase()
    }
    fn to_titlecase_lower_rest(&self) -> String {
        self.word.to_titlecase_lower_rest()
    }
    fn to_titlecase_tr_or_az(&self) -> String {
        self.word.to_titlecase_tr_or_az()
    }
    fn to_titlecase_tr_or_az_lower_rest(&self) -> String {
        self.word.to_titlecase_tr_or_az_lower_rest()
    }
    fn starts_titlecase(&self) -> bool {
        self.word.starts_titlecase()
    }
    fn starts_titlecase_rest_lower(&self) -> bool {
        self.word.starts_titlecase_rest_lower()
    }
}

impl Display for Word {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(self.word.as_ref())?;
        Ok(())
    }
}