Skip to main content

sqruff_lib/templaters/
types.rs

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}