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}