1use fancy_regex::Regex;
2
3#[cfg(feature = "python")]
4use super::{DBT_TEMPLATER, JINJA_TEMPLATER, PYTHON_TEMPLATER};
5use super::{PLACEHOLDER_TEMPLATER, RAW_TEMPLATER, Templater};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum TemplaterKind {
9 Raw,
10 Placeholder,
11 #[cfg(feature = "python")]
12 Python,
13 #[cfg(feature = "python")]
14 Jinja,
15 #[cfg(feature = "python")]
16 Dbt,
17}
18
19impl TemplaterKind {
20 pub const fn as_str(self) -> &'static str {
21 match self {
22 Self::Raw => "raw",
23 Self::Placeholder => "placeholder",
24 #[cfg(feature = "python")]
25 Self::Python => "python",
26 #[cfg(feature = "python")]
27 Self::Jinja => "jinja",
28 #[cfg(feature = "python")]
29 Self::Dbt => "dbt",
30 }
31 }
32
33 pub fn templater(self) -> &'static dyn Templater {
34 match self {
35 Self::Raw => &RAW_TEMPLATER,
36 Self::Placeholder => &PLACEHOLDER_TEMPLATER,
37 #[cfg(feature = "python")]
38 Self::Python => &PYTHON_TEMPLATER,
39 #[cfg(feature = "python")]
40 Self::Jinja => &JINJA_TEMPLATER,
41 #[cfg(feature = "python")]
42 Self::Dbt => &DBT_TEMPLATER,
43 }
44 }
45
46 pub fn available_names() -> Vec<&'static str> {
47 Self::available().iter().map(|kind| kind.as_str()).collect()
48 }
49
50 pub const fn available() -> &'static [Self] {
51 #[cfg(feature = "python")]
52 {
53 &[
54 Self::Raw,
55 Self::Placeholder,
56 Self::Python,
57 Self::Jinja,
58 Self::Dbt,
59 ]
60 }
61
62 #[cfg(not(feature = "python"))]
63 {
64 &[Self::Raw, Self::Placeholder]
65 }
66 }
67
68 pub fn from_name(s: &str) -> Result<Self, String> {
69 match s {
70 "raw" => Ok(Self::Raw),
71 "placeholder" => Ok(Self::Placeholder),
72 #[cfg(feature = "python")]
73 "python" => Ok(Self::Python),
74 #[cfg(feature = "python")]
75 "jinja" => Ok(Self::Jinja),
76 #[cfg(feature = "python")]
77 "dbt" => Ok(Self::Dbt),
78 _ => Err(format!(
79 "Unknown templater '{}'. Available templaters: {}",
80 s,
81 Self::available_names().join(", ")
82 )),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88pub enum PlaceholderStyle {
89 Colon,
90 ColonNoSpaces,
91 NumericColon,
92 At,
93 Pyformat,
94 Dollar,
95 FlywayVar,
96 QuestionMark,
97 NumericDollar,
98 Percent,
99 Ampersand,
100 ApacheCamel,
101}
102
103impl PlaceholderStyle {
104 pub const fn all() -> &'static [Self] {
105 &[
106 Self::Colon,
107 Self::ColonNoSpaces,
108 Self::NumericColon,
109 Self::At,
110 Self::Pyformat,
111 Self::Dollar,
112 Self::FlywayVar,
113 Self::QuestionMark,
114 Self::NumericDollar,
115 Self::Percent,
116 Self::Ampersand,
117 Self::ApacheCamel,
118 ]
119 }
120
121 pub const fn as_str(self) -> &'static str {
122 match self {
123 Self::Colon => "colon",
124 Self::ColonNoSpaces => "colon_nospaces",
125 Self::NumericColon => "numeric_colon",
126 Self::At => "at",
127 Self::Pyformat => "pyformat",
128 Self::Dollar => "dollar",
129 Self::FlywayVar => "flyway_var",
130 Self::QuestionMark => "question_mark",
131 Self::NumericDollar => "numeric_dollar",
132 Self::Percent => "percent",
133 Self::Ampersand => "ampersand",
134 Self::ApacheCamel => "apache_camel",
135 }
136 }
137
138 pub const fn regex_pattern(self) -> &'static str {
139 match self {
140 Self::Colon => r"(?<![:\w\\]):(?P<param_name>\w+)(?!:)",
141 Self::ColonNoSpaces => r"(?<!:):(?P<param_name>\w+)",
142 Self::NumericColon => r"(?<![:\w\\]):(?P<param_name>\d+)",
143 Self::At => r"(?<![:\w\\])@(?P<param_name>\w+)",
144 Self::Pyformat => r"(?<![:\w\\])%\((?P<param_name>[\w_]+)\)s",
145 Self::Dollar => r"(?<![:\w\\])\${?(?P<param_name>[\w_]+)}?",
146 Self::FlywayVar => r#"\${(?P<param_name>\w+[:\w_]+)}"#,
147 Self::QuestionMark => r"(?<![:\w\\])\?",
148 Self::NumericDollar => r"(?<![:\w\\])\${?(?P<param_name>[\d]+)}?",
149 Self::Percent => r"(?<![:\w\\])%s",
150 Self::Ampersand => r"(?<!&)&{?(?P<param_name>[\w]+)}?",
151 Self::ApacheCamel => r":#\$\{(?P<param_name>.+)}",
152 }
153 }
154
155 pub fn regex(self) -> Regex {
156 Regex::new(self.regex_pattern()).unwrap()
157 }
158
159 pub fn from_name(s: &str) -> Result<Self, String> {
160 match s {
161 "colon" => Ok(Self::Colon),
162 "colon_nospaces" => Ok(Self::ColonNoSpaces),
163 "numeric_colon" => Ok(Self::NumericColon),
164 "at" => Ok(Self::At),
165 "pyformat" => Ok(Self::Pyformat),
166 "dollar" => Ok(Self::Dollar),
167 "flyway_var" => Ok(Self::FlywayVar),
168 "question_mark" => Ok(Self::QuestionMark),
169 "numeric_dollar" => Ok(Self::NumericDollar),
170 "percent" => Ok(Self::Percent),
171 "ampersand" => Ok(Self::Ampersand),
172 "apache_camel" => Ok(Self::ApacheCamel),
173 _ => Err(format!("Unknown placeholder style '{s}'")),
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::{PlaceholderStyle, TemplaterKind};
181
182 #[test]
183 fn templater_kind_parses_and_lists_available_names() {
184 assert_eq!(TemplaterKind::from_name("raw").unwrap(), TemplaterKind::Raw);
185 assert!(TemplaterKind::available_names().contains(&"placeholder"));
186 }
187
188 #[test]
189 fn placeholder_style_builds_regex() {
190 let regex = PlaceholderStyle::QuestionMark.regex();
191 assert!(regex.is_match("?").unwrap());
192 }
193}