1use std::{borrow::Cow, rc::Rc};
8
9use crate::{
10 binding::def::dialect::{def_conditional, def_macro},
11 common::{def_parser::parse_parameters, error::*, store::Stored},
12 definition::{
13 ExpansionBody, ExpansionClosure, argument::ArgWrap, conditional::ConditionalOptions,
14 },
15 mouth::tokenize,
16 parameter::Parameter,
17 state,
18 token::{Catcode, Token},
19 tokens::{TeXString, Tokens},
20};
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct KeyVal {
24 prefix: String,
26 key: String,
27 keyset: String,
28}
29
30impl Default for KeyVal {
31 fn default() -> Self {
32 KeyVal {
33 prefix: "KV".to_string(),
34 keyset: String::new(),
35 key: String::new(),
36 }
37 }
38}
39
40impl KeyVal {
41 pub fn new(prefix: Option<String>, keyset: String, key: String) -> Self {
42 let prefix = prefix.unwrap_or_else(|| "KV".to_string());
43 KeyVal { prefix, key, keyset }
44 }
45
46 pub fn get_header(&self) -> String { s!("{}@{}@{}", self.prefix, self.keyset, self.key) }
47
48 pub fn get_prop(&self, key: &str) -> Option<Stored> {
53 state::lookup_value(&s!("KEYVAL@{}@{}", key, self.get_header()))
54 }
55 pub fn get_default(&self) -> Option<Stored> { self.get_prop("default") }
56 pub fn get_type(&self) -> Option<Rc<Parameter>> {
57 state::with_value(&s!("KEYVAL@type@{}", self.get_header()), |v| match v {
62 Some(Stored::Parameter(p)) => Some(Rc::clone(p)),
63 _ => None,
64 })
65 }
66}
67
68pub(crate) fn keyval_qname(prefix: &str, keyset: &str, key: &str) -> String {
70 let prefix = if prefix.is_empty() { "KV" } else { prefix };
71 s!("{prefix}@{keyset}@{key}")
72}
73
74pub(crate) fn keyval_get(qname: &str, prop: &str) -> Option<Stored> {
75 state::lookup_value(&s!("KEYVAL@{prop}@{qname}"))
76}
77
78pub(crate) fn keyval_set(qname: &str, prop: &str, value: Stored) {
83 state::assign_value(&s!("KEYVAL@{prop}@{qname}"), value, None);
84}
85
86pub fn has_keyval(prefix: &str, keyset: &str, key: &str) -> bool {
88 let qname = keyval_qname(prefix, keyset, key);
89 state::with_value(&s!("KEYVAL@defined@{}", qname), |v| v.is_some())
90 || state::has_meaning(&T_CS!(s!("\\{qname}")))
91}
92
93pub fn disable_keyval(prefix: &str, keyset: &str, key: &str) -> Result<()> {
95 let qname = keyval_qname(prefix, keyset, key);
96 keyval_set(&qname, "disabled", true.into());
97 define_ordinary(
99 &qname,
100 Some(ExpansionBody::Tokens(tokenize(TeXString::assembled(s!(
101 "\\PackageWarning{{keyval}}{{`{key}' has been disabled. }}"
102 ))))),
103 )
104}
105
106#[derive(Debug, Default, Clone)]
110pub struct KeyvalConfig<'a> {
112 pub prefix: &'a str,
113 pub keyset: &'a str,
114 pub key: &'a str,
115 pub vtype: &'a str,
116 pub default: Option<&'a str>,
117 pub kind: Option<&'a str>,
118 pub code: Option<ExpansionBody>,
119 pub macroprefix: Option<&'a str>,
120 pub mismatch: Option<ExpansionBody>,
121 pub normalize: Option<bool>,
122 pub bin: Option<Tokens>,
123 pub choices: Vec<&'static str>,
124}
125
126fn register_keyval(qname: &str) {
128 use crate::common::arena;
129 let registry_key = "KEYVAL@registry";
130 let mut registry: Vec<arena::SymStr> = state::with_value(registry_key, |v| match v {
133 Some(Stored::Strings(v)) => v.to_vec(),
134 _ => Vec::new(),
135 });
136 let sym = arena::pin(qname);
137 if !registry.contains(&sym) {
139 registry.push(sym);
140 }
141 state::assign_value(registry_key, Stored::Strings(registry.into()), None);
142}
143
144#[derive(Debug, Clone)]
146pub struct KeyvalMeta {
147 pub key: String,
148 pub prefix: String,
149 pub keyset: String,
150 pub kind: String,
151 pub default: String,
152}
153
154pub fn enumerate_keyvals() -> Vec<KeyvalMeta> {
156 use crate::common::arena;
157 let registry_key = "KEYVAL@registry";
158 let registry = state::with_value(registry_key, |v| match v {
159 Some(Stored::Strings(v)) => v.to_vec(),
160 _ => Vec::new(),
161 });
162 if registry.is_empty() {
163 return Vec::new();
164 }
165 let mut result = Vec::new();
166 for sym in registry {
167 let entry = arena::with(sym, |qname| {
171 let key = keyval_get(qname, "key_name")
172 .map(|s| s.to_string())
173 .unwrap_or_default();
174 let prefix = keyval_get(qname, "keyval_prefix")
175 .map(|s| s.to_string())
176 .unwrap_or_else(|| "KV".to_string());
177 let keyset = keyval_get(qname, "keyset")
178 .map(|s| s.to_string())
179 .unwrap_or_default();
180 let kind = keyval_get(qname, "kind")
181 .map(|s| s.to_string())
182 .unwrap_or_else(|| "ordinary".to_string());
183 let default = keyval_get(qname, "default")
184 .map(|s| s.to_string())
185 .unwrap_or_else(|| "[none]".to_string());
186 KeyvalMeta {
187 key,
188 prefix,
189 keyset,
190 kind,
191 default,
192 }
193 });
194 result.push(entry);
195 }
196 result
197}
198
199pub fn define(options: KeyvalConfig) -> Result<()> {
237 let KeyvalConfig {
238 prefix,
239 keyset,
240 key,
241 vtype,
242 default,
243 kind,
244 code,
245 macroprefix,
246 mismatch,
247 normalize,
248 bin,
249 choices,
250 } = options;
251
252 let qname = keyval_qname(prefix, keyset, key);
253
254 keyval_set(&qname, "exists", true.into());
256 keyval_set(&qname, "disabled", false.into());
257 if state::lookup_bool("XKVVIEW_TRACKING") {
260 let identifier = |s: &str| TeXString::assembled(s.to_string());
263 keyval_set(
264 &qname,
265 "kind",
266 Stored::Tokens(tokenize(identifier(kind.unwrap_or("ordinary")))),
267 );
268 keyval_set(
269 &qname,
270 "keyval_prefix",
271 Stored::Tokens(tokenize(identifier(prefix))),
272 );
273 keyval_set(
274 &qname,
275 "keyset",
276 Stored::Tokens(tokenize(identifier(keyset))),
277 );
278 keyval_set(
279 &qname,
280 "key_name",
281 Stored::Tokens(tokenize(identifier(key))),
282 );
283 register_keyval(&qname);
284 }
285 let vtype = if vtype.is_empty() { "{}" } else { vtype };
287 let paramlist_opt = parse_parameters(
288 vtype,
289 &T_OTHER!(s!("KeyVal {key} in set {keyset} with prefix {prefix}")),
290 true,
291 )?;
292 match paramlist_opt {
293 None => {
294 Warn!(
295 "unexpected",
296 "keyval",
297 s!(
298 "No parameters in keyval {key} (in set {keyset} with prefix {prefix}) taking only first"
299 )
300 );
301 },
302 Some(paramlist) => {
303 if paramlist.get_num_args() != 1 {
304 Warn!(
305 "unexpected",
306 "keyval",
307 s!(
308 "Too many parameters in keyval {key} (in set {keyset} with prefix {prefix})\
309 taking only first"
310 )
311 );
312 }
313 keyval_set(&qname, "type", paramlist.take_parameters().remove(0).into());
314 },
315 };
316 if let Some(default_str) = default {
319 let default_tks = tokenize(TeXString::assembled(default_str.to_string()));
320 keyval_set(&qname, "default", Stored::Tokens(default_tks.clone()));
321 def_macro(
322 T_CS!(s!("\\{qname}@default")),
323 None,
324 ExpansionBody::Tokens(Tokens!(
325 T_CS!(s!("\\{qname}")),
326 T_BEGIN!(),
327 default_tks,
328 T_END!()
329 )),
330 None,
331 )?;
332 }
333
334 let kind = kind.unwrap_or("ordinary");
336 match kind {
337 "ordinary" => define_ordinary(&qname, code)?,
338 "command" => {
339 let macroname = match macroprefix {
344 Some(mpfx) if !mpfx.is_empty() => s!("{mpfx}{key}"),
345 _ => s!("cmd{qname}"),
346 };
347 define_command(&qname, code, ¯oname)?;
348 },
349 "choice" => define_choice(
350 &qname,
351 code,
352 mismatch,
353 choices,
354 normalize.unwrap_or(false),
355 bin,
356 )?,
357 "boolean" => define_boolean(
358 &qname,
359 code,
360 mismatch,
361 &if let Some(mpfx) = macroprefix {
362 Cow::Owned(s!("{mpfx}{key}"))
363 } else {
364 Cow::Borrowed(&qname)
365 },
366 )?,
367 _ => Warn!(
368 "unknown",
369 "undef",
370 s!(
371 "Unknown KeyVals kind {kind} should be one of 'ordinary', 'command', 'choice', 'boolean'. "
372 )
373 ),
374 };
375 Ok(())
376}
377
378fn define_ordinary(qname: &str, code_expansion: Option<ExpansionBody>) -> Result<()> {
380 let qname_cs = T_CS!(s!("\\{qname}"));
381 let plain_params = parse_parameters("{}", &qname_cs, true)?;
382 def_macro(qname_cs, plain_params, code_expansion, None)
383}
384
385fn define_command(qname: &str, code: Option<ExpansionBody>, macroname: &str) -> Result<()> {
387 let qname_cs = T_CS!(s!("\\{qname}"));
388 let plain_params = parse_parameters("{}", &qname_cs, true)?;
389 let plainp = plain_params.clone();
390 let orig = s!("\\ltxml@orig@{qname}");
391 let macroname_cs = s!("\\{macroname}");
392 let closure: ExpansionClosure = Rc::new(move |value: Vec<ArgWrap>| {
393 def_macro(T_CS!(&orig), plainp.clone(), code.clone(), None)?;
394 let value_tks: Vec<Token> = value
395 .into_iter()
396 .flat_map(|v| {
397 v.owned_tokens()
398 .map(|inner| inner.unlist())
399 .unwrap_or_default()
400 })
401 .collect();
402 Ok(Tokens!(
404 T_CS!("\\def"),
405 T_CS!(¯oname_cs),
406 T_BEGIN!(),
407 value_tks.clone(),
408 T_END!(),
409 T_CS!(&orig),
410 T_BEGIN!(),
411 T_PARAM!(),
412 value_tks,
413 T_END!()
414 ))
415 });
416 def_macro(
417 qname_cs,
418 plain_params,
419 ExpansionBody::Closure(closure),
420 None,
421 )
422}
423
424fn define_choice(
426 qname: &str,
427 code_opt: Option<ExpansionBody>,
428 mismatch_opt: Option<ExpansionBody>,
429 choices: Vec<&'static str>,
430 normalize: bool,
431 bin: Option<Tokens>,
432) -> Result<()> {
433 let (varmacro_opt, idxmacro_opt) = if let Some(bin_tks) = bin {
434 let mut bin_iter = bin_tks.unlist().into_iter();
435 (bin_iter.next(), bin_iter.next())
436 } else {
437 (None, None)
438 };
439 let qname_cs = T_CS!(s!("\\{qname}"));
440 let orig = T_CS!(s!("\\ltxml@orig@{qname}"));
441 let plain_params = parse_parameters("{}", &qname_cs, true)?;
442 let plain_params_main = plain_params.clone();
443 let closure: ExpansionClosure = Rc::new(move |mut values| {
444 let value = values.remove(0).owned_tokens().unwrap_or_default();
446 let mut nvalue = value.to_string();
447 if normalize {
448 nvalue = nvalue.to_lowercase();
449 }
450 if let Some(varmacro) = varmacro_opt {
451 def_macro(
452 varmacro,
453 None,
454 ExpansionBody::Tokens(Tokens::new(Explode!(nvalue))),
455 None,
456 )?;
457 }
458 let mut valid = false;
460 for (index, choice_str) in choices.iter().enumerate() {
461 if (normalize && (choice_str.to_lowercase() == nvalue)) || *choice_str == nvalue {
462 valid = true;
463 if let Some(idxmacro) = idxmacro_opt {
464 def_macro(
465 idxmacro,
466 None,
467 ExpansionBody::Tokens(Tokens::new(Explode!(index))),
468 None,
469 )?;
470 }
471 }
472 }
473 let mut tokens = Vec::new();
475 if valid {
477 if let Some(ref code) = code_opt {
478 def_macro(orig, plain_params.clone(), code.clone(), None)?;
479 tokens.push(orig);
480 tokens.push(T_BEGIN!());
481 tokens.extend(value.unlist());
482 tokens.push(T_END!());
483 }
484 } else if let Some(ref mismatch) = mismatch_opt {
485 def_macro(orig, plain_params.clone(), mismatch.clone(), None)?;
487 tokens.push(orig);
488 tokens.push(T_BEGIN!());
489 tokens.extend(value.unlist());
490 tokens.push(T_END!());
491 }
492 Ok(Tokens::new(tokens))
493 });
494 def_macro(
495 qname_cs,
496 plain_params_main,
497 ExpansionBody::Closure(closure),
498 None,
499 )
500}
501
502fn define_boolean(
504 qname: &str,
505 code_opt: Option<ExpansionBody>,
506 mismatch: Option<ExpansionBody>,
507 macroname: &str,
508) -> Result<()> {
509 def_conditional(
510 T_CS!(s!("\\if{macroname}")),
511 None,
512 None,
513 ConditionalOptions::default(),
514 )?; let orig = s!("\\ltxml@@rig@{qname}");
516 let orig_cs = T_CS!(orig);
517 let plain_params = parse_parameters("{}", &orig_cs, true)?;
518 let macroname_true = T_CS!(s!("\\{macroname}true"));
519 let macroname_false = T_CS!(s!("\\{macroname}false"));
520 let closure: ExpansionClosure = Rc::new(move |mut values: Vec<ArgWrap>| {
521 let value = values.remove(0).owned_tokens().unwrap_or_default();
523 let value_str = value.to_string().to_lowercase();
524 let mut tokens = vec![];
525 if value_str == "true" {
527 tokens.push(macroname_true);
528 } else {
529 tokens.push(macroname_false);
530 }
531 if let Some(ref code) = code_opt {
533 def_macro(orig_cs, plain_params.clone(), code.clone(), None)?;
534 tokens.push(orig_cs);
535 tokens.push(T_BEGIN!());
536 tokens.extend(value.unlist());
537 tokens.push(T_END!());
538 }
539 Ok(Tokens::new(tokens))
540 });
541
542 define_choice(
543 qname,
544 Some(ExpansionBody::Closure(closure)),
545 mismatch,
546 vec!["true", "false"],
547 true,
548 None,
549 )
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555
556 #[test]
557 fn keyval_default_fields() {
558 let kv = KeyVal::default();
559 assert_eq!(kv.prefix, "KV");
560 assert!(kv.keyset.is_empty());
561 assert!(kv.key.is_empty());
562 }
563
564 #[test]
565 fn keyval_new_custom_prefix() {
566 let kv = KeyVal::new(
567 Some("custom".to_string()),
568 "ks".to_string(),
569 "k".to_string(),
570 );
571 assert_eq!(kv.prefix, "custom");
572 assert_eq!(kv.keyset, "ks");
573 assert_eq!(kv.key, "k");
574 }
575
576 #[test]
577 fn keyval_new_default_prefix_on_none() {
578 let kv = KeyVal::new(None, "ks".to_string(), "k".to_string());
580 assert_eq!(kv.prefix, "KV");
581 }
582
583 #[test]
584 fn keyval_get_header_format() {
585 let kv = KeyVal::new(
586 Some("P".to_string()),
587 "set".to_string(),
588 "width".to_string(),
589 );
590 assert_eq!(kv.get_header(), "P@set@width");
591 }
592
593 #[test]
594 fn keyval_get_header_default_prefix() {
595 let kv = KeyVal::new(None, "tabular".to_string(), "vattach".to_string());
596 assert_eq!(kv.get_header(), "KV@tabular@vattach");
597 }
598
599 #[test]
600 fn keyval_equality_by_all_fields() {
601 let a = KeyVal::new(Some("P".to_string()), "ks".to_string(), "k".to_string());
602 let b = KeyVal::new(Some("P".to_string()), "ks".to_string(), "k".to_string());
603 let c = KeyVal::new(Some("P".to_string()), "ks".to_string(), "other".to_string());
604 assert_eq!(a, b);
605 assert_ne!(a, c);
606 }
607
608 #[test]
609 fn keyval_qname_normalizes_empty_prefix() {
610 assert_eq!(keyval_qname("", "set", "k"), "KV@set@k");
612 assert_eq!(keyval_qname("P", "set", "k"), "P@set@k");
613 }
614}
615
616pub fn split_keyval_source(kv: &str) -> Vec<(String, String)> {
626 use winnow::{
627 combinator::{opt, separated},
628 prelude::*,
629 };
630
631 fn item(input: &mut &str) -> ModalResult<(String, String)> {
633 let mut depth = 0usize;
634 let mut split = None; let mut end = input.len();
636 for (i, c) in input.char_indices() {
637 match c {
638 '{' => depth += 1,
639 '}' => depth = depth.saturating_sub(1),
640 '=' if depth == 0 && split.is_none() => split = Some(i),
641 ',' if depth == 0 => {
642 end = i;
643 break;
644 },
645 _ => {},
646 }
647 }
648 let (raw, rest) = input.split_at(end);
649 *input = rest;
650 let (k, v) = match split.filter(|s| *s < end) {
651 Some(eq) => (&raw[..eq], &raw[eq + 1..]),
652 None => (raw, ""),
653 };
654 let v = v.trim();
655 let v = v
656 .strip_prefix('{')
657 .and_then(|s| s.strip_suffix('}'))
658 .unwrap_or(v);
659 Ok((k.trim().to_string(), v.to_string()))
660 }
661
662 let mut parser = (separated(0.., item, ","), opt(","));
663 let items: Vec<(String, String)> = match parser.parse(kv) {
664 Ok((items, _)) => items,
665 Err(_) => Vec::new(), };
667 items.into_iter().filter(|(k, _)| !k.is_empty()).collect()
668}
669
670#[cfg(test)]
671mod keyval_source_tests {
672 use super::split_keyval_source;
673
674 #[test]
675 fn splits_at_depth_zero_only() {
676 assert_eq!(split_keyval_source("lang=rust, size={1, 2}"), vec![
677 ("lang".to_string(), "rust".to_string()),
678 ("size".to_string(), "1, 2".to_string()),
679 ]);
680 }
681
682 #[test]
683 fn flag_keys_and_empties() {
684 assert_eq!(split_keyval_source("draft,, a=1 ,"), vec![
685 ("draft".to_string(), String::new()),
686 ("a".to_string(), "1".to_string()),
687 ]);
688 }
689
690 #[test]
691 fn braced_equals_is_not_a_split() {
692 assert_eq!(split_keyval_source("k={a=b}"), vec![(
693 "k".to_string(),
694 "a=b".to_string()
695 )]);
696 }
697
698 #[test]
699 fn empty_input() {
700 assert!(split_keyval_source("").is_empty());
701 }
702}