sass_rocket_fairing/
lib.rs

1mod context;
2
3use rocket::{
4    fairing::{Fairing, Info, Kind},
5    log::PaintExt,
6    yansi::Paint,
7    Build, Orbit, Rocket,
8};
9
10use std::path::PathBuf;
11
12// Re-exports
13// pub use sass_rs;
14pub use context::{Context, ContextManager};
15pub use rsass;
16
17const DEFAULT_SASS_DIR: &str = "static/sass";
18const DEFAULT_CSS_DIR: &str = "static/css";
19
20/// Compiles a single sass file and returns the resultant `String`
21/// Using the rsass format specified
22pub fn compile_file(path_buf: PathBuf, format: rsass::output::Format) -> Result<String, String> {
23    match rsass::compile_scss_path(path_buf.as_path(), format) {
24        Ok(res) => Ok(String::from_utf8(res).unwrap()),
25        Err(e) => Err(e.to_string()),
26    }
27}
28
29/// Main user facing rocket `Fairing`
30pub struct SassFairing {
31    rsass_format: rsass::output::Format,
32}
33
34impl SassFairing {
35    /// Creates a new `SassFairing` with the specified format
36    pub fn new(format: rsass::output::Format) -> Self {
37        Self {
38            rsass_format: format,
39        }
40    }
41}
42
43impl Default for SassFairing {
44    fn default() -> Self {
45        Self {
46            rsass_format: rsass::output::Format::default(),
47        }
48    }
49}
50
51#[rocket::async_trait]
52impl Fairing for SassFairing {
53    fn info(&self) -> Info {
54        let kind = Kind::Ignite | Kind::Liftoff | Kind::Singleton;
55
56        // Enable Request Kind in debug mode
57        #[cfg(debug_assertions)]
58        let kind = kind | Kind::Request;
59
60        Info {
61            name: "Sass Compiler",
62            kind,
63        }
64    }
65
66    async fn on_ignite(&self, rocket: Rocket<Build>) -> rocket::fairing::Result {
67        use rocket::figment::value::magic::RelativePathBuf;
68
69        // Get sass directory
70        let sass_dir = rocket
71            .figment()
72            .extract_inner::<RelativePathBuf>("sass_dir")
73            .map(|path| path.relative());
74
75        let sass_path = match sass_dir {
76            Ok(dir) => dir,
77            Err(e) if e.missing() => DEFAULT_SASS_DIR.into(),
78            Err(e) => {
79                rocket::config::pretty_print_error(e);
80                return Err(rocket);
81            }
82        };
83
84        // Get css directory
85        let css_dir = rocket
86            .figment()
87            .extract_inner::<RelativePathBuf>("css_dir")
88            .map(|path| path.relative());
89
90        let css_path = match css_dir {
91            Ok(dir) => dir,
92            Err(e) if e.missing() => DEFAULT_CSS_DIR.into(),
93            Err(e) => {
94                rocket::config::pretty_print_error(e);
95                return Err(rocket);
96            }
97        };
98
99        if let Some(ctx) = Context::initialize(&sass_path, &css_path, self.rsass_format) {
100            Ok(rocket.manage(ContextManager::new(ctx)))
101        } else {
102            rocket::error!("Sass Initialization failed. Aborting launch.");
103            Err(rocket)
104        }
105    }
106
107    async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
108        let ctx_manager = rocket
109            .state::<ContextManager>()
110            .expect("Sass Context not registered in on_ignite");
111
112        let context = &*ctx_manager.context();
113
114        let sass_dir = context
115            .sass_dir
116            .strip_prefix(std::env::current_dir().unwrap())
117            .unwrap();
118        let css_dir = context
119            .css_dir
120            .strip_prefix(std::env::current_dir().unwrap())
121            .unwrap();
122
123        rocket::info!("{}{}:", Paint::emoji("✨ "), Paint::magenta("Sass"));
124        rocket::info_!("sass directory: {}", Paint::white(sass_dir.display()));
125        rocket::info_!("css directory: {}", Paint::white(css_dir.display()));
126
127        // Precompile sass files if in debug mode
128        if cfg!(debug_assertions) {
129            rocket::info_!("pre-compiling sass files");
130            ctx_manager.compile_all_and_write();
131        }
132    }
133
134    /// Calls `ContextManager.reload_if_needed` on new incoming request.
135    /// Only applicable in debug builds
136    #[cfg(debug_assertions)]
137    async fn on_request(&self, req: &mut rocket::Request<'_>, _data: &mut rocket::Data<'_>) {
138        let context_manager = req
139            .rocket()
140            .state::<ContextManager>()
141            .expect("Sass ContextManager not registered in on_ignite");
142
143        context_manager.reload_if_needed();
144    }
145}