sqruff-lib 0.39.0

A high-speed SQL linter.
Documentation
use fancy_regex::Regex;

#[cfg(feature = "python")]
use super::{DBT_TEMPLATER, JINJA_TEMPLATER, PYTHON_TEMPLATER};
use super::{PLACEHOLDER_TEMPLATER, RAW_TEMPLATER, Templater};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TemplaterKind {
    Raw,
    Placeholder,
    #[cfg(feature = "python")]
    Python,
    #[cfg(feature = "python")]
    Jinja,
    #[cfg(feature = "python")]
    Dbt,
}

impl TemplaterKind {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Raw => "raw",
            Self::Placeholder => "placeholder",
            #[cfg(feature = "python")]
            Self::Python => "python",
            #[cfg(feature = "python")]
            Self::Jinja => "jinja",
            #[cfg(feature = "python")]
            Self::Dbt => "dbt",
        }
    }

    pub fn templater(self) -> &'static dyn Templater {
        match self {
            Self::Raw => &RAW_TEMPLATER,
            Self::Placeholder => &PLACEHOLDER_TEMPLATER,
            #[cfg(feature = "python")]
            Self::Python => &PYTHON_TEMPLATER,
            #[cfg(feature = "python")]
            Self::Jinja => &JINJA_TEMPLATER,
            #[cfg(feature = "python")]
            Self::Dbt => &DBT_TEMPLATER,
        }
    }

    pub fn available_names() -> Vec<&'static str> {
        Self::available().iter().map(|kind| kind.as_str()).collect()
    }

    pub const fn available() -> &'static [Self] {
        #[cfg(feature = "python")]
        {
            &[
                Self::Raw,
                Self::Placeholder,
                Self::Python,
                Self::Jinja,
                Self::Dbt,
            ]
        }

        #[cfg(not(feature = "python"))]
        {
            &[Self::Raw, Self::Placeholder]
        }
    }

    pub fn from_name(s: &str) -> Result<Self, String> {
        match s {
            "raw" => Ok(Self::Raw),
            "placeholder" => Ok(Self::Placeholder),
            #[cfg(feature = "python")]
            "python" => Ok(Self::Python),
            #[cfg(feature = "python")]
            "jinja" => Ok(Self::Jinja),
            #[cfg(feature = "python")]
            "dbt" => Ok(Self::Dbt),
            _ => Err(format!(
                "Unknown templater '{}'. Available templaters: {}",
                s,
                Self::available_names().join(", ")
            )),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PlaceholderStyle {
    Colon,
    ColonNoSpaces,
    NumericColon,
    At,
    Pyformat,
    Dollar,
    FlywayVar,
    QuestionMark,
    NumericDollar,
    Percent,
    Ampersand,
    ApacheCamel,
}

impl PlaceholderStyle {
    pub const fn all() -> &'static [Self] {
        &[
            Self::Colon,
            Self::ColonNoSpaces,
            Self::NumericColon,
            Self::At,
            Self::Pyformat,
            Self::Dollar,
            Self::FlywayVar,
            Self::QuestionMark,
            Self::NumericDollar,
            Self::Percent,
            Self::Ampersand,
            Self::ApacheCamel,
        ]
    }

    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Colon => "colon",
            Self::ColonNoSpaces => "colon_nospaces",
            Self::NumericColon => "numeric_colon",
            Self::At => "at",
            Self::Pyformat => "pyformat",
            Self::Dollar => "dollar",
            Self::FlywayVar => "flyway_var",
            Self::QuestionMark => "question_mark",
            Self::NumericDollar => "numeric_dollar",
            Self::Percent => "percent",
            Self::Ampersand => "ampersand",
            Self::ApacheCamel => "apache_camel",
        }
    }

    pub const fn regex_pattern(self) -> &'static str {
        match self {
            Self::Colon => r"(?<![:\w\\]):(?P<param_name>\w+)(?!:)",
            Self::ColonNoSpaces => r"(?<!:):(?P<param_name>\w+)",
            Self::NumericColon => r"(?<![:\w\\]):(?P<param_name>\d+)",
            Self::At => r"(?<![:\w\\])@(?P<param_name>\w+)",
            Self::Pyformat => r"(?<![:\w\\])%\((?P<param_name>[\w_]+)\)s",
            Self::Dollar => r"(?<![:\w\\])\${?(?P<param_name>[\w_]+)}?",
            Self::FlywayVar => r#"\${(?P<param_name>\w+[:\w_]+)}"#,
            Self::QuestionMark => r"(?<![:\w\\])\?",
            Self::NumericDollar => r"(?<![:\w\\])\${?(?P<param_name>[\d]+)}?",
            Self::Percent => r"(?<![:\w\\])%s",
            Self::Ampersand => r"(?<!&)&{?(?P<param_name>[\w]+)}?",
            Self::ApacheCamel => r":#\$\{(?P<param_name>.+)}",
        }
    }

    pub fn regex(self) -> Regex {
        Regex::new(self.regex_pattern()).unwrap()
    }

    pub fn from_name(s: &str) -> Result<Self, String> {
        match s {
            "colon" => Ok(Self::Colon),
            "colon_nospaces" => Ok(Self::ColonNoSpaces),
            "numeric_colon" => Ok(Self::NumericColon),
            "at" => Ok(Self::At),
            "pyformat" => Ok(Self::Pyformat),
            "dollar" => Ok(Self::Dollar),
            "flyway_var" => Ok(Self::FlywayVar),
            "question_mark" => Ok(Self::QuestionMark),
            "numeric_dollar" => Ok(Self::NumericDollar),
            "percent" => Ok(Self::Percent),
            "ampersand" => Ok(Self::Ampersand),
            "apache_camel" => Ok(Self::ApacheCamel),
            _ => Err(format!("Unknown placeholder style '{s}'")),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{PlaceholderStyle, TemplaterKind};

    #[test]
    fn templater_kind_parses_and_lists_available_names() {
        assert_eq!(TemplaterKind::from_name("raw").unwrap(), TemplaterKind::Raw);
        assert!(TemplaterKind::available_names().contains(&"placeholder"));
    }

    #[test]
    fn placeholder_style_builds_regex() {
        let regex = PlaceholderStyle::QuestionMark.regex();
        assert!(regex.is_match("?").unwrap());
    }
}