tmpl_resolver/resolver/from_slice.rs
1use alloc::collections::BTreeMap;
2
3use tap::Pipe;
4
5use crate::{
6 TemplateResolver,
7 error::{ResolverError, ResolverResult},
8 parsers::parse_value_or_map_err,
9 resolver::TemplateAST,
10};
11
12impl TryFrom<&[(&str, &str)]> for TemplateResolver {
13 type Error = ResolverError;
14
15 fn try_from(value: &[(&str, &str)]) -> Result<Self, Self::Error> {
16 Self::try_from_slice(value)
17 }
18}
19
20impl<const N: usize> TryFrom<[(&str, &str); N]> for TemplateResolver {
21 type Error = ResolverError;
22
23 fn try_from(value: [(&str, &str); N]) -> Result<Self, Self::Error> {
24 Self::try_from_str_entries(value.into_iter())
25 }
26}
27
28impl<K, V> TryFrom<BTreeMap<K, V>> for TemplateResolver
29where
30 K: AsRef<str>,
31 V: AsRef<str>,
32{
33 type Error = ResolverError;
34
35 fn try_from(value: BTreeMap<K, V>) -> Result<Self, Self::Error> {
36 Self::try_from_str_entries(value.into_iter())
37 }
38}
39
40impl TemplateResolver {
41 /// Construct from slice (no_std compatible)
42 ///
43 /// ```
44 /// use tap::Pipe;
45 /// use tmpl_resolver::TemplateResolver;
46 ///
47 /// let res = [
48 /// ("🐱", "喵 ฅ(°ω°ฅ)"),
49 /// ("hello", "Hello {🐱}"),
50 /// ]
51 /// .as_ref()
52 /// .pipe(TemplateResolver::try_from_slice)?;
53 ///
54 /// let text = res.get_with_context("hello", &[])?;
55 /// assert_eq!(text, "Hello 喵 ฅ(°ω°ฅ)");
56 ///
57 /// # Ok::<(), tmpl_resolver::error::ResolverError>(())
58 /// ```
59 pub fn try_from_slice(raw: &[(&str, &str)]) -> ResolverResult<Self> {
60 Self::try_from_str_entries(raw.iter().copied())
61 }
62
63 /// Attempts to build a TemplateResolver from raw unprocessed key-value
64 /// entries.
65 ///
66 /// ## Process Flow
67 ///
68 /// 1. Accepts an iterator of raw (key, value) pairs
69 /// 2. Parses each value into template AST (Abstract Syntax Tree)
70 /// 3. Converts keys to normalized format
71 /// 4. Collects results into a TemplateAST
72 /// 5. Constructs the final resolver
73 ///
74 /// ## Parameters
75 /// - `iter`: Iterator over raw unvalidated entries.
76 /// - e.g., `[(k1, v1), (k2, v2)].into_iter()`
77 ///
78 /// ## Type Constraints
79 /// - `K`: Key type with string-like representation
80 /// - `V`: Raw value type containing template text
81 /// - `I`: Iterator providing raw configuration entries
82 ///
83 /// ## Example
84 ///
85 /// ```
86 /// # #[cfg(all(feature = "serde", feature = "toml"))] {
87 /// use tap::Pipe;
88 /// use tmpl_resolver::{TemplateResolver, resolver::MiniStr, resolver::BTreeRawMap};
89 ///
90 ///
91 /// let res = r##"
92 /// "🐱" = "喵 ฅ(°ω°ฅ)"
93 ///
94 /// "问候" = """
95 /// $period ->
96 /// [morning] 早安{🐱}
97 /// [night] 晚安{🐱}
98 /// *[other] {$period}好
99 /// """
100 ///
101 /// "称谓" = """
102 /// $gender ->
103 /// [male] 先生
104 /// [female] 女士
105 /// *[test] { $🧑🏫 }
106 /// """
107 ///
108 /// greeting = "{ 问候 }!{ $name }{ 称谓 }。"
109 /// "##
110 /// .pipe(toml::from_str::<BTreeRawMap>)?
111 /// .into_iter()
112 /// .pipe(TemplateResolver::try_from_str_entries)?;
113 ///
114 /// assert_eq!(res.try_get("🐱")?, "喵 ฅ(°ω°ฅ)");
115 ///
116 /// # }
117 /// # Ok::<(), tmpl_resolver::Error>(())
118 /// ```
119 ///
120 /// See also:
121 /// - [Self::try_from_slice]
122 /// - [Self::try_from_raw]
123 pub fn try_from_str_entries<K, V, I>(iter: I) -> ResolverResult<Self>
124 where
125 K: AsRef<str>,
126 V: AsRef<str>,
127 I: Iterator<Item = (K, V)>,
128 {
129 iter
130 .map(|(key, value)| {
131 parse_value_or_map_err(key.as_ref(), value.as_ref()) //
132 .map(|tmpl| (convert_map_key(key.as_ref()), tmpl))
133 })
134 .collect::<Result<TemplateAST, _>>()?
135 .pipe(Self)
136 .pipe(Ok)
137 }
138}
139
140#[cfg(not(feature = "std"))]
141fn convert_map_key(key: &str) -> crate::MiniStr {
142 key.into()
143}
144
145#[cfg(feature = "std")]
146fn convert_map_key(key: &str) -> kstring::KString {
147 key.pipe(kstring::KString::from_ref)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[ignore]
155 #[test]
156 fn test_try_from_slice() -> ResolverResult<()> {
157 let _res = [
158 ("g", "Good"),
159 ("greeting", "{g} { time-period }! { $name }"),
160 (
161 "time-period",
162 "$period ->
163 [morning] Morning
164 *[other] {$period}",
165 ),
166 ]
167 .pipe_as_ref(TemplateResolver::try_from_slice)?;
168
169 // extern crate std;
170 // std::dbg!(res);
171 Ok(())
172 }
173}