manganis_core/
css_module.rs1use std::{
2 collections::HashSet,
3 hash::{DefaultHasher, Hash, Hasher},
4 path::Path,
5};
6
7use crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};
8use const_serialize_07 as const_serialize;
9use const_serialize_08::SerializeConst;
10
11#[derive(
13 Debug,
14 Eq,
15 PartialEq,
16 PartialOrd,
17 Clone,
18 Copy,
19 Hash,
20 SerializeConst,
21 const_serialize::SerializeConst,
22 serde::Serialize,
23 serde::Deserialize,
24)]
25#[const_serialize(crate = const_serialize_08)]
26#[non_exhaustive]
27#[doc(hidden)]
28pub struct CssModuleAssetOptions {
29 minify: bool,
30 preload: bool,
31}
32
33impl Default for CssModuleAssetOptions {
34 fn default() -> Self {
35 Self::default()
36 }
37}
38
39impl CssModuleAssetOptions {
40 pub const fn new() -> AssetOptionsBuilder<CssModuleAssetOptions> {
42 AssetOptions::css_module()
43 }
44
45 pub const fn default() -> Self {
47 Self {
48 preload: false,
49 minify: true,
50 }
51 }
52
53 pub const fn minified(&self) -> bool {
55 self.minify
56 }
57
58 pub const fn preloaded(&self) -> bool {
60 self.preload
61 }
62}
63
64impl AssetOptions {
65 pub const fn css_module() -> AssetOptionsBuilder<CssModuleAssetOptions> {
72 AssetOptionsBuilder::variant(CssModuleAssetOptions::default())
73 }
74}
75
76impl AssetOptionsBuilder<CssModuleAssetOptions> {
77 pub const fn with_minify(mut self, minify: bool) -> Self {
81 self.variant.minify = minify;
82 self
83 }
84
85 pub const fn with_preload(mut self, preload: bool) -> Self {
89 self.variant.preload = preload;
90 self
91 }
92
93 pub const fn into_asset_options(self) -> AssetOptions {
95 AssetOptions {
96 add_hash: self.add_hash,
97 variant: AssetVariant::CssModule(self.variant),
98 }
99 }
100}
101
102pub fn create_module_hash(css_path: &Path) -> String {
104 let path_string = css_path.to_string_lossy();
105 let mut hasher = DefaultHasher::new();
106 path_string.hash(&mut hasher);
107 let hash = hasher.finish();
108 format!("{:016x}", hash)[..8].to_string()
109}
110
111#[deprecated(
120 since = "0.7.3",
121 note = "This function is no longer used by the css module system and will be removed in a future release."
122)]
123pub fn collect_css_idents(css: &str) -> (HashSet<String>, HashSet<String>) {
124 const ALLOWED: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
125
126 let mut classes = HashSet::new();
127 let mut ids = HashSet::new();
128
129 let mut start: Option<(String, bool)> = None;
131
132 let mut comment_start = false;
134 let mut comment_end = false;
136 let mut in_comment_scope = false;
138
139 let mut in_block_scope = false;
141
142 for (_byte_index, c) in css.char_indices() {
148 if let Some(ident) = start.as_mut() {
149 if ALLOWED.find(c).is_some() {
150 if ident.0.is_empty() && c.is_numeric() {
154 start = None;
155 continue;
156 }
157
158 ident.0.push(c);
159 } else {
160 match ident.1 {
161 true => ids.insert(ident.0.clone()),
162 false => classes.insert(ident.0.clone()),
163 };
164
165 start = None;
166 }
167 } else {
168 match c {
170 '*' if comment_start => {
172 comment_start = false;
173 in_comment_scope = true;
174 }
175 '*' if in_comment_scope => comment_end = true,
177 '/' if !in_comment_scope => {
179 comment_start = true;
180 }
181 '/' if comment_end => {
183 in_comment_scope = false;
184 comment_start = false;
185 comment_end = false;
186 }
187 '{' => in_block_scope = true,
189 '}' => in_block_scope = false,
190 _ => {
192 comment_start = false;
193 comment_end = false;
194 }
195 }
196
197 if in_comment_scope || in_block_scope {
199 continue;
200 }
201
202 match c {
203 '.' => start = Some((String::new(), false)),
204 '#' => start = Some((String::new(), true)),
205 _ => {}
206 }
207 }
208 }
209
210 (classes, ids)
211}