grimoire_css_lib/core/
spell.rs1use std::collections::{HashMap, HashSet};
29
30use super::GrimoireCssError;
31
32#[derive(Eq, Hash, PartialEq, Debug, Clone)]
33pub struct Spell {
34 pub raw_spell: String,
35 pub component: String,
36 pub component_target: String,
37 pub effects: String,
38 pub area: String,
39 pub focus: String,
40 pub with_template: bool,
41 pub scroll_spells: Option<Vec<Spell>>,
42}
43
44impl Spell {
45 pub fn new(
47 raw_spell: &str,
48 shared_spells: &HashSet<String>,
49 scrolls: &Option<HashMap<String, Vec<String>>>,
50 ) -> Result<Option<Self>, GrimoireCssError> {
51 let with_template = Self::check_for_template(raw_spell);
52
53 let raw_spell = if with_template {
54 raw_spell
55 .strip_prefix("g!")
56 .and_then(|s| s.strip_suffix(";"))
57 .unwrap_or(raw_spell)
58 } else {
59 raw_spell
60 };
61
62 let (area, rest) = raw_spell.split_once("__").unwrap_or(("", raw_spell));
64
65 let (focus, rest) = rest
67 .split_once('}')
68 .map_or(("", rest), |(f, r)| (f.strip_prefix('{').unwrap_or(f), r));
69
70 let (effects, rest) = rest.split_once(':').unwrap_or(("", rest));
72
73 if let Some((component, component_target)) = rest.split_once("=") {
75 let mut spell = Spell {
76 raw_spell: raw_spell.to_string(),
77 component: component.to_string(),
78 component_target: component_target.to_string(),
79 effects: effects.to_string(),
80 area: area.to_string(),
81 focus: focus.to_string(),
82 with_template,
83 scroll_spells: None,
84 };
85
86 if let Some(raw_scroll_spells) =
87 Self::check_raw_scroll_spells(&spell.component, scrolls)
88 {
89 spell.scroll_spells = Self::parse_scroll(
90 component,
91 raw_scroll_spells,
92 &spell.component_target,
93 shared_spells,
94 scrolls,
95 )?;
96 }
97
98 return Ok(Some(spell));
99 } else if let Some(raw_scroll_spells) = Self::check_raw_scroll_spells(rest, scrolls) {
100 return Ok(Some(Spell {
101 raw_spell: raw_spell.to_string(),
102 component: rest.to_string(),
103 component_target: String::new(),
104 effects: effects.to_string(),
105 area: area.to_string(),
106 focus: focus.to_string(),
107 with_template,
108 scroll_spells: Self::parse_scroll(
109 rest,
110 raw_scroll_spells,
111 "",
112 shared_spells,
113 scrolls,
114 )?,
115 }));
116 }
117
118 Ok(None) }
120
121 fn check_for_template(class_name: &str) -> bool {
122 class_name.starts_with("g!") && class_name.ends_with(";")
123 }
124
125 fn check_raw_scroll_spells<'a>(
126 spell_component: &'a str,
127 scrolls: &'a Option<HashMap<String, Vec<String>>>,
128 ) -> Option<&'a Vec<String>> {
129 if let Some(scrolls) = scrolls {
130 return scrolls.get(spell_component);
131 };
132
133 None
134 }
135
136 fn parse_scroll(
137 scroll_name: &str,
138 raw_scroll_spells: &[String],
139 component_target: &str,
140 shared_spells: &HashSet<String>,
141 scrolls: &Option<HashMap<String, Vec<String>>>,
142 ) -> Result<Option<Vec<Spell>>, GrimoireCssError> {
143 if raw_scroll_spells.is_empty() {
144 return Ok(None);
145 }
146
147 let scroll_variables: Vec<&str> = component_target.split('_').collect();
148 let count_of_variables = if component_target.is_empty() {
149 0
150 } else {
151 scroll_variables.len()
152 };
153 let mut count_of_used_variables = 0;
154
155 let mut spells = Vec::with_capacity(raw_scroll_spells.len());
156
157 for raw_spell in raw_scroll_spells.iter() {
158 if raw_spell.contains("=$") {
159 if count_of_used_variables > scroll_variables.len() - 1 {
160 break;
161 }
162
163 let variabled_raw_spell = raw_spell.replace(
164 "=$",
165 format!("={}", scroll_variables[count_of_used_variables]).as_str(),
166 );
167
168 if let Ok(Some(spell)) = Spell::new(&variabled_raw_spell, shared_spells, scrolls) {
169 spells.push(spell);
170 }
171
172 count_of_used_variables += 1;
173 } else if let Ok(Some(spell)) = Spell::new(raw_spell, shared_spells, scrolls) {
174 spells.push(spell);
175 }
176 }
177
178 if count_of_used_variables != count_of_variables {
179 return Err(GrimoireCssError::InvalidInput(format!(
180 "Not all variables used in scroll '{}'. Expected {}, but used {}",
181 scroll_name, count_of_variables, count_of_used_variables,
182 )));
183 }
184
185 if spells.is_empty() {
186 Ok(None)
187 } else {
188 Ok(Some(spells))
189 }
190 }
191
192 pub fn generate_spells_from_classes(
193 css_classes: Vec<String>,
194 shared_spells: &HashSet<String>,
195 scrolls: &Option<HashMap<String, Vec<String>>>,
196 ) -> Result<Vec<Spell>, GrimoireCssError> {
197 let mut spells = Vec::with_capacity(css_classes.len());
198
199 for cs in css_classes {
200 if !shared_spells.contains(&cs) {
201 if let Some(spell) = Spell::new(&cs, shared_spells, scrolls)? {
202 spells.push(spell);
203 }
204 }
205 }
206
207 Ok(spells)
208 }
209}