Skip to content

Commit eb1b7dc

Browse files
committed
Tweak comments + missing unit test
1 parent 81c8bac commit eb1b7dc

File tree

2 files changed

+61
-89
lines changed

2 files changed

+61
-89
lines changed

src/haystack/val/dict.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -371,14 +371,7 @@ macro_rules! dict_has(
371371
};
372372
);
373373

374-
fn decode_str_from_value(val: &'_ Value) -> Cow<'_, str> {
375-
match val {
376-
Value::Str(val) => Cow::Borrowed(&val.value),
377-
_ => Cow::Owned(val.to_string()),
378-
}
379-
}
380-
381-
/// Convert a dict to its display string.
374+
/// Convert a dict to its formatted display string.
382375
pub fn dict_to_dis<'a, GetLocalizedFunc>(
383376
dict: &'a Dict,
384377
get_localized: &'a GetLocalizedFunc,
@@ -393,9 +386,7 @@ where
393386

394387
if let Some(val) = dict.get("disMacro") {
395388
return match val {
396-
Value::Str(val) => {
397-
Cow::Owned(dis_macro(&val.value, |val| dict.get(val), get_localized))
398-
}
389+
Value::Str(val) => dis_macro(&val.value, |val| dict.get(val), get_localized),
399390
_ => decode_str_from_value(val),
400391
};
401392
}
@@ -431,6 +422,13 @@ where
431422
def.unwrap_or(Cow::Borrowed(""))
432423
}
433424

