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}