latexml_core/binding/def/
builder.rs1use super::dialect::{def_constructor, def_environment};
19use crate::{
20 common::{
21 def_parser::{parse_parameters, parse_prototype},
22 error::{Error, Result},
23 },
24 definition::{
25 BeforeDigestClosure, ConstructionClosure, DigestionClosure, FontDirective, PropertiesClosure,
26 ReplacementClosure, Reversion, constructor::ConstructorOptions,
27 },
28 parameter::Parameters,
29 token::Token,
30 util::text::{Delimiter, extract_bracketed},
31};
32
33macro_rules! shared_hook_setters {
37 () => {
38 pub fn after_digest(mut self, hook: DigestionClosure) -> Self {
41 self.options.after_digest.push(hook);
42 self
43 }
44
45 pub fn after_digest_begin(mut self, hook: DigestionClosure) -> Self {
48 self.options.after_digest_begin.push(hook);
49 self
50 }
51
52 pub fn after_digest_body(mut self, hook: DigestionClosure) -> Self {
55 self.options.after_digest_body.push(hook);
56 self
57 }
58
59 pub fn before_digest(mut self, hook: BeforeDigestClosure) -> Self {
62 self.options.before_digest.push(hook);
63 self
64 }
65
66 pub fn before_digest_end(mut self, hook: BeforeDigestClosure) -> Self {
68 self.options.before_digest_end.push(hook);
69 self
70 }
71
72 pub fn before_construct(mut self, hook: ConstructionClosure) -> Self {
74 self.options.before_construct.push(hook);
75 self
76 }
77
78 pub fn after_construct(mut self, hook: ConstructionClosure) -> Self {
80 self.options.after_construct.push(hook);
81 self
82 }
83
84 pub fn properties(mut self, props: PropertiesClosure) -> Self {
87 self.options.properties = props;
88 self
89 }
90
91 pub fn reversion(mut self, rev: Reversion) -> Self {
93 self.options.reversion = Some(rev);
94 self
95 }
96
97 pub fn font(mut self, font: FontDirective) -> Self {
99 self.options.font = Some(font);
100 self
101 }
102
103 pub fn sizer(mut self, sizer: crate::definition::SizingClosure) -> Self {
105 self.options.sizer = Some(sizer);
106 self
107 }
108 };
109}
110
111pub enum OptionValue {
115 Str(String),
116 Bool(bool),
117 Int(i64),
118}
119
120impl OptionValue {
121 fn into_string(self) -> Result<String> {
122 match self {
123 OptionValue::Str(s) => Ok(s),
124 _ => Err(Error::from("constructor option expected a string value")),
125 }
126 }
127
128 pub fn into_bool(self) -> Result<bool> {
133 match self {
134 OptionValue::Bool(b) => Ok(b),
135 OptionValue::Int(i) => Ok(i != 0),
136 OptionValue::Str(s) => Ok(!s.is_empty()),
137 }
138 }
139}
140
141pub struct ConstructorBuilder {
143 cs: Token,
144 paramlist: Option<Parameters>,
145 replacement: Option<ReplacementClosure>,
146 options: ConstructorOptions,
147}
148
149impl ConstructorBuilder {
150 pub fn new(proto: &str) -> Result<Self> {
152 let (cs, paramlist) = parse_prototype(proto, true)?;
153 Ok(Self {
154 cs,
155 paramlist,
156 replacement: None,
157 options: ConstructorOptions::default(),
158 })
159 }
160
161 pub fn replacement(mut self, repl: ReplacementClosure) -> Self {
163 self.replacement = Some(repl);
164 self
165 }
166
167 pub fn set_option(mut self, key: &str, value: OptionValue) -> Result<Self> {
170 apply_scalar_option(&mut self.options, key, value)?;
171 Ok(self)
172 }
173
174 shared_hook_setters!();
175
176 pub fn install(self) -> Result<()> {
178 def_constructor(self.cs, self.paramlist, self.replacement, self.options);
179 Ok(())
180 }
181}
182
183fn apply_scalar_option(
189 options: &mut ConstructorOptions,
190 key: &str,
191 value: OptionValue,
192) -> Result<()> {
193 match key {
194 "mode" => options.mode = Some(value.into_string()?),
195 "bounded" => options.bounded = value.into_bool()?,
196 "requireMath" => options.require_math = value.into_bool()?,
197 "forbidMath" => options.forbid_math = value.into_bool()?,
198 "enterHorizontal" => options.enter_horizontal = value.into_bool()?,
199 "leaveHorizontal" => options.leave_horizontal = value.into_bool()?,
200 "captureBody" => options.capture_body = value.into_bool()?,
201 "alias" => options.alias = Some(value.into_string()?),
202 _ => log::debug!("binding builder: ignoring unknown scalar option '{key}'"),
203 }
204 Ok(())
205}
206
207pub struct EnvironmentBuilder {
212 name: String,
213 paramlist: Option<Parameters>,
214 replacement: Option<ReplacementClosure>,
215 options: ConstructorOptions,
216}
217
218impl EnvironmentBuilder {
219 pub fn new(proto: &str) -> Result<Self> {
223 let mut proto = proto.trim_start().to_string();
224 let name = extract_bracketed(&mut proto, Some(&Delimiter::Brace)).ok_or_else(|| {
225 Error::from(format!(
226 "DefEnvironment prototype must start with {{name}}: {proto:?}"
227 ))
228 })?;
229 let paramlist_str = proto.trim_start().to_string();
230 let paramlist = if paramlist_str.is_empty() {
231 None
232 } else {
233 let cs = crate::T_CS!(crate::s!("\\{}", &name));
234 parse_parameters(¶mlist_str, &cs, true)?
235 };
236 Ok(Self {
237 name,
238 paramlist,
239 replacement: None,
240 options: ConstructorOptions::default(),
241 })
242 }
243
244 pub fn replacement(mut self, repl: ReplacementClosure) -> Self {
246 self.replacement = Some(repl);
247 self
248 }
249
250 pub fn set_option(mut self, key: &str, value: OptionValue) -> Result<Self> {
252 apply_scalar_option(&mut self.options, key, value)?;
253 Ok(self)
254 }
255
256 shared_hook_setters!();
257
258 pub fn install(self) -> Result<()> {
260 def_environment(self.name, self.paramlist, self.replacement, self.options);
261 Ok(())
262 }
263}