425+
fn decode_str_from_value(val: &'_ Value) -> Cow<'_, str> {
426+
match val {
427+
Value::Str(val) => Cow::Borrowed(&val.value),
428+
_ => Cow::Owned(val.to_string()),
429+
}
430+
}
431+
434432
#[cfg(test)]
435433
mod test {
436434
use std::borrow::Cow;
@@ -511,4 +509,13 @@ mod test {
511509
let dict = dict!["dis" => Value::make_str("display")];
512510
assert_eq!(dict.dis(), "display");
513511
}
512+
513+
#[test]
514+
fn dict_returns_default_value_if_none_found() {
515+
let dict = dict!["something" => Value::make_str("display")];
516+
assert_eq!(
517+
dict_to_dis(&dict, &|_| None, Some("default".into())),
518+
"default"
519+
);
520+
}
514521
}

src/haystack/val/dis_macro.rs

Lines changed: 43 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,28 @@
33
use std::{borrow::Cow, sync::OnceLock};
44

55
use super::Value;
6-
use regex::{Captures, Regex, Replacer};
6+
use regex::{Captures, Match, Regex, Replacer};
77

8-
/// Replaces a value in a string.
9-
struct ValueReplacer<'a, 'b, GetValueFunc>
8+
/// A regular expression replacer implementation for replacing formatted
9+
/// values in a display macro string.
10+
struct DisReplacer<'a, 'b, GetValueFunc, GetLocalizedFunc>
1011
where
1112
GetValueFunc: Fn(&str) -> Option<&'a Value>,
13+
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
1214
{
1315
get_value: &'b GetValueFunc,
16+
17+
get_localized: &'b GetLocalizedFunc,
1418
}
1519

16-
impl<'a, 'b, GetValueFunc> Replacer for ValueReplacer<'a, 'b, GetValueFunc>
20+
impl<'a, 'b, GetValueFunc, GetLocalizedFunc> Replacer
21+
for DisReplacer<'a, 'b, GetValueFunc, GetLocalizedFunc>
1722
where
1823
GetValueFunc: Fn(&str) -> Option<&'a Value>,
24+
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
1925
{
2026
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
21-
if let Some(cap_match) = caps.get(1) {
27+
let mut handle_value_capture = |cap_match: Match<'_>| {
2228
if let Some(value) = (self.get_value)(cap_match.as_str()) {
2329
if let Value::Ref(val) = value {
2430
dst.push_str(val.dis.as_ref().unwrap_or(&val.value));
@@ -30,26 +36,16 @@ where
3036
} else {
3137
dst.push_str(caps.get(0).unwrap().as_str());
3238
}
33-
} else {
34-
dst.push_str(caps.get(0).unwrap().as_str());
35-
}
36-
}
37-
}
38-
39-
/// Replaces a localized value in a string.
40-
struct LocalizedReplacer<'a, 'b, GetLocalizedFunc>
41-
where
42-
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
43-
{
44-
get_localized: &'b GetLocalizedFunc,
45-
}
46-
47-
impl<'a, 'b, GetLocalizedFunc> Replacer for LocalizedReplacer<'a, 'b, GetLocalizedFunc>
48-
where
49-
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
50-
{
51-
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
52-
if let Some(cap_match) = caps.get(1) {
39+
};
40+
41+
// Replace $tag
42+
if let Some(cap_match) = caps.get(2) {
43+
handle_value_capture(cap_match);
44+
// Replace ${tag}
45+
} else if let Some(cap_match) = caps.get(4) {
46+
handle_value_capture(cap_match);
47+
// Replace $<pod::key>
48+
} else if let Some(cap_match) = caps.get(6) {
5349
if let Some(value) = (self.get_localized)(cap_match.as_str()) {
5450
dst.push_str(&value);
5551
} else {
@@ -63,70 +59,39 @@ where
6359

6460
/// Process a macro pattern with the given scope of name/value pairs. Also includes localization processing.
6561
///
66-
/// * The pattern is a unicode string with embedded expressions:
67-
/// * '$tag': resolve tag name from scope, variable name ends with first non-tag character.
68-
/// * '${tag}': resolve tag name from scope.
69-
/// * '$<pod::key> localization key.
62+
/// The pattern is a unicode string with embedded expressions:
63+
/// * `$tag`: resolve tag name from scope, variable name ends with first non-tag character.
64+
/// * `${tag}`: resolve tag name from scope.
65+
/// * `$<pod::key>` localization key.
7066
///
71-
/// Any variables which cannot be resolved are resolved in the scope are returned as-is (i.e. $name)
67+
/// Any variables which cannot be resolved are resolved in the scope are returned as-is (i.e. `$name`)
7268
/// in the result string.
7369
///
74-
/// If a tag resolves to a Ref, then we use the Ref.dis for the string.
70+
/// If a tag resolves to a `Ref`, then we use the `Ref.dis` for the string.
7571
pub fn dis_macro<'a, GetValueFunc, GetLocalizedFunc>(
7672
pattern: &'a str,
7773
get_value: GetValueFunc,
7874
get_localized: GetLocalizedFunc,
79-
) -> String
75+
) -> Cow<'a, str>
8076
where
8177
GetValueFunc: Fn(&str) -> Option<&'a Value>,
8278
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
8379
{
84-
// Replace $tag
85-
let result = {
86-
static VARIABLE_REG_EX: OnceLock<Regex> = OnceLock::new();
87-
88-
let variable_regex =
89-
VARIABLE_REG_EX.get_or_init(|| Regex::new(r"\$([a-z][a-zA-Z0-9_]+)").unwrap());
90-
91-
variable_regex.replace_all(
92-
pattern,
93-
ValueReplacer {
94-
get_value: &get_value,
95-
},
96-
)
97-
};
98-
99-
// Replace ${tag}
100-
let result = {
101-
static SCOPE_REG_EX: OnceLock<Regex> = OnceLock::new();
102-
103-
let scope_regex =
104-
SCOPE_REG_EX.get_or_init(|| Regex::new(r"\$\{([a-z][a-zA-Z0-9_]+)\}").unwrap());
105-
106-
scope_regex.replace_all(
107-
&result,
108-
ValueReplacer {
109-
get_value: &get_value,
110-
},
111-
)
112-
};
113-
114-
// Replace $<pod::key>
115-
let result = {
116-
static LOCALIZED_REG_EX: OnceLock<Regex> = OnceLock::new();
117-
118-
let localized_regex = LOCALIZED_REG_EX.get_or_init(|| Regex::new(r"\$<([^>]+)>").unwrap());
119-
120-
localized_regex.replace_all(
121-
&result,
122-
LocalizedReplacer {
123-
get_localized: &get_localized,
124-
},
125-
)
126-
};
127-
128-
// TODO: return Cow?
129-
result.to_string()
80+
// Cache the regular expression in memory so it doesn't need to compile on each invocation.
81+
static REG_EX: OnceLock<Regex> = OnceLock::new();
82+
83+
let regex = REG_EX.get_or_init(|| {
84+
// Replace $tags, ${tag} or $<pod::key>
85+
Regex::new(r"(\$([a-z][a-zA-Z0-9_]+))|(\$\{([a-z][a-zA-Z0-9_]+)\})|(\$<([^>]+)>)").unwrap()
86+
});
87+
88+
regex.replace_all(
89+
pattern,
90+
DisReplacer {
91+
get_value: &get_value,
92+
get_localized: &get_localized,
93+
},
94+
)
13095
}
13196

13297
#[cfg(test)]

0 commit comments

Comments
 (0)