Skip to main content

sass_rocket_fairing/
context.rs

1use normpath::PathExt;
2
3use std::path::{Path, PathBuf};
4
5/// A Shared reference containing configuration data
6pub struct Context {
7    pub sass_dir: PathBuf,
8    pub css_dir: PathBuf,
9    pub rsass_format: rsass::output::Format,
10}
11
12impl Context {
13    /// Initializes the `Context` while checking for bad configuration
14    pub fn initialize(sass_dir: &Path, css_dir: &Path, rsass_format: rsass::output::Format) -> Option<Self> {
15        let sass_dir_buf = match sass_dir.normalize() {
16            Ok(dir) => dir.into_path_buf(),
17            Err(e) => {
18                rocket::error!("Invalid sass directory '{}': {}.", sass_dir.display(), e);
19                return None;
20            }
21        };
22        
23        let css_dir_buf = match css_dir.normalize() {
24            Ok(dir) => dir.into_path_buf(),
25            Err(e) => {
26                rocket::error_!("Invalid css directory '{}': {}.", css_dir.display(), e);
27                return None;
28            }
29        };
30
31        Some(Self { sass_dir: sass_dir_buf, css_dir: css_dir_buf, rsass_format: rsass_format })
32    }
33}
34
35pub use self::manager::ContextManager;
36
37#[cfg(not(debug_assertions))]
38mod manager {
39    use std::ops::Deref;
40    use crate::Context;
41
42    pub struct ContextManager(Context);
43
44    impl ContextManager {
45        pub fn new(ctx: Context) -> ContextManager {
46            ContextManager(ctx)
47        }
48
49        pub fn context<'a>(&'a self) -> impl Deref<Target=Context> + 'a {
50            &self.0
51        }
52
53        pub fn is_reloading(&self) -> bool {
54            false
55        }
56
57        // This method is just a quickfix to get rid of not-defined errors
58        pub fn compile_all_and_write(&self) {}
59    }
60}
61
62#[cfg(debug_assertions)]
63mod manager {
64    use std::sync::{RwLock, Mutex, mpsc};
65    use std::collections::HashMap;
66    use std::path::PathBuf;
67    use std::fs;
68
69    use std::io::Write;
70
71    use notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher};
72    use walkdir::WalkDir;
73
74    use super::Context;
75
76    /// Manages the `Context`
77    pub struct ContextManager{
78        context: RwLock<Context>,
79        watcher: Option<(RecommendedWatcher, Mutex<mpsc::Receiver<RawEvent>>)>
80    }
81
82    impl ContextManager {
83        pub fn new(ctx: Context) -> Self {
84            let (tx, rx) = mpsc::channel();
85            let watcher = raw_watcher(tx).and_then(|mut watcher| {
86                watcher.watch(ctx.sass_dir.canonicalize()?, RecursiveMode::Recursive)?;
87
88                Ok(watcher)
89            });
90
91            let watcher = match watcher {
92                Ok(watcher) => Some((watcher, Mutex::new(rx))),
93                Err(e) => {
94                    rocket::warn!("Failed to enable live sass compiling: {}", e);
95                    rocket::debug_!("Reload error: {:?}", e);
96                    rocket::warn_!("Live sass compiling is unawailable.");
97
98                    None
99                }
100            };
101
102            Self { context: RwLock::new(ctx), watcher }
103        }
104
105        /// Returns `Context` as read only
106        pub fn context(&self) -> impl std::ops::Deref<Target=Context> + '_ {
107            self.context.read().unwrap()
108        } 
109        
110        /// Returns `Context` as mutable
111        pub fn context_mut(&self) -> impl std::ops::DerefMut<Target=Context> + '_ {
112            self.context.write().unwrap()
113        } 
114
115        /// Compiles all files in `sass_dir`
116        pub fn compile_all(&self) -> Result<HashMap<String, String>, ()> {
117            let mut compiled: HashMap<String, String> = HashMap::new();
118            let sass_dir = &*self.context().sass_dir;
119            let rsass_format = *&self.context().rsass_format;
120
121            for entry in WalkDir::new(sass_dir).into_iter().filter_map(|e| e.ok()) {
122                if entry.metadata().unwrap().is_file() {
123                    let file_name = entry.path().file_name().unwrap().to_str().unwrap().to_string();
124                    let result = match crate::compile_file(entry.into_path(), rsass_format) {
125                        Ok(result) => result,
126                        Err(e) => {
127                            rocket::error!("Failed to compile file '{}'", file_name);
128                            rocket::error!("Sass error: {:?}", e);
129
130                            break;
131                        }
132                    };
133
134                    compiled.insert(file_name, result);
135                }
136            }
137
138            Ok(compiled)
139        }
140
141        /// Writes all compiled files to `css_dir`
142        pub fn write_compiled(&self, compiled_files: HashMap<String, String>) {
143            let css_dir = &*self.context().css_dir;
144
145            for (sass_file_name, compiled) in compiled_files {
146                let mut sass_file_name_path = PathBuf::new();
147
148                sass_file_name_path.push(sass_file_name);
149                sass_file_name_path.set_extension("css");
150
151                let css_file_path = css_dir.join(sass_file_name_path);
152
153                let mut file = fs::File::create(&css_file_path)
154                    .expect(format!("Failed to create css file: '{:?}'", css_file_path).as_str());
155
156                file.write_all(compiled.as_bytes())
157                    .expect(format!("Failed to write file: {:?}", css_file_path).as_str());
158            }
159        }
160
161        /// Shorthand for `compile_all` + `write_compiled`
162        pub fn compile_all_and_write(&self) {
163            if let Ok(compiled_files) = self.compile_all() {
164                self.write_compiled(compiled_files);
165            }
166
167        }
168
169        /// Returns `true` if reloading
170        pub fn is_reloading(&self) -> bool {
171            self.watcher.is_some()
172        }
173
174        /// Checks for any changes on `sass_dir`. 
175        /// If found, compiles again (reloads)
176        pub fn reload_if_needed(&self) {
177            let sass_changes = self.watcher.as_ref()
178                .map(|(_, rx)| rx.lock().expect("Failed to lock receiver").try_iter().count() > 0 );
179
180            if let Some(true) = sass_changes {
181                rocket::info_!("Change detected: compiling sass files.");
182                
183                self.compile_all_and_write();
184            }
185        }
186    }
187}