1use alloc::{
2 string::{String, ToString},
3 sync::Arc,
4 vec::Vec,
5};
6#[cfg(feature = "std")]
7use std::path::{Path, PathBuf};
8
9use crate::{
10 compat::{HashMap, HashSet},
11 compiled::{self, CompiledInlineTemplate, Segment},
12 context::Context,
13 error::TemplateError,
14 frontmatter::{self, Frontmatter},
15 types::VarDecl,
16 value::Value,
17};
18
19pub(crate) mod analysis;
20mod render_methods;
21#[cfg(not(feature = "std"))]
22use self::analysis::hash_source_no_std;
23use self::analysis::{
24 check_bare_enum_access, check_internal_key_access, check_name_collisions,
25 check_static_enum_in_conditions, check_undeclared_variables, check_unused_params,
26 collect_enum_type_keys, inject_enum_type_constants,
27};
28
29#[non_exhaustive]
47#[derive(Debug, Clone, Copy, Default)]
48pub struct CompileOptions<'a> {
49 pub allow_unused: bool,
54 #[cfg(feature = "std")]
58 pub base_dir: Option<&'a std::path::Path>,
59 pub env: &'a [(&'a str, crate::Value)],
63 #[cfg(not(feature = "std"))]
65 _phantom: core::marker::PhantomData<&'a ()>,
66}
67
68#[cfg(feature = "std")]
69impl<'a> CompileOptions<'a> {
70 #[must_use]
72 pub fn base_dir(mut self, dir: &'a std::path::Path) -> Self {
73 self.base_dir = Some(dir);
74 self
75 }
76}
77
78impl<'a> CompileOptions<'a> {
79 #[must_use]
81 pub fn allow_unused(mut self, allow: bool) -> Self {
82 self.allow_unused = allow;
83 self
84 }
85
86 #[must_use]
88 pub fn env(mut self, pairs: &'a [(&'a str, crate::Value)]) -> Self {
89 self.env = pairs;
90 self
91 }
92}
93
94pub struct Template {
100 body: String,
102 name: Option<String>,
104 description: Option<String>,
106 segments: Arc<[Segment]>,
108 declared_variables: Arc<[VarDecl]>,
110 #[cfg(feature = "std")]
112 base_dir: Option<PathBuf>,
113 inline_templates: Arc<HashMap<String, CompiledInlineTemplate>>,
115 source_hash: u64,
116 max_include_depth: usize,
117 has_defaults: bool,
119 consts: Arc<HashMap<String, crate::value::Value>>,
121 imported_consts: Arc<HashMap<String, crate::value::Value>>,
123 estimated_capacity: usize,
125 #[cfg(feature = "std")]
128 env_values: alloc::sync::Arc<[(String, Value)]>,
129 declared_names: Arc<HashSet<String>>,
131 #[cfg(feature = "std")]
137 checked_type_ids: std::sync::Mutex<Vec<core::any::TypeId>>,
138}
139
140impl core::fmt::Debug for Template {
141 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
142 f.debug_struct("Template")
143 .field("body", &self.body)
144 .field("name", &self.name)
145 .field("description", &self.description)
146 .field("segments", &self.segments)
147 .field("declared_variables", &self.declared_variables)
148 .field("source_hash", &self.source_hash)
149 .finish_non_exhaustive()
150 }
151}
152
153impl Clone for Template {
154 fn clone(&self) -> Self {
155 Self {
156 body: self.body.clone(),
157 name: self.name.clone(),
158 description: self.description.clone(),
159 segments: self.segments.clone(),
160 declared_variables: self.declared_variables.clone(),
161 #[cfg(feature = "std")]
162 base_dir: self.base_dir.clone(),
163 inline_templates: self.inline_templates.clone(),
164 source_hash: self.source_hash,
165 max_include_depth: self.max_include_depth,
166 has_defaults: self.has_defaults,
167 consts: self.consts.clone(),
168 imported_consts: self.imported_consts.clone(),
169 estimated_capacity: self.estimated_capacity,
170 #[cfg(feature = "std")]
171 env_values: self.env_values.clone(),
172 declared_names: self.declared_names.clone(),
173 #[cfg(feature = "std")]
176 checked_type_ids: std::sync::Mutex::new(
177 self.checked_type_ids
178 .lock()
179 .unwrap_or_else(std::sync::PoisonError::into_inner)
180 .clone(),
181 ),
182 }
183 }
184}
185
186#[cfg(feature = "std")]
190pub(crate) struct CachedTemplateData {
191 pub segments: Arc<[Segment]>,
193 pub declared_variables: Arc<[VarDecl]>,
195 pub base_dir: Option<PathBuf>,
197 pub inline_templates: Arc<HashMap<String, CompiledInlineTemplate>>,
199 pub source_hash: u64,
201 pub consts: Arc<HashMap<String, crate::value::Value>>,
203 pub imported_consts: Arc<HashMap<String, crate::value::Value>>,
205 pub name: Option<String>,
207 pub description: Option<String>,
209}
210
211#[doc(hidden)]
215pub struct PrecompiledTemplateData<'a> {
216 pub segments: &'a [Segment],
218 pub declared_variables: &'a [VarDecl],
220 pub inline_templates: &'a [(&'a str, CompiledInlineTemplate)],
222 pub source_hash: u64,
224 pub consts: &'a [(&'a str, crate::value::Value)],
226 pub imported_consts: &'a [(&'a str, crate::value::Value)],
228 pub name: Option<&'a str>,
230 pub description: Option<&'a str>,
232}
233
234fn build_declared_names(
235 declarations: &[VarDecl],
236 consts: &HashMap<String, Value>,
237) -> Arc<HashSet<String>> {
238 let mut names = HashSet::with_capacity(declarations.len() + consts.len());
239 for d in declarations {
240 names.insert(d.name.clone());
241 }
242 for k in consts.keys() {
243 names.insert(k.clone());
244 }
245 Arc::new(names)
246}
247
248impl Template {
249 #[cfg(feature = "std")]
255 pub fn from_file(path: &Path) -> Result<Self, TemplateError> {
256 let mut source = std::fs::read_to_string(path)?;
257 if source.contains('\r') {
259 source = source.replace("\r\n", "\n");
260 }
261 let (tmpl, _fm) =
262 Self::compile_from_source(&source, Some(path.parent().unwrap_or(Path::new("."))))?;
263 Ok(tmpl)
264 }
265
266 pub fn from_source(source: &str) -> Result<Self, TemplateError> {
272 let source = if source.contains('\r') {
274 alloc::borrow::Cow::Owned(source.replace("\r\n", "\n"))
275 } else {
276 alloc::borrow::Cow::Borrowed(source)
277 };
278 #[cfg(feature = "std")]
279 let (tmpl, _fm) = Self::compile_from_source(&source, None)?;
280 #[cfg(not(feature = "std"))]
281 let (tmpl, _fm) = Self::compile_from_source_no_std(&source)?;
282 Ok(tmpl)
283 }
284
285 pub fn compile(
310 source: &str,
311 options: CompileOptions<'_>,
312 ) -> Result<(Self, Frontmatter), TemplateError> {
313 let source = if source.contains('\r') {
316 alloc::borrow::Cow::Owned(source.replace("\r\n", "\n"))
317 } else {
318 alloc::borrow::Cow::Borrowed(source)
319 };
320 #[cfg(feature = "std")]
321 return Self::compile_inner(&source, options.base_dir, options.allow_unused, options.env);
322 #[cfg(not(feature = "std"))]
323 return Self::compile_inner_no_std(&source, options.allow_unused, options.env);
324 }
325
326 #[cfg(feature = "std")]
347 pub fn compile_file(
348 path: &Path,
349 options: CompileOptions<'_>,
350 ) -> Result<(Self, Frontmatter), TemplateError> {
351 let mut source = std::fs::read_to_string(path)?;
352 if source.contains('\r') {
354 source = source.replace("\r\n", "\n");
355 }
356 let base_dir = options.base_dir.or_else(|| path.parent());
357 Self::compile_inner(&source, base_dir, options.allow_unused, options.env)
358 }
359
360 #[cfg(feature = "std")]
362 fn compile_from_source(
363 source: &str,
364 base_dir: Option<&Path>,
365 ) -> Result<(Self, Frontmatter), TemplateError> {
366 Self::compile_inner(source, base_dir, false, &[])
367 }
368
369 #[cfg(feature = "std")]
374 fn compile_inner(
375 source: &str,
376 base_dir: Option<&Path>,
377 force_allow_unused: bool,
378 env_values: &[(&str, Value)],
379 ) -> Result<(Self, Frontmatter), TemplateError> {
380 let source_hash = crate::cache::hash_source(source);
381 let (fm, body) = if let Some(dir) = base_dir {
382 frontmatter::parse_frontmatter_with_base_dir(source, dir, env_values)?
383 } else {
384 frontmatter::parse_frontmatter_with_env(source, env_values)?
385 };
386 let body = body.to_string();
387 let (segments, inline_templates) = compiled::compile(&body, &fm.type_aliases)?;
388
389 let referenced = compiled::collect_referenced_params(&segments);
391 let case_labels = compiled::collect_unquoted_case_labels(&segments);
392 check_undeclared_variables(&referenced, &fm, &inline_templates)?;
393 check_unused_params(
394 &fm.declarations,
395 &referenced,
396 &case_labels,
397 force_allow_unused || fm.allow_unused,
398 )?;
399 check_name_collisions(&fm, &inline_templates, &segments)?;
400 let enum_keys = collect_enum_type_keys(&fm);
401 check_bare_enum_access(&segments, &enum_keys)?;
402 check_static_enum_in_conditions(&segments, &fm.type_aliases)?;
403 check_internal_key_access(&segments)?;
404 let label_errors =
406 compiled::validate_match_labels(&segments, &fm.declarations, &fm.type_aliases);
407 if !label_errors.is_empty() {
408 return Err(TemplateError::Syntax(label_errors.join("; ").into()));
409 }
410
411 let has_defaults = fm.declarations.iter().any(|d| d.default_value.is_some());
412 let mut consts: HashMap<String, Value> = fm
413 .consts
414 .iter()
415 .filter_map(|d| d.default_value.clone().map(|v| (d.name.clone(), v)))
416 .collect();
417 for d in &fm.env {
419 if let Some(ref v) = d.default_value {
420 consts.entry(d.name.clone()).or_insert_with(|| v.clone());
421 }
422 }
423 inject_enum_type_constants(&fm.type_aliases, &mut consts);
425 let segments: Arc<[Segment]> = Arc::from(segments);
426 let estimated_capacity = compiled::render::estimate_output_capacity(&segments);
427 let env_values: alloc::sync::Arc<[(String, Value)]> = env_values
428 .iter()
429 .map(|(k, v)| (k.to_string(), v.clone()))
430 .collect();
431 let declared_names = build_declared_names(&fm.declarations, &consts);
432 let tmpl = Self {
433 body,
434 name: fm.name.clone(),
435 description: fm.description.clone(),
436 segments,
437 declared_variables: Arc::from(fm.declarations.clone()),
438 base_dir: base_dir.map(Path::to_path_buf),
439 inline_templates: Arc::new(inline_templates),
440 source_hash,
441 max_include_depth: crate::scope::MAX_INCLUDE_DEPTH,
442 has_defaults,
443 consts: Arc::new(consts),
444 imported_consts: Arc::new(fm.imported_consts.clone()),
445 estimated_capacity,
446 env_values,
447 declared_names,
448 checked_type_ids: std::sync::Mutex::new(Vec::new()),
449 };
450 Ok((tmpl, fm))
451 }
452
453 #[cfg(not(feature = "std"))]
455 fn compile_from_source_no_std(source: &str) -> Result<(Self, Frontmatter), TemplateError> {
456 Self::compile_inner_no_std(source, false, &[])
457 }
458
459 #[cfg(not(feature = "std"))]
461 fn compile_inner_no_std(
462 source: &str,
463 force_allow_unused: bool,
464 env_values: &[(&str, Value)],
465 ) -> Result<(Self, Frontmatter), TemplateError> {
466 let source_hash = hash_source_no_std(source);
467 let (fm, body) = frontmatter::parse_frontmatter_with_env(source, env_values)?;
468 let body = body.to_string();
469 let (segments, inline_templates) = compiled::compile(&body, &fm.type_aliases)?;
470
471 let referenced = compiled::collect_referenced_params(&segments);
472 let case_labels = compiled::collect_unquoted_case_labels(&segments);
473 check_undeclared_variables(&referenced, &fm, &inline_templates)?;
474 check_unused_params(
475 &fm.declarations,
476 &referenced,
477 &case_labels,
478 force_allow_unused || fm.allow_unused,
479 )?;
480 check_name_collisions(&fm, &inline_templates, &segments)?;
481 let enum_keys = collect_enum_type_keys(&fm);
482 check_bare_enum_access(&segments, &enum_keys)?;
483 check_static_enum_in_conditions(&segments, &fm.type_aliases)?;
484 check_internal_key_access(&segments)?;
485 let label_errors =
487 compiled::validate_match_labels(&segments, &fm.declarations, &fm.type_aliases);
488 if !label_errors.is_empty() {
489 return Err(TemplateError::Syntax(label_errors.join("; ").into()));
490 }
491
492 let has_defaults = fm.declarations.iter().any(|d| d.default_value.is_some());
493 let mut consts: HashMap<String, Value> = fm
494 .consts
495 .iter()
496 .filter_map(|d| d.default_value.clone().map(|v| (d.name.clone(), v)))
497 .collect();
498 for d in &fm.env {
500 if let Some(ref v) = d.default_value {
501 consts.entry(d.name.clone()).or_insert_with(|| v.clone());
502 }
503 }
504 inject_enum_type_constants(&fm.type_aliases, &mut consts);
506 let segments: Arc<[Segment]> = Arc::from(segments);
507 let estimated_capacity = compiled::render::estimate_output_capacity(&segments);
508 let declared_names = build_declared_names(&fm.declarations, &consts);
509 let tmpl = Self {
510 body,
511 name: fm.name.clone(),
512 description: fm.description.clone(),
513 segments,
514 declared_variables: Arc::from(fm.declarations.clone()),
515 inline_templates: Arc::new(inline_templates),
516 source_hash,
517 max_include_depth: crate::scope::MAX_INCLUDE_DEPTH,
518 has_defaults,
519 consts: Arc::new(consts),
520 imported_consts: Arc::new(fm.imported_consts.clone()),
521 estimated_capacity,
522 declared_names,
523 };
524 Ok((tmpl, fm))
525 }
526
527 #[cfg(feature = "std")]
534 pub(crate) fn from_cached(data: CachedTemplateData) -> Self {
535 let has_defaults = data
536 .declared_variables
537 .iter()
538 .any(|d| d.default_value.is_some());
539 let estimated_capacity = compiled::render::estimate_output_capacity(&data.segments);
540 let declared_names = build_declared_names(&data.declared_variables, &data.consts);
541 Self {
542 body: String::new(),
543 name: data.name,
544 description: data.description,
545 segments: data.segments,
546 declared_variables: data.declared_variables,
547 base_dir: data.base_dir,
548 inline_templates: data.inline_templates,
549 source_hash: data.source_hash,
550 max_include_depth: crate::scope::MAX_INCLUDE_DEPTH,
551 has_defaults,
552 consts: data.consts,
553 imported_consts: data.imported_consts,
554 estimated_capacity,
555 env_values: alloc::sync::Arc::from([]),
556 declared_names,
557 checked_type_ids: std::sync::Mutex::new(Vec::new()),
558 }
559 }
560
561 #[doc(hidden)]
563 #[must_use]
564 pub fn from_precompiled(data: &PrecompiledTemplateData<'_>) -> Self {
565 let inline_map = data
566 .inline_templates
567 .iter()
568 .map(|(k, v)| (k.to_string(), v.clone()))
569 .collect();
570 let const_map = data
571 .consts
572 .iter()
573 .map(|(k, v)| (k.to_string(), v.clone()))
574 .collect();
575 let imported_const_map = data
576 .imported_consts
577 .iter()
578 .map(|(k, v)| (k.to_string(), v.clone()))
579 .collect();
580 let has_defaults = data
581 .declared_variables
582 .iter()
583 .any(|d| d.default_value.is_some());
584 let segments: Arc<[Segment]> = Arc::from(data.segments);
585 let estimated_capacity = compiled::render::estimate_output_capacity(&segments);
586 let declared_names = build_declared_names(data.declared_variables, &const_map);
587 Self {
588 body: String::new(),
589 name: data.name.map(String::from),
590 description: data.description.map(String::from),
591 segments,
592 declared_variables: Arc::from(data.declared_variables),
593 #[cfg(feature = "std")]
594 base_dir: None,
595 inline_templates: Arc::new(inline_map),
596 source_hash: data.source_hash,
597 max_include_depth: crate::scope::MAX_INCLUDE_DEPTH,
598 has_defaults,
599 consts: Arc::new(const_map),
600 imported_consts: Arc::new(imported_const_map),
601 estimated_capacity,
602 #[cfg(feature = "std")]
603 env_values: alloc::sync::Arc::from([]),
604 declared_names,
605 #[cfg(feature = "std")]
606 checked_type_ids: std::sync::Mutex::new(Vec::new()),
607 }
608 }
609
610 fn validate_context(&self, ctx: &Context, allow_extra: bool) -> Result<(), TemplateError> {
623 let mut missing = Vec::new();
624 let mut mismatch: Option<(String, crate::types::TypeCheckError)> = None;
625 for decl in self.declared_variables.iter() {
626 match ctx.get(&decl.name) {
627 None => {
628 if decl.default_value.is_none() {
630 missing.push(decl.name.as_str());
631 }
632 }
633 Some(value) => {
634 if mismatch.is_none()
635 && let Err(e) = decl.var_type.check(value)
636 {
637 mismatch = Some((decl.name.clone(), e));
638 }
639 }
640 }
641 }
642 if !missing.is_empty() {
644 return Err(TemplateError::MissingParams(
645 missing.into_iter().map(String::from).collect(),
646 ));
647 }
648 if let Some((name, check_err)) = mismatch {
649 let detail = if check_err.path.is_empty() {
650 String::new()
651 } else {
652 format!(" (at .{})", check_err.path)
653 };
654 return Err(TemplateError::TypeMismatch {
655 name: format!("{name}{detail}"),
656 expected: check_err.expected,
657 actual: check_err.actual,
658 actual_value: check_err.actual_value,
659 });
660 }
661 if !allow_extra
663 && ctx
664 .values
665 .keys()
666 .any(|k| !self.declared_names.contains(k.as_str()))
667 {
668 let extra: Vec<String> = ctx
669 .values
670 .keys()
671 .filter(|k| !self.declared_names.contains(k.as_str()))
672 .cloned()
673 .collect();
674 return Err(TemplateError::ExtraParams(extra));
675 }
676 Ok(())
677 }
678
679 #[must_use]
681 pub fn defaults(&self) -> HashMap<String, crate::value::Value> {
682 self.declared_variables
683 .iter()
684 .filter_map(|d| {
685 d.default_value
686 .as_ref()
687 .map(|v| (d.name.clone(), v.clone()))
688 })
689 .collect()
690 }
691
692 #[must_use]
694 pub fn default(&self, name: &str) -> Option<&crate::value::Value> {
695 self.declared_variables
696 .iter()
697 .find(|d| d.name == name)
698 .and_then(|d| d.default_value.as_ref())
699 }
700
701 #[must_use]
720 pub fn defaults_context(&self) -> Context {
721 let defaults = self.defaults();
722 let mut ctx = Context::with_capacity(defaults.len());
723 for (k, v) in defaults {
724 ctx.set(k, v);
725 }
726 ctx
727 }
728
729 #[must_use]
733 pub fn body(&self) -> &str {
734 &self.body
735 }
736
737 #[must_use]
739 pub fn name(&self) -> Option<&str> {
740 self.name.as_deref()
741 }
742
743 #[must_use]
745 pub fn description(&self) -> Option<&str> {
746 self.description.as_deref()
747 }
748
749 pub fn set_max_include_depth(&mut self, depth: usize) {
751 self.max_include_depth = depth;
752 }
753
754 #[must_use]
756 pub fn with_max_include_depth(mut self, depth: usize) -> Self {
757 self.max_include_depth = depth;
758 self
759 }
760
761 #[must_use]
766 pub fn declarations(&self) -> &[VarDecl] {
767 &self.declared_variables
768 }
769
770 pub(crate) fn segments(&self) -> &[crate::compiled::Segment] {
771 &self.segments
772 }
773
774 #[cfg(feature = "std")]
776 #[must_use]
777 pub fn base_dir(&self) -> Option<&Path> {
778 self.base_dir.as_deref()
779 }
780
781 #[must_use]
805 pub fn consts(&self) -> Arc<HashMap<String, Value>> {
806 self.consts.clone()
807 }
808
809 #[must_use]
812 pub fn consts_ref(&self) -> &HashMap<String, Value> {
813 &self.consts
814 }
815
816 #[must_use]
821 pub fn imported_consts(&self) -> Arc<HashMap<String, Value>> {
822 self.imported_consts.clone()
823 }
824
825 #[must_use]
828 pub fn imported_consts_ref(&self) -> &HashMap<String, Value> {
829 &self.imported_consts
830 }
831
832 pub(crate) fn inline_templates(&self) -> &HashMap<String, CompiledInlineTemplate> {
833 &self.inline_templates
834 }
835
836 #[must_use]
843 pub fn source_hash(&self) -> u64 {
844 self.source_hash
845 }
846
847 pub fn validate_declarations(&self, expected: &[VarDecl]) -> Result<(), TemplateError> {
862 let current: HashMap<&str, &crate::types::VarType> = self
863 .declared_variables
864 .iter()
865 .map(|d| (d.name.as_str(), &d.var_type))
866 .collect();
867 let expected_map: HashMap<&str, &crate::types::VarType> = expected
868 .iter()
869 .map(|d| (d.name.as_str(), &d.var_type))
870 .collect();
871
872 let current_names: HashSet<&str> = current.keys().copied().collect();
873 let expected_names: HashSet<&str> = expected_map.keys().copied().collect();
874
875 let missing: Vec<&str> = expected_names.difference(¤t_names).copied().collect();
876 let extra: Vec<&str> = current_names.difference(&expected_names).copied().collect();
877
878 let retyped: Vec<String> = current_names
880 .intersection(&expected_names)
881 .filter_map(|name| {
882 let cur_type = current[name];
883 let exp_type = expected_map[name];
884 if cur_type == exp_type {
885 None
886 } else {
887 Some(format!("{name}: {exp_type} → {cur_type}"))
888 }
889 })
890 .collect();
891
892 if missing.is_empty() && extra.is_empty() && retyped.is_empty() {
893 return Ok(());
894 }
895
896 let mut parts = Vec::new();
897 if !missing.is_empty() {
898 parts.push(format!("removed: {}", missing.join(", ")));
899 }
900 if !extra.is_empty() {
901 parts.push(format!("added: {}", extra.join(", ")));
902 }
903 if !retyped.is_empty() {
904 parts.push(format!("retyped: {}", retyped.join(", ")));
905 }
906
907 Err(TemplateError::DeclarationsMutated {
908 details: parts.join("; "),
909 })
910 }
911}
912
913impl PartialEq for Template {
926 fn eq(&self, other: &Self) -> bool {
927 self.source_hash == other.source_hash
928 }
929}
930
931impl Eq for Template {}
932
933#[cfg(feature = "serde")]
939impl serde::Serialize for Template {
940 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
941 serializer.serialize_str(&format!("template:{:016x}", self.source_hash))
942 }
943}
944
945#[cfg(feature = "serde")]
951impl<'de> serde::Deserialize<'de> for Template {
952 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
953 let _ = <serde::de::IgnoredAny as serde::Deserialize>::deserialize(deserializer)?;
955 Err(serde::de::Error::custom(
956 "Template cannot be deserialized; construct from source with \
957 Template::from_source() or Template::from_file()",
958 ))
959 }
960}
961
962#[cfg(feature = "std")]
970pub fn load_template(dir: &Path, name: &str) -> Result<Template, TemplateError> {
971 let path = dir.join(format!("{name}.tmpl.md"));
972 Template::from_file(&path)
973}
974
975#[cfg(all(test, feature = "std"))]
976mod adversarial_tests;
977#[cfg(all(test, feature = "std"))]
978mod collision_and_scope_tests;
979#[cfg(all(test, feature = "std"))]
980mod const_tests;
981#[cfg(all(test, feature = "std"))]
982mod error_diagnostic_tests;
983#[cfg(all(test, feature = "std"))]
984mod higher_order_tests;
985#[cfg(all(test, feature = "std"))]
986mod inline_edge_tests;
987#[cfg(all(test, feature = "std"))]
988mod render_integration_tests;
989#[cfg(all(test, feature = "std"))]
990mod shared_tests;
991#[cfg(all(test, feature = "std"))]
992mod tests;
993
994#[cfg(all(test, feature = "std"))]
995mod doc_example_tests;