Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_steel/src/srv/dev/sass.rs

4.6 KiB, 13 runs

created by r1870400018:985, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! A Sass/SCSS compiler using the Grass engine.
2//!
3//! This module provides functionality to compile Sass/SCSS files into a single CSS output file.
4//! It handles imports, resolves dependencies, and supports path aliases for `@use` and `@import` rules.
5//!
6//! # Usage Example
7//! ```rust,no_run
8//! use oxedyne_fe2o3_core::prelude::*;
9//! use oxedyne_fe2o3_steel::app::dev::sass::SassBundle;
10//! use std::path::Path;
11//!
12//! let mut bundler = SassBundle::new();
13//!
14//! // Optional: Configure load paths for Sass imports.
15//! bundler.add_load_path("node_modules");
16//!
17//! // Compile all Sass/SCSS files.
18//! res!(bundler.compile_directory(
19//! // Source directory containing entry point files.
20//! Path::new("src/styles"),
21//! // Output CSS file.
22//! Path::new("dist/styles.css"),
23//! ));
24//! ```
25
26use oxedyne_fe2o3_core::prelude::*;
27
28use std::{
29 collections::{
30 HashSet,
31 },
32 path::{
33 Path,
34 PathBuf,
35 },
36 fs,
37};
38
39use grass::{
40 self,
41 Options,
42 OutputStyle,
43};
44
45
46#[derive(Debug)]
47#[allow(dead_code)]
48struct StyleInfo {
49 path: PathBuf,
50 content: String,
51}
52
53pub struct SassBundle {
54 load_paths: Vec<PathBuf>,
55 processed: HashSet<PathBuf>,
56 styles: Vec<StyleInfo>,
57}
58
59impl SassBundle {
60 pub fn new() -> Self {
61 Self {
62 load_paths: Vec::new(),
63 processed: HashSet::new(),
64 styles: Vec::new(),
65 }
66 }
67
68 /// Add a path to search for Sass/SCSS imports.
69 /// This is used to resolve `@use` and `@import` rules.
70 pub fn add_load_path<P: AsRef<Path>>(
71 &mut self,
72 path: P,
73 ) {
74 self.load_paths.push(path.as_ref().to_path_buf());
75 }
76
77 pub fn compile_directory(
78 mut self,
79 css_paths: &(PathBuf, PathBuf),
80 )
81 -> Outcome<()>
82 {
83 // Find entry points in input directory
84 let mut entry_files = Vec::new();
85 res!(self.collect_sass_files(&css_paths.0, &mut entry_files));
86
87 // Process each entry point
88 for entry in entry_files {
89 res!(self.process_style(
90 &entry,
91 ));
92 }
93
94 // Bundle in order of processing
95 let mut bundled = String::new();
96 for style in &self.styles {
97 bundled.push_str(&style.content);
98 bundled.push('\n');
99 }
100
101 // Write bundled output
102 res!(fs::write(css_paths.1.clone(), bundled));
103
104 Ok(())
105 }
106
107 fn process_style(
108 &mut self,
109 file_path: &Path,
110 )
111 -> Outcome<()>
112 {
113 if self.processed.contains(file_path) {
114 return Ok(());
115 }
116 debug!("Processing {:?}", file_path);
117 let content = res!(fs::read_to_string(file_path));
118 let mut options = Options::default().style(OutputStyle::Compressed);
119
120 // Get directory containing the current file for import resolution.
121 let import_dir = match file_path.parent() {
122 Some(dir) => dir,
123 None => return Err(err!(
124 "Could not get parent directory of {:?}.",
125 file_path;
126 Path)),
127 };
128 options = options.load_path(import_dir);
129
130 // Add all configured load paths to options.
131 for load_path in &self.load_paths {
132 if let Some(path_str) = load_path.to_str() {
133 options = options.load_path(path_str);
134 }
135 }
136
137 let css = match grass::from_string(
138 content,
139 &options,
140 ) {
141 Ok(css) => css,
142 Err(e) => return Err(err!(e,
143 "Why trying to compile Sass file {:?}", file_path;
144 IO, Format)),
145 };
146
147 self.styles.push(StyleInfo {
148 path: file_path.to_path_buf(),
149 content: css,
150 });
151 self.processed.insert(file_path.to_path_buf());
152 debug!(" Successful processing {:?}", file_path);
153 Ok(())
154 }
155
156 fn collect_sass_files(
157 &self,
158 dir: &Path,
159 files: &mut Vec<PathBuf>,
160 )
161 -> Outcome<()>
162 {
163 if dir.is_dir() {
164 for entry in res!(fs::read_dir(dir)) {
165 let entry = res!(entry);
166 let path = entry.path();
167 if path.is_dir() {
168 res!(self.collect_sass_files(&path, files));
169 } else if let Some(ext) = path.extension() {
170 if ext == "sass" || ext == "scss" {
171 files.push(path);
172 }
173 }
174 }
175 }
176 Ok(())
177 }
178}