includer_codegen/
utils.rs

1//! Tools to build custom `Pipeline`s that aren't already provided.
2//!
3//! Using the tools here to build custom [`Pipelines`] can help keep your
4//! pipelines more consistent, and easier to use for others.
5//!
6//! [`Pipelines`]: ../trait.Pipeline.html
7use self::Filter::*;
8use regex::Regex;
9use std::path::Path;
10
11/// The type of filter list to use.
12pub enum FilterListType {
13    Blacklist,
14    Whitelist,
15}
16
17/// A filter to determine if a file or path should be added to the `Pipeline`.
18pub enum Filter {
19    Include(FilterRule),
20    Exclude(FilterRule),
21}
22
23impl Filter {
24    /// Create a filter that includes a file extension.
25    ///
26    /// Can panic, see [`FilterRule::extension`].
27    ///
28    /// [`FilterRule::extension`]: ./enum.FilterRule.html#method.extension
29    pub fn include_extension<S: Into<String>>(ext: S) -> Self {
30        Include(FilterRule::extension(ext))
31    }
32
33    /// Create a filter that excludes a file extension.
34    ///
35    /// Can panic, see [`FilterRule::extension`].
36    ///
37    /// [`FilterRule::extension`]: ./enum.FilterRule.html#method.extension
38    pub fn exclude_extension<S: Into<String>>(ext: S) -> Self {
39        Exclude(FilterRule::extension(ext))
40    }
41
42    /// Create a filter that includes a regex.
43    ///
44    /// Can panic, see [`FilterRule::regex`].
45    ///
46    /// [`FilterRule::regex`]: ./enum.FilterRule.html#method.regex
47    pub fn include_regex<S: AsRef<str>>(regex_str: S) -> Self {
48        Include(FilterRule::regex(regex_str))
49    }
50
51    /// Create a filter that excludes a regex.
52    ///
53    /// Can panic, see [`FilterRule::regex`].
54    ///
55    /// [`FilterRule::regex`]: ./enum.FilterRule.html#method.regex
56    pub fn exclude_regex<S: AsRef<str>>(regex_str: S) -> Self {
57        Exclude(FilterRule::regex(regex_str))
58    }
59
60    pub fn matches<P: AsRef<Path>>(&self, relative_path: P) -> bool {
61        match self {
62            Include(rule) => rule.matches(relative_path),
63            Exclude(rule) => rule.matches(relative_path),
64        }
65    }
66}
67
68/// A rule on how to match a file or path
69pub enum FilterRule {
70    /// Match any file that contains the specified extension.
71    ///
72    /// It is suggested to use the [`extension`] helper method instead for
73    /// ease of use and consistency.
74    ///
75    /// ```
76    /// # use includer_codegen::utils::FilterRule;
77    /// #
78    /// let html_files = FilterRule::Extension("html".to_string());
79    /// let inconsistent = FilterRule::Extension(".html".to_string());
80    /// ```
81    ///
82    /// Note: For consistency, the extension should *not* have a leading period
83    ///
84    /// [`extension`]: #method.extension
85    Extension(String),
86
87    /// Match any file that has a regex match on its path.
88    ///
89    /// It is suggested to use the [`regex`] helper method instead for
90    /// ease of use and consistency.
91    ///
92    /// ```
93    /// # use includer_codegen::utils::FilterRule;
94    /// use includer_codegen::regex::Regex;
95    ///
96    /// // Match all css files in the "styles" subdirectory
97    /// let css_files = FilterRule::Regex(Regex::new(r"^styles[/\\].*\.css$").unwrap());
98    /// ```
99    ///
100    /// Note: For consistency, the path compared to the regex should be
101    /// relative to the root asset path with no leading slash.  You can get
102    /// this result by using [`strip_prefix`].
103    ///
104    /// [`regex`]: #method.regex
105    /// [`strip_prefix`]: https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix
106    Regex(Regex),
107}
108
109impl FilterRule {
110    /// Creates a validated `FilterRule::Extension`
111    ///
112    /// Makes sure that the extension does not have a leading `"."`.
113    ///
114    /// ```
115    /// # use includer_codegen::utils::FilterRule;
116    /// #
117    /// // Only accept HTML files
118    /// FilterRule::extension("html");
119    /// ```
120    ///
121    /// # Panics
122    ///
123    /// This function will panic if extension is not valid.
124    ///
125    /// ```should_panic
126    /// # use includer_codegen::utils::FilterRule;
127    /// #
128    /// // should panic
129    /// FilterRule::extension(".html");
130    /// ```
131    pub fn extension<S: Into<String>>(extension: S) -> Self {
132        let ext = extension.into();
133
134        if &ext[0..1] == "." {
135            panic!("Filter::Extension should not contain a period prefix!");
136        }
137
138        FilterRule::Extension(ext)
139    }
140
141    /// Creates a validated `Filer::Regex`
142    ///
143    /// ```
144    /// # use includer_codegen::utils::FilterRule;
145    /// #
146    /// // Accept all css files that are under the root subdirectory `styles` (multi-platform)
147    /// FilterRule::regex(r"^styles[/\\].*\.css$");
148    /// ```
149    ///
150    /// # Panics
151    ///
152    /// Invalid regex expressions will panic.
153    ///
154    /// ```should_panic
155    /// # use includer_codegen::utils::FilterRule;
156    /// #
157    /// // should panic
158    /// FilterRule::regex(r"\h");
159    /// ```
160    pub fn regex<S: AsRef<str>>(regex_str: S) -> Self {
161        let regex = Regex::new(regex_str.as_ref());
162        FilterRule::Regex(regex.unwrap())
163    }
164
165    /// See if the path matches the filter rule
166    pub fn matches<P: AsRef<Path>>(&self, relative_path: P) -> bool {
167        let path = relative_path.as_ref();
168        match self {
169            FilterRule::Extension(ext) => path.extension() == Some(ext.as_ref()),
170            FilterRule::Regex(re) => re.is_match(
171                path.to_str()
172                    .expect("Path couldn't be represented by a str"),
173            ),
174        }
175    }
176}
177
178pub(crate) fn path_to_string<P: AsRef<Path>>(path: P) -> String {
179    path.as_ref()
180        .to_str()
181        .expect("Unable to represent path as str")
182        .to_string()
183}
184
185/// Makes Cargo re-run build script if path has changed since last build.
186///
187/// Note this only has an effect inside a build script, as it just prints a
188/// Cargo interpreted key to stdout.  See [`reference`].
189///
190/// # Panics
191///
192/// Panics if the path cannot be casted to a str.
193///
194/// [`reference`]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
195pub fn watch_path<P: AsRef<Path>>(p: P) {
196    println!("cargo:rerun-if-changed={}", p.as_ref().to_str().unwrap());
197}