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