include_sass/
lib.rs

1#![cfg_attr(feature = "nightly", feature(track_path))]
2
3use std::{cell::RefCell, collections::HashSet, path::PathBuf};
4
5use grass_compiler::StdFs;
6use proc_macro::TokenStream;
7#[cfg(not(feature = "nightly"))]
8use quote::format_ident;
9use syn::{parse_macro_input, LitStr};
10
11use quote::__private::TokenStream as TokenStream2;
12
13#[derive(Debug)]
14struct FileTracker<'a> {
15    files: RefCell<HashSet<PathBuf>>,
16    fs: &'a dyn grass_compiler::Fs,
17}
18
19impl<'a> grass_compiler::Fs for FileTracker<'a> {
20    fn is_dir(&self, path: &std::path::Path) -> bool {
21        #[cfg(feature = "nightly")]
22        if let Ok(p) = std::fs::canonicalize(path) {
23            self.files.borrow_mut().insert(p);
24        }
25
26        self.fs.is_dir(path)
27    }
28
29    fn is_file(&self, path: &std::path::Path) -> bool {
30        #[cfg(feature = "nightly")]
31        if let Ok(p) = std::fs::canonicalize(path) {
32            self.files.borrow_mut().insert(p);
33        }
34
35        self.fs.is_file(path)
36    }
37
38    fn read(&self, path: &std::path::Path) -> std::io::Result<Vec<u8>> {
39        if let Ok(p) = std::fs::canonicalize(path) {
40            self.files.borrow_mut().insert(p);
41        }
42
43        self.fs.read(path)
44    }
45}
46
47#[cfg(not(feature = "nightly"))]
48fn track_files(files: &HashSet<PathBuf>) -> TokenStream2 {
49    let mut s: TokenStream2 = quote::quote!();
50
51    for (idx, file) in files.iter().enumerate() {
52        let ident = format_ident!("__VAR{}", idx);
53        let file_name = file.to_string_lossy();
54        s.extend::<TokenStream2>(quote::quote!(
55            const #ident: &str = include_str!(#file_name);
56        ));
57    }
58
59    s
60}
61
62#[cfg(feature = "nightly")]
63fn track_files(files: &HashSet<PathBuf>) {
64    for file in files {
65        proc_macro::tracked_path::path(file.to_string_lossy());
66    }
67}
68
69#[cfg(not(feature = "nightly"))]
70fn finish(css: String, files: &HashSet<PathBuf>) -> TokenStream {
71    let files = track_files(files);
72
73    quote::quote!(
74        {
75            #files
76            #css
77        }
78    )
79    .into()
80}
81
82#[cfg(feature = "nightly")]
83fn finish(css: String, files: &HashSet<PathBuf>) -> TokenStream {
84    track_files(files);
85    quote::quote!(#css).into()
86}
87
88#[proc_macro]
89pub fn include_sass(item: TokenStream) -> TokenStream {
90    let input = parse_macro_input!(item as LitStr);
91
92    let options = grass_compiler::Options::default();
93
94    let fs = FileTracker {
95        files: RefCell::new(HashSet::new()),
96        fs: &StdFs,
97    };
98
99    let value = input.value();
100
101    let css = match grass_compiler::from_path(
102        value,
103        &options
104            .fs(&fs)
105            .style(grass_compiler::OutputStyle::Compressed),
106    ) {
107        Ok(css) => css,
108        Err(e) => {
109            let err = syn::Error::new(input.span(), format!("Failed to compile Sass\n{}", e));
110            return syn::Error::into_compile_error(err).into();
111        }
112    };
113
114    let files = &*fs.files.borrow();
115
116    finish(css, files)
117}