1use std::{
2 collections::hash_map::Entry,
3 io::{self, Write},
4 rc::Rc,
5};
6
7use regex::Regex;
8use rustc_hash::FxHashMap;
9use smol_str::SmolStr;
10use xiss_map::IdKind;
11
12use crate::{global_id::IdSet, id::Id};
13
14#[derive(Debug, thiserror::Error)]
15pub enum CssMapError {
16 #[error(transparent)]
17 IOError(#[from] io::Error),
18 #[error(transparent)]
19 ParserError(#[from] xiss_map::parser::Error),
20 #[error("Invalid exclude rule: {0}")]
21 InvalidExcludeRule(regex::Error),
22 #[error("Duplicate entry: {0},{1},{2}")]
23 DuplicateEntry(IdKind, SmolStr, SmolStr),
24}
25
26pub struct CssMapModule {
27 pub id: SmolStr,
28 pub index: u32,
29 pub classes: FxHashMap<SmolStr, Rc<Id>>,
30 pub vars: FxHashMap<SmolStr, Rc<Id>>,
31 pub keyframes: FxHashMap<SmolStr, Rc<Id>>,
32}
33
34impl CssMapModule {
35 pub fn new(id: SmolStr, index: u32) -> Self {
36 Self {
37 id,
38 index,
39 classes: FxHashMap::default(),
40 vars: FxHashMap::default(),
41 keyframes: FxHashMap::default(),
42 }
43 }
44}
45
46pub struct CssMap {
49 index: FxHashMap<SmolStr, u32>,
50 pub modules: Vec<Box<CssMapModule>>,
51 classes: IdSet,
52 vars: IdSet,
53 keyframes: IdSet,
54 new_ids_buf: String,
55}
56
57impl CssMap {
58 pub fn new(
59 exclude_class: &Vec<String>,
60 exclude_var: &Vec<String>,
61 exclude_keyframes: &Vec<String>,
62 ) -> Result<Self, CssMapError> {
63 Ok(Self {
64 index: FxHashMap::default(),
65 modules: Vec::new(),
66 classes: IdSet::new(0, build_exclude(exclude_class)?),
67 vars: IdSet::new(0, build_exclude(exclude_var)?),
68 keyframes: IdSet::new(0, build_exclude(exclude_keyframes)?),
69 new_ids_buf: String::new(),
70 })
71 }
72
73 pub fn import<R: io::BufRead>(&mut self, reader: &mut R) -> Result<(), CssMapError> {
75 let mut buf = String::new();
76 reader.read_to_string(&mut buf)?;
77 let mut parser = xiss_map::parser::Parser::new(&buf);
78 let mut module_index = 0;
79
80 while let Some((kind, module_id, local_id, global_id)) = parser.next_id()? {
81 if let Some(module_id) = module_id {
82 let i = match self.index.entry(module_id.into()) {
83 Entry::Occupied(entry) => *entry.get(),
84 Entry::Vacant(entry) => {
85 let index = self.modules.len() as u32;
86 self.modules
87 .push(Box::new(CssMapModule::new(entry.key().clone(), index)));
88 *entry.insert(index)
89 }
90 };
91 module_index = i;
92 }
93 if let Some(module) = self.modules.get_mut(module_index as usize) {
94 let id = Rc::new(Id::new(
95 kind,
96 module.index,
97 local_id.into(),
98 global_id.into(),
99 ));
100 match kind {
101 IdKind::Class => {
102 insert_id(&module.id, &mut module.classes, &mut self.classes, id)?
103 }
104 IdKind::Var => insert_id(&module.id, &mut module.vars, &mut self.vars, id)?,
105 IdKind::Keyframes => {
106 insert_id(&module.id, &mut module.keyframes, &mut self.keyframes, id)?
107 }
108 }
109 }
110 }
111
112 Ok(())
113 }
114
115 pub fn get_id(&mut self, module_index: u32, id_kind: IdKind, local_id: &str) -> Rc<Id> {
117 let module = &mut self.modules[module_index as usize];
118 let (id_char, id_set, map) = match id_kind {
119 IdKind::Class => ('C', &mut self.classes, &mut module.classes),
120 IdKind::Var => ('V', &mut self.vars, &mut module.vars),
121 IdKind::Keyframes => ('K', &mut self.keyframes, &mut module.keyframes),
122 };
123 if let Some(id) = map.get(local_id) {
124 id.clone()
125 } else {
126 let global_id = id_set.next_id();
127
128 let buf = &mut self.new_ids_buf;
129 buf.reserve(5 + module.id.len() + local_id.len() + global_id.len());
130 buf.push(id_char);
131 buf.push(',');
132 buf.push_str(&module.id);
133 buf.push(',');
134 buf.push_str(local_id);
135 buf.push(',');
136 buf.push_str(&global_id);
137 buf.push('\n');
138
139 let id = Rc::new(Id::new(
140 IdKind::Class,
141 module_index,
142 local_id.into(),
143 global_id,
144 ));
145 map.insert(local_id.into(), id.clone());
146
147 id
148 }
149 }
150
151 pub fn get_module_index(&mut self, module_name: &str) -> u32 {
154 get_module_index(&mut self.index, &mut self.modules, module_name)
155 }
156
157 pub fn flush_new_ids<W: Write>(&mut self, output: &mut W) -> Result<(), io::Error> {
159 output.write(self.new_ids_buf.as_bytes())?;
160 output.flush()?;
161 self.new_ids_buf.clear();
162 Ok(())
163 }
164}
165
166fn build_exclude(exclude: &Vec<String>) -> Result<Vec<Regex>, CssMapError> {
171 let mut result = Vec::with_capacity(exclude.len());
172 for s in exclude {
173 let r = match Regex::new(s) {
174 Ok(r) => r,
175 Err(err) => return Err(CssMapError::InvalidExcludeRule(err)),
176 };
177 result.push(r);
178 }
179 Ok(result)
180}
181
182fn get_module_index<'a>(
185 modules_index: &mut FxHashMap<SmolStr, u32>,
186 modules: &'a mut Vec<Box<CssMapModule>>,
187 module_name: &str,
188) -> u32 {
189 if let Some(module_id) = modules_index.get(module_name) {
190 *module_id
191 } else {
192 let module_id = modules.len() as u32;
193 modules_index.insert(module_name.into(), module_id);
194 modules.push(Box::new(CssMapModule::new(module_name.into(), module_id)));
195 module_id
196 }
197}
198
199fn insert_id(
200 module_id: &str,
201 map: &mut FxHashMap<SmolStr, Rc<Id>>,
202 id_set: &mut IdSet,
203 id: Rc<Id>,
204) -> Result<(), CssMapError> {
205 if let Entry::Vacant(v) = map.entry(id.local_id.clone()) {
206 id_set.add(&id.global_id);
207 v.insert(id);
208 Ok(())
209 } else {
210 Err(CssMapError::DuplicateEntry(
211 id.kind,
212 module_id.into(),
213 id.local_id.clone(),
214 ))
215 }
216}