heck_string_cli/
convert.rs

1use std::fmt::{Display, Formatter};
2
3use clap::builder::PossibleValue;
4use clap::ValueEnum;
5use heck::{
6    ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase,
7    ToShoutySnakeCase, ToSnakeCase, ToTrainCase, ToTitleCase
8};
9
10/// [clap::ValueEnum] to convert input string to a target case
11#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
12pub enum ToCase {
13    KebabCase,
14    CamelCase,
15    PascalCase,
16    ShoutyKebabCase,
17    ShoutySnakeCase,
18    SnakeCase,
19    TrainCase,
20    TitleCase,
21}
22impl Display for ToCase {
23    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
24        let variant = self.variant_name().to_kebab_case();
25        write!(f, "{variant}")
26    }
27}
28impl ToCase {
29    pub fn variant_name(&self) -> &'static str {
30        match self {
31            ToCase::KebabCase => "Kebab",
32            ToCase::CamelCase => "Camel",
33            ToCase::PascalCase => "Pascal",
34            ToCase::ShoutyKebabCase => "ShoutyKebab",
35            ToCase::ShoutySnakeCase => "ShoutySnake",
36            ToCase::SnakeCase => "Snake",
37            ToCase::TrainCase => "Train",
38            ToCase::TitleCase => "Title",
39        }
40    }
41
42    pub fn convert<T: Display>(&self, input: T) -> String {
43        let string = input.to_string();
44        match self {
45            ToCase::KebabCase => string.to_kebab_case(),
46            ToCase::CamelCase => string.to_lower_camel_case(),
47            ToCase::PascalCase => string.to_pascal_case(),
48            ToCase::ShoutyKebabCase => string.to_shouty_kebab_case(),
49            ToCase::ShoutySnakeCase => string.to_shouty_snake_case(),
50            ToCase::SnakeCase => string.to_snake_case(),
51            ToCase::TrainCase => string.to_train_case(),
52            ToCase::TitleCase => string.to_title_case(),
53        }
54    }
55
56    pub fn variant_name_with_to_prefix(&self) -> String {
57        let variant = self.variant_name();
58        format!("To{variant}")
59    }
60
61    pub fn variant_name_with_case_suffix(&self) -> String {
62        let variant = self.variant_name();
63        format!("{variant}Case")
64    }
65
66    pub fn variant_name_with_prefix_and_suffix(&self) -> String {
67        let variant = self.variant_name();
68        format!("To{variant}Case")
69    }
70
71    pub fn base_variant_names(&self) -> [String; 4] {
72        [
73            self.variant_name().to_string(),
74            self.variant_name_with_to_prefix(),
75            self.variant_name_with_case_suffix(),
76            self.variant_name_with_prefix_and_suffix(),
77        ]
78    }
79
80    pub fn variant_names(&self) -> Vec<String> {
81        let mut variants = Vec::<String>::new();
82
83        for variant in self
84            .base_variant_names()
85            .into_iter()
86            .map(|variant| {
87                [
88                    variant.to_kebab_case(),
89                    variant.to_lower_camel_case(),
90                    variant.to_pascal_case(),
91                    variant.to_train_case(),
92                    variant.to_title_case(),
93                    variant.to_snake_case(),
94                    variant.to_shouty_snake_case(),
95                    variant.to_shouty_kebab_case(),
96                ]
97                .to_vec()
98            })
99            .flatten()
100        {
101            if !variants.contains(&variant) {
102                variants.push(variant);
103            }
104        }
105        variants
106    }
107
108    pub fn variants<'a>() -> &'a [ToCase] {
109        &[
110            ToCase::KebabCase,
111            ToCase::CamelCase,
112            ToCase::PascalCase,
113            ToCase::ShoutyKebabCase,
114            ToCase::ShoutySnakeCase,
115            ToCase::SnakeCase,
116            ToCase::TrainCase,
117            ToCase::TitleCase,
118        ]
119    }
120}
121impl ValueEnum for ToCase {
122    fn value_variants<'a>() -> &'a [ToCase] {
123        ToCase::variants()
124    }
125
126    fn to_possible_value(&self) -> Option<PossibleValue> {
127        let variants = self.variant_names();
128        let mut pv = PossibleValue::new(variants[0].to_string());
129        for variant in &variants[1..] {
130            pv = pv.alias(variant);
131        }
132        Some(pv)
133    }
134
135    fn from_str(
136        val: &str,
137        ignore_case: bool,
138    ) -> std::result::Result<ToCase, String> {
139        let val = if ignore_case {
140            val.to_lowercase()
141        } else {
142            val.to_string()
143        };
144        let val = val.trim();
145        for variant in ToCase::variants() {
146            for variant_name in
147                variant
148                    .variant_names()
149                    .into_iter()
150                    .map(|variant| {
151                        if ignore_case {
152                            variant.to_lowercase().to_string()
153                        } else {
154                            variant.to_string()
155                        }
156                    })
157            {
158                if val.to_string() == variant_name {
159                    return Ok(variant.clone());
160                }
161            }
162        }
163        return Err(val.to_string());
164    }
165}