How Do I Use Include_Str! for Multiple Files or an Entire Directory?

I would like to copy an entire directory to a location in a user's $HOME. Individually copying files to that directory is straightforward:

let contents = include_str!("resources/profiles/default.json");
let fpath = dpath.join(&fname);
fs::write(fpath, contents).expect(&format!("failed to create profile: {}", n));

I haven't found a way to adapt this to multiple files:

for n in ["default"] {
    let fname = format!("{}{}", n, ".json");
    let x = format!("resources/profiles/{}", fname).as_str();
    let contents = include_str!(x);
    let fpath = dpath.join(&fname);
    fs::write(fpath, contents).expect(&format!("failed to create profile: {}", n));
}

...the compiler complains that x must be a string literal.

As far as I know, there are two options:

  1. Write a custom macro.
  2. Replicate the first code for each file I want to copy.

What is the best way of doing this?

3 Answers

I would create a build script that iterates through a directory, building up an array of tuples containing the name and another macro call to include the raw data:

use std::{
    env,
    error::Error,
    fs::{self, File},
    io::Write,
    path::Path,
};

const SOURCE_DIR: &str = "some/path/to/include";

fn main() -> Result<(), Box<dyn Error>> {
    let out_dir = env::var("OUT_DIR")?;
    let dest_path = Path::new(&out_dir).join("all_the_files.rs");
    let mut all_the_files = File::create(&dest_path)?;

    writeln!(&mut all_the_files, r##"["##,)?;

    for f in fs::read_dir(SOURCE_DIR)? {
        let f = f?;

        if !f.file_type()?.is_file() {
            continue;
        }

        writeln!(
            &mut all_the_files,
            r##"("{name}", include_bytes!(r#"{name}"#)),"##,
            name = f.path().display(),
        )?;
    }

    writeln!(&mut all_the_files, r##"]"##,)?;

    Ok(())
}

This has some weaknesses, namely that it requires the path to be expressible as a &str. Since you were already using include_string!, I don't think that's an extra requirement. This also means that the generated string has to be a valid Rust string. We use raw strings inside the generated file, but this can still fail if a filename were to contain the string "#. A better solution would probably use str::escape_default.

Since we are including files, I used include_bytes! instead of include_str!, but if you really needed to you can switch back. The raw bytes skips performing UTF-8 validation at compile time, so it's a small win.

Using it involves importing the generated value:

const ALL_THE_FILES: &[(&str, &[u8])] = &include!(concat!(env!("OUT_DIR"), "/all_the_files.rs"));

fn main() {
    for (name, data) in ALL_THE_FILES {
        println!("File {} is {} bytes", name, data.len());
    }
}

See also:

3

Using a macro:

macro_rules! incl_profiles {
    ( $( $x:expr ),* ) => {
        {
            let mut profs = Vec::new();
            $(
                profs.push(($x, include_str!(concat!("resources/profiles/", $x, ".json"))));
            )*

            profs
        }
    };
}

...

let prof_tups: Vec<(&str, &str)> = incl_profiles!("default", "python");

for (prof_name, prof_str) in prof_tups {
    let fname = format!("{}{}", prof_name, ".json");
    let fpath = dpath.join(&fname);
    fs::write(fpath, prof_str).expect(&format!("failed to create profile: {}", prof_name));
}

Note: This is not dynamic. The files ("default" and "python") are specified in the call to the macro.

Updated: Use Vec instead of HashMap.

3

You can use include_dir macro.

use include_dir::{include_dir, Dir};
use std::path::Path;

const PROJECT_DIR: Dir = include_dir!(".");

// of course, you can retrieve a file by its full path
let lib_rs = PROJECT_DIR.get_file("src/lib.rs").unwrap();

// you can also inspect the file's contents
let body = lib_rs.contents_utf8().unwrap();
assert!(body.contains("SOME_INTERESTING_STRING"));

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Sophia Al-Mansoor

Sophia Al-Mansoor

Global Business & E-Commerce Reporter

Sophia analyzes international trade, startup ecosystems, retail transformation, and supply chain logistics for modern digital publications.

Share this article
Twitter Facebook Pinterest