summaryrefslogtreecommitdiffstats
path: root/src/launcher/strsub.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher/strsub.rs')
-rw-r--r--src/launcher/strsub.rs142
1 files changed, 91 insertions, 51 deletions
diff --git a/src/launcher/strsub.rs b/src/launcher/strsub.rs
index e01ef80..c89681e 100644
--- a/src/launcher/strsub.rs
+++ b/src/launcher/strsub.rs
@@ -8,39 +8,30 @@ const VAR_BEGIN: &str = "${";
const VAR_END: &str = "}";
const VAR_DEFAULT: &str = ":-";
-fn prev_char(slice: &str, idx: usize) -> Option<(usize, char)> {
- slice[..idx].char_indices().rev().next()
+pub trait SubFunc<'k, 'rep>: Copy {
+ fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>>;
}
-pub trait SubFunc<'rep>: Fn(&str) -> Option<Cow<'rep, str>> {
- fn substitute(&self, key: &str) -> Option<Cow<'rep, str>>;
-}
-
-impl<'rep, F> SubFunc<'rep> for F
+impl<'k, 'rep, F> SubFunc<'k, 'rep> for F
where
- F: Fn(&str) -> Option<Cow<'rep, str>>
+ F: Fn(&'k str) -> Option<Cow<'rep, str>> + Copy
{
- fn substitute(&self, key: &str) -> Option<Cow<'rep, str>> {
+ fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>> {
self(key)
}
}
-// basically the same thing as replace_string, but it creates the String itself and returns it.
-pub fn replace_str<'rep>(input: &str, sub: impl SubFunc<'rep>) -> String {
- let mut input = String::from(input);
- replace_string(&mut input, sub);
- input
-}
-
-pub fn replace_thru<'rep>(mut input: String, sub: impl SubFunc<'rep>) -> String {
- replace_string(&mut input, sub);
- input
-}
+/* NOTE: the in-place implementation has been replaced for the following reasons:
+ * - it was annoying to get lifetimes to work, so you could only either pass a trait implementation
+ * or a closure
+ * - it was probably slower than doing it out-of-place anyway, since you keep having to copy the
+ * tail of the string for each replacement
+ */
// handles ${replacements} on this string IN-PLACE. Calls the "sub" function for each key it receives.
// if "sub" returns None, it will use a default value or ignore the ${substitution}.
// There are no "invalid inputs" and this function should never panic unless "sub" panics.
-pub fn replace_string<'rep>(input: &mut String, sub: impl SubFunc<'rep>) {
+/*pub fn replace_string(input: &mut String, sub: impl SubFunc) {
let mut cursor = input.len();
while let Some(idx) = input[..cursor].rfind(VAR_BEGIN) {
// note: for some reason, apache processes escapes BEFORE checking if it's even a valid
@@ -76,6 +67,58 @@ pub fn replace_string<'rep>(input: &mut String, sub: impl SubFunc<'rep>) {
cursor = idx;
}
+}*/
+
+pub fn replace_string<'k, 'rep>(input: &'k str, sub: impl SubFunc<'k, 'rep>) -> Cow<'k, str> {
+ let mut ret: Option<String> = None;
+ let mut cursor = 0usize;
+
+ while let Some(idx) = input[cursor..].find(VAR_BEGIN) {
+ let idx = idx + cursor; // make idx an absolute index into 'input'
+ let spec_start = idx + VAR_BEGIN.len(); // the start of the "spec" (area inside {})
+
+ // first, check if this is escaped
+ if let Some((prev_idx, ESCAPE)) = input[..idx].char_indices().rev().next() {
+ let s = ret.get_or_insert_default();
+ s.push_str(&input[cursor..prev_idx]);
+
+ // advance past this so we don't match it again
+ s.push_str(&input[idx..spec_start]);
+ cursor = spec_start;
+ continue;
+ }
+
+ // now, find the closing tag
+ let Some(spec_end) = input[spec_start..].find(VAR_END).map(|v| v + spec_start) else {
+ break; // reached the end of the string
+ };
+
+ let full_spec = &input[spec_start..spec_end];
+
+ // check for a default argument
+ let (name, def) = if let Some(defidx) = full_spec.find(VAR_DEFAULT) {
+ (&full_spec[..defidx], Some(&full_spec[(defidx + VAR_DEFAULT.len())..]))
+ } else {
+ (full_spec, None)
+ };
+
+ let after = spec_end + VAR_END.len();
+ if let Some(subst) = sub.substitute(name).map_or_else(|| def.map(|d| Cow::Borrowed(d)), |v| Some(v)) {
+ let s = ret.get_or_insert_default();
+ s.push_str(&input[cursor..idx]);
+ s.push_str(subst.as_ref());
+ } else {
+ ret.get_or_insert_default().push_str(&input[cursor..after]);
+ }
+
+ cursor = after;
+ }
+
+ if let Some(ret) = ret.as_mut() {
+ ret.push_str(&input[cursor..]);
+ }
+
+ ret.map_or(Cow::Borrowed(input), Cow::Owned)
}
#[cfg(test)]
@@ -93,65 +136,62 @@ mod tests {
#[test]
fn test_standard_replace() {
- assert_eq!(replace_str("this has ${exists} and more", replace_fun), "this has value123 and more");
- assert_eq!(replace_str("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace");
- assert_eq!(replace_str("${exists}${exists}", replace_fun), "value123value123");
+ assert_eq!(replace_string("this has ${exists} and more", replace_fun), "this has value123 and more");
+ assert_eq!(replace_string("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace");
+ assert_eq!(replace_string("${exists}${exists}", replace_fun), "value123value123");
}
#[test]
fn test_empty_replace() {
- assert_eq!(replace_str("this has ${empty} and more", replace_fun), "this has ${empty} and more");
- assert_eq!(replace_str("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace");
- assert_eq!(replace_str("${empty}${empty}", replace_fun), "${empty}${empty}");
+ assert_eq!(replace_string("this has ${empty} and more", replace_fun), "this has ${empty} and more");
+ assert_eq!(replace_string("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace");
+ assert_eq!(replace_string("${empty}${empty}", replace_fun), "${empty}${empty}");
}
#[test]
fn test_homogenous_replace() {
- assert_eq!(replace_str("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ...");
- assert_eq!(replace_str("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ...");
- assert_eq!(replace_str("${exists}${empty}", replace_fun), "value123${empty}");
- assert_eq!(replace_str("${empty}${exists}", replace_fun), "${empty}value123");
+ assert_eq!(replace_string("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ...");
+ assert_eq!(replace_string("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ...");
+ assert_eq!(replace_string("${exists}${empty}", replace_fun), "value123${empty}");
+ assert_eq!(replace_string("${empty}${exists}", replace_fun), "${empty}value123");
}
#[test]
fn test_default_replace() {
- assert_eq!(replace_str("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ...");
- assert_eq!(replace_str("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ...");
- assert_eq!(replace_str("abc${empty:-}def", replace_fun), "abcdef");
- assert_eq!(replace_str("${empty:-}${empty:-}", replace_fun), "");
+ assert_eq!(replace_string("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ...");
+ assert_eq!(replace_string("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ...");
+ assert_eq!(replace_string("abc${empty:-}def", replace_fun), "abcdef");
+ assert_eq!(replace_string("${empty:-}${empty:-}", replace_fun), "");
}
#[test]
fn test_escape() {
- assert_eq!(replace_str("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)");
- assert_eq!(replace_str("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123");
+ assert_eq!(replace_string("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)");
+ assert_eq!(replace_string("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123");
// make sure this weird behavior is preserved... (the original code seemed to show it)
- assert_eq!(replace_str("some $${ else", replace_fun), "some ${ else");
+ assert_eq!(replace_string("some $${ else", replace_fun), "some ${ else");
}
#[test]
fn test_weird() {
- assert_eq!(replace_str("${exists}", replace_fun), "value123");
- assert_eq!(replace_str("$${empty}", replace_fun), "${empty}");
- assert_eq!(replace_str("${empty:-a}", replace_fun), "a");
- assert_eq!(replace_str("${empty:-}", replace_fun), "");
-
- // there is no nested evaluation, but the algorithm does proceed through the string backwards
- assert_eq!(replace_str("${exists:-${exists}}", replace_fun), "${exists:-value123}");
+ assert_eq!(replace_string("${exists}", replace_fun), "value123");
+ assert_eq!(replace_string("$${empty}", replace_fun), "${empty}");
+ assert_eq!(replace_string("${empty:-a}", replace_fun), "a");
+ assert_eq!(replace_string("${empty:-}", replace_fun), "");
}
// these make sure it doesn't chop up multibyte characters illegally
#[test]
fn test_multibyte_surround() {
- assert_eq!(replace_str("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}");
- assert_eq!(replace_str("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}");
+ assert_eq!(replace_string("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}");
+ assert_eq!(replace_string("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}");
}
#[test]
fn test_multibyte_replace() {
- assert_eq!(replace_str("borger ${borger}", replace_fun), "borger \u{1f354}");
- assert_eq!(replace_str("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}");
- assert_eq!(replace_str("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}");
+ assert_eq!(replace_string("borger ${borger}", replace_fun), "borger \u{1f354}");
+ assert_eq!(replace_string("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}");
+ assert_eq!(replace_string("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}");
}
-} \ No newline at end of file
+}