0

I have the following:

  • A Vec<&str>.
  • A &str that may contain $0, $1, etc. referencing the elements in the vector.

I want to get a version of my &str where all occurences of $i are replaced by the ith element of the vector. So if I have vec!["foo", "bar"] and $0$1, the result would be foobar.

My first naive approach was to iterate over i = 1..N and do a search and replace for every index. However, this is a quite ugly and inefficient solution. Also, it gives undesired outputs if any of the values in the vector contains the $ character.

Is there a better way to do this in Rust?

Anders
  • 8,307
  • 9
  • 56
  • 88
  • 2
    *iterate [...] and do a search and replace for every index* — What behavior do you want if you have `vec!["$1", "x"]` and `$0$1`? What about `vec!["$", "2", "moo"]` and `$0$1`? – Shepmaster Dec 30 '18 at 00:37
  • @Shepmaster I have control over the inputs, and the vector will never contain any `$`. But preferably it would not replace recursively, so in your example `$1x` and `$2`. – Anders Dec 30 '18 at 00:55

2 Answers2

3

This solution is inspired (including copied test cases) by Shepmaster's, but simplifies things by using the replace_all method.

use regex::{Regex, Captures};

fn template_replace(template: &str, values: &[&str]) -> String {
    let regex = Regex::new(r#"\$(\d+)"#).unwrap();
    regex.replace_all(template, |captures: &Captures| {
        values
            .get(index(captures))
            .unwrap_or(&"")
    }).to_string()
}

fn index(captures: &Captures) -> usize {
    captures.get(1)
        .unwrap()
        .as_str()
        .parse()
        .unwrap()
}

fn main() {
    assert_eq!("ab", template_replace("$0$1", &["a", "b"]));
    assert_eq!("$1b", template_replace("$0$1", &["$1", "b"]));
    assert_eq!("moo", template_replace("moo", &[]));
    assert_eq!("abc", template_replace("a$0b$0c", &[""]));
    assert_eq!("abcde", template_replace("a$0c$1e", &["b", "d"]));
    println!("It works!");
}
Anders
  • 8,307
  • 9
  • 56
  • 88
  • 1
    I think this answer is better than mine; I'm sad that I forgot that the regex crate has this functionality. – Shepmaster Dec 31 '18 at 02:08
2

I would use a regex

use regex::Regex; // 1.1.0

fn example(s: &str, vals: &[&str]) -> String {
    let r = Regex::new(r#"\$(\d+)"#).unwrap();

    let mut start = 0;
    let mut new = String::new();

    for caps in r.captures_iter(s) {
        let m = caps.get(0).expect("Regex group 0 missing");
        let d = caps.get(1).expect("Regex group 1 missing");
        let d: usize = d.as_str().parse().expect("Could not parse index");

        // Copy non-placeholder
        new.push_str(&s[start..m.start()]);
        // Copy placeholder
        new.push_str(&vals[d]);

        start = m.end()
    }

    // Copy non-placeholder
    new.push_str(&s[start..]);

    new
}

fn main() {
    assert_eq!("ab", example("$0$1", &["a", "b"]));
    assert_eq!("$1b", example("$0$1", &["$1", "b"]));
    assert_eq!("moo", example("moo", &[]));
    assert_eq!("abc", example("a$0b$0c", &[""]));
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366