//! Finds all the `*.rs` files in `docs/snippets/all`, //! copies them to `src/snippets` (with slight modifications), and generate a `snippets/mod.rs` for them. //! //! The reason we combine all the snippets into a single binary //! is to reduce the amount of binaries in our workspace. //! //! Motivation: // TODO(#3408): remove unwrap() #![expect(clippy::unwrap_used)] use std::fs; use std::path::Path; use itertools::Itertools as _; fn main() { let crate_path = Path::new( &re_build_tools::get_and_track_env_var("CARGO_MANIFEST_DIR").unwrap(), ) .to_owned(); let all_path = crate_path.join("all"); let src_path = crate_path.join("src"); let snippets_path = src_path.join("snippets"); assert!( all_path.exists() && all_path.is_dir(), "Failed to find {all_path:?}" ); let mut snippets = Vec::new(); re_build_tools::rerun_if_changed(&all_path); for subdir in fs::read_dir(&all_path).unwrap().flatten() { if !subdir.path().is_dir() { continue; } for entry in fs::read_dir(subdir.path()).unwrap().flatten() { let path = entry.path(); if let Some(extension) = path.extension() { if extension == "rs" { let snippet_name = path.file_stem().unwrap().to_str().unwrap().to_owned(); let contents = fs::read_to_string(&path).unwrap(); // TODO(#4047): some snippets lack a main, they should come with their necessary stub code commented out so that we can re-add it here. if contents.contains("fn main()") { // Patch the source code so we can call into `main` and pass arguments to it: let contents = contents.replace( "fn main()", "pub fn main(_args: &[String])", ); let contents = contents.replace( "let args = std::env::args().collect::>();", "let args = _args;", ); let contents = format!( "//! DO NOT EDIT! This file was autogenerated by `{}`. The original is in `{}`.\n{contents}", file!().replace('\\', "/"), path.to_str().unwrap().replace('\\', "/"), ); let target_path = snippets_path.join(format!("{snippet_name}.rs")); println!("{}", target_path.display()); re_build_tools::write_file_if_necessary( target_path, contents.as_bytes(), ) .expect("failed to write snippet??"); snippets.push(snippet_name); } } else if extension == "png" { // Files used by the snippets, e.g. via `include_bytes`. Copy them: let target_path = snippets_path.join(path.file_name().unwrap()); fs::copy(&path, &target_path).unwrap(); } } } } snippets.sort(); assert!( snippets.len() > 10, "Found too few snippets in {all_path:?}" ); let source = r#" //! DO NOT EDIT! Code generated by ${FILE}. #![expect(clippy::exit)] ${MODS} // The generated match arm grows with every snippet, so we have exceeded clippy's 600-line limit. #[expect(clippy::too_many_lines)] pub fn run() { let args: Vec = std::env::args().skip(1).collect(); if args.is_empty() { eprintln!("Usage: {} ", std::env::args().next().unwrap_or_else(|| "snippets".to_owned())); eprintln!("Available snippets:"); eprintln!(); eprintln!("${SNIPPETS}"); std::process::exit(1); } let snippet_name = args[0].as_str(); match snippet_name { ${MATCH_SNIPPETS} _ => { eprintln!("Unknown snippet: {snippet_name}"); eprintln!("Available snippets:"); eprintln!(); eprintln!("${SNIPPETS}"); std::process::exit(1); } } } "# .trim() .replace("${FILE}", file!()) .replace( "${MODS}", &snippets.iter().map(|m| format!("mod {m};")).join("\n"), ) .replace("${SNIPPETS}", &snippets.iter().join("\\n")) .replace( "${MATCH_SNIPPETS}", &snippets .iter() .map(|m| { format!( r#"{m:?} => {{ if let Err(err) = {m}::main(&args) {{ panic!("Failed to run '{m}': {{err}}"); }} }}"# ) }) .join(",\n"), ); let source = re_build_tools::rustfmt_str(&source).expect("Failed to format"); re_build_tools::write_file_if_necessary( snippets_path.join("mod.rs"), source.as_bytes(), ) .unwrap(); }