glossa_dsl/resolver/from_slice.rs
1use alloc::collections::BTreeMap;
2
3use tap::Pipe;
4
5use crate::{
6 Resolver,
7 error::{ResolverError, ResolverResult},
8 parsers::parse_value_or_map_err,
9 resolver::AST,
10};
11
12impl TryFrom<&[(&str, &str)]> for Resolver {
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 Resolver {
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 Resolver
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 Resolver {
41 /// Construct from slice (no_std compatible)
42 ///
43 /// ```
44 /// use tap::Pipe;
45 /// use glossa_dsl::Resolver;
46 ///
47 /// let res = [
48 /// ("🐱", "喵 ฅ(°ω°ฅ)"),
49 /// ("hello", "Hello {🐱}"),
50 /// ]
51 /// .as_ref()
52 /// .pipe(Resolver::try_from_slice)?;
53 ///
54 /// let text = res.get_with_context("hello", &[])?;
55 /// assert_eq!(text, "Hello 喵 ฅ(°ω°ฅ)");
56 ///
57 /// # Ok::<(), glossa_dsl::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 Resolver 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 Glossa-DSL AST
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 glossa_dsl::{Resolver, resolver::MiniStr, resolver::BTreeRawMap};
89 ///
90 ///
91 /// let res = r##"
92 /// meow = "喵"
93 /// "🐱" = "{ meow } ฅ(°ω°ฅ)"
94 /// "##
95 /// .pipe(toml::from_str::<BTreeRawMap>)?
96 /// .into_iter()
97 /// .pipe(Resolver::try_from_str_entries)?;
98 ///
99 /// assert_eq!(res.try_get("🐱")?, "喵 ฅ(°ω°ฅ)");
100 ///
101 /// # }
102 /// # Ok::<(), glossa_dsl::Error>(())
103 /// ```
104 ///
105 /// See also:
106 /// - [Self::try_from_slice]
107 /// - [Self::try_from_raw]
108 pub fn try_from_str_entries<K, V, I>(iter: I) -> ResolverResult<Self>
109 where
110 K: AsRef<str>,
111 V: AsRef<str>,
112 I: Iterator<Item = (K, V)>,
113 {
114 iter
115 .map(|(key, value)| {
116 parse_value_or_map_err(key.as_ref(), value.as_ref()) //
117 .map(|tmpl| (convert_map_key(key.as_ref()), tmpl))
118 })
119 .collect::<Result<AST, _>>()?
120 .pipe(Self)
121 .pipe(Ok)
122 }
123}
124
125#[cfg(not(feature = "std"))]
126fn convert_map_key(key: &str) -> crate::MiniStr {
127 key.into()
128}
129
130#[cfg(feature = "std")]
131fn convert_map_key(key: &str) -> kstring::KString {
132 key.pipe(kstring::KString::from_ref)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[ignore]
140 #[test]
141 fn test_try_from_slice() -> ResolverResult<()> {
142 let _res = [
143 ("g", "Good"),
144 ("greeting", "{g} { time-period }! { $name }"),
145 (
146 "time-period",
147 "$period ->
148 [morning] Morning
149 *[other] {$period}",
150 ),
151 ]
152 .pipe_as_ref(Resolver::try_from_slice)?;
153
154 // extern crate std;
155 // std::dbg!(res);
156 Ok(())
157 }
158}