1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! Tools to build custom `Pipeline`s that aren't already provided.
//!
//! Using the tools here to build custom [`Pipelines`] can help keep your
//! pipelines more consistent, and easier to use for others.
//!
//! [`Pipelines`]: ../trait.Pipeline.html
use self::Filter::*;
use regex::Regex;
use std::path::Path;

/// The type of filter list to use.
pub enum FilterListType {
    Blacklist,
    Whitelist,
}

/// A filter to determine if a file or path should be added to the `Pipeline`.
pub enum Filter {
    Include(FilterRule),
    Exclude(FilterRule),
}

impl Filter {
    /// Create a filter that includes a file extension.
    ///
    /// Can panic, see [`FilterRule::extension`].
    ///
    /// [`FilterRule::extension`]: ./enum.FilterRule.html#method.extension
    pub fn include_extension<S: Into<String>>(ext: S) -> Self {
        Include(FilterRule::extension(ext))
    }

    /// Create a filter that excludes a file extension.
    ///
    /// Can panic, see [`FilterRule::extension`].
    ///
    /// [`FilterRule::extension`]: ./enum.FilterRule.html#method.extension
    pub fn exclude_extension<S: Into<String>>(ext: S) -> Self {
        Exclude(FilterRule::extension(ext))
    }

    /// Create a filter that includes a regex.
    ///
    /// Can panic, see [`FilterRule::regex`].
    ///
    /// [`FilterRule::regex`]: ./enum.FilterRule.html#method.regex
    pub fn include_regex<S: AsRef<str>>(regex_str: S) -> Self {
        Include(FilterRule::regex(regex_str))
    }

    /// Create a filter that excludes a regex.
    ///
    /// Can panic, see [`FilterRule::regex`].
    ///
    /// [`FilterRule::regex`]: ./enum.FilterRule.html#method.regex
    pub fn exclude_regex<S: AsRef<str>>(regex_str: S) -> Self {
        Exclude(FilterRule::regex(regex_str))
    }

    pub fn matches<P: AsRef<Path>>(&self, relative_path: P) -> bool {
        match self {
            Include(rule) => rule.matches(relative_path),
            Exclude(rule) => rule.matches(relative_path),
        }
    }
}

/// A rule on how to match a file or path
pub enum FilterRule {
    /// Match any file that contains the specified extension.
    ///
    /// It is suggested to use the [`extension`] helper method instead for
    /// ease of use and consistency.
    ///
    /// ```
    /// # use includer_codegen::utils::FilterRule;
    /// #
    /// let html_files = FilterRule::Extension("html".to_string());
    /// let inconsistent = FilterRule::Extension(".html".to_string());
    /// ```
    ///
    /// Note: For consistency, the extension should *not* have a leading period
    ///
    /// [`extension`]: #method.extension
    Extension(String),

    /// Match any file that has a regex match on its path.
    ///
    /// It is suggested to use the [`regex`] helper method instead for
    /// ease of use and consistency.
    ///
    /// ```
    /// # use includer_codegen::utils::FilterRule;
    /// use includer_codegen::regex::Regex;
    ///
    /// // Match all css files in the "styles" subdirectory
    /// let css_files = FilterRule::Regex(Regex::new(r"^styles[/\\].*\.css$").unwrap());
    /// ```
    ///
    /// Note: For consistency, the path compared to the regex should be
    /// relative to the root asset path with no leading slash.  You can get
    /// this result by using [`strip_prefix`].
    ///
    /// [`regex`]: #method.regex
    /// [`strip_prefix`]: https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix
    Regex(Regex),
}

impl FilterRule {
    /// Creates a validated `FilterRule::Extension`
    ///
    /// Makes sure that the extension does not have a leading `"."`.
    ///
    /// ```
    /// # use includer_codegen::utils::FilterRule;
    /// #
    /// // Only accept HTML files
    /// FilterRule::extension("html");
    /// ```
    ///
    /// # Panics
    ///
    /// This function will panic if extension is not valid.
    ///
    /// ```should_panic
    /// # use includer_codegen::utils::FilterRule;
    /// #
    /// // should panic
    /// FilterRule::extension(".html");
    /// ```
    pub fn extension<S: Into<String>>(extension: S) -> Self {
        let ext = extension.into();

        if &ext[0..1] == "." {
            panic!("Filter::Extension should not contain a period prefix!");
        }

        FilterRule::Extension(ext)
    }

    /// Creates a validated `Filer::Regex`
    ///
    /// ```
    /// # use includer_codegen::utils::FilterRule;
    /// #
    /// // Accept all css files that are under the root subdirectory `styles` (multi-platform)
    /// FilterRule::regex(r"^styles[/\\].*\.css$");
    /// ```
    ///
    /// # Panics
    ///
    /// Invalid regex expressions will panic.
    ///
    /// ```should_panic
    /// # use includer_codegen::utils::FilterRule;
    /// #
    /// // should panic
    /// FilterRule::regex(r"\h");
    /// ```
    pub fn regex<S: AsRef<str>>(regex_str: S) -> Self {
        let regex = Regex::new(regex_str.as_ref());
        FilterRule::Regex(regex.unwrap())
    }

    /// See if the path matches the filter rule
    pub fn matches<P: AsRef<Path>>(&self, relative_path: P) -> bool {
        let path = relative_path.as_ref();
        match self {
            FilterRule::Extension(ext) => path.extension() == Some(ext.as_ref()),
            FilterRule::Regex(re) => re.is_match(
                path.to_str()
                    .expect("Path couldn't be represented by a str"),
            ),
        }
    }
}

pub(crate) fn path_to_string<P: AsRef<Path>>(path: P) -> String {
    path.as_ref()
        .to_str()
        .expect("Unable to represent path as str")
        .to_string()
}

/// Makes Cargo re-run build script if path has changed since last build.
///
/// Note this only has an effect inside a build script, as it just prints a
/// Cargo interpreted key to stdout.  See [`reference`].
///
/// # Panics
///
/// Panics if the path cannot be casted to a str.
///
/// [`reference`]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
pub fn watch_path<P: AsRef<Path>>(p: P) {
    println!("cargo:rerun-if-changed={}", p.as_ref().to_str().unwrap());
}