Read file and formats

Hi to everyone,
I achieve this point:

let license_path = Path::new(path).join("LICENSE.md");
// LICENSE.md have {author} string
let license_string = format!(license_path)

but the format! macro acing on compile time and doesn't work here...
Anyone know how I can do it?
Thanks so much

Assuming your goal is to convert license_path into a string, you can do this two ways, which differ in how they handle any non-UTF-8 sequences that may be in the path:

  • license_path.to_string_lossy().into_owned() replaces non-UTF-8 sequences with U+FFFD REPLACEMENT CHARACTER
  • license_path.display().to_string() escapes non-UTF-8 sequences using backslash escapes[1], but any backslash sequences that may already be in the path are not escaped again, making the result potentially ambiguous

  1. I think; it's been a while since I last triggered this behavior ↩︎

You can't use format! for late-bound/dynamic string interpolation I'm afraid. format! only works with a string literal as its first argument, not variables or even constant string slices. You'll have to write your own interpolation logic (e.g. a simple license_string.replace("{author}", &author)), or use a more capable templating system, like handlebars.

1 Like

Hi @jofas, and in fact, your proposal it is that I use:

        // Check author
        if author != &String::from("None") {
            let license_path = Path::new(name).join("LICENSE.md");
            let license_string = read_to_string(license_path.as_path())
                .unwrap()
                .replace("{author}", author);
            let license_file = make_file(
                format!("{}", license_path.display()).as_str(),
                license_string.to_owned(),
            );
            if let Err(e) = license_file {
                eprintln!("error: {}", e);
            }
        }

I would think substitute replace method to format macro...but I don't know how, because this macro is acing at compile time...
If you see entire code is here: psp/src/main.rs at 61da40060a8ee5bff21186074d0b43a6a348439c · MatteoGuadrini/psp · GitHub

Yes. You can't substitute format! here, as you already have observed. The arguments you pass to make_file are suboptimal though. license_string is already an owned string, so no need to call .to_owned() on it. And you can pass the license_path directly if you change the first argument type of make_file from &str to &Path or even better, make it generic like File::create, i.e. fn make_file(file: impl AsRef<Path>, content: String) -> std::io::Result<()>.

I wrote a simple template engine to solve the task. So you can replace ${name} with a value of name at runtime. For example:

let created = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
        //let args = format!("{vec![Box::new(created}"))]; // created 
        let name:String = name.as_ref().to_string();
        let name = simweb::interpolate(&name,&vec![Box::new(&created as &dyn Display)]);
        let path:String=path.into();
        let mut log_path = PathBuf::from(&path);
        log_path.push(name.clone());
        log_path.set_extension("log");
        let file = File::create(&log_path).unwrap_or_else(|e| panic!("can't create log {log_path:?}/{e:?}"));

Or use std::fs::write unless you need the early exit(3).

Generally, you should avoid doing Path -> String -> Path round-trips.

1 Like