sokoban-elements 0.2.0

Parsing and writing Sokoban, Hexoban, Trioban and Octoban file formats.
Documentation
#![allow(clippy::panic, clippy::unwrap_used)]

use std::path::{Path, PathBuf};
use std::{env, fs};

fn main() {
    let (crate_root_dir, target_docs_root_dir) = root_dirs();

    let source_paths = [
        crate_root_dir.join("doc/img"),
        crate_root_dir.join("doc/puzzles"),
    ];

    const COPY_OPTS: fs_extra::dir::CopyOptions = fs_extra::dir::CopyOptions {
        overwrite:    true,
        skip_exist:   false,
        buffer_size:  64000,
        copy_inside:  false,
        content_only: false,
        // fs_extra: 0 means "no depth limit"
        depth:        0,
    };

    fs_extra::copy_items(&source_paths, &target_docs_root_dir, &COPY_OPTS).unwrap_or_else(|err| {
        panic!("copy into \"{}\" failed: {err}", target_docs_root_dir.display())
    });

    // Note: `rerun-if-changed` on a directory only tracks that directory's own mtime. Adding or
    // removing a file under `doc/img` triggers a rerun; editing one in place does not.
    //
    // There is also no way to detect that `target/doc` was wiped, because detecting it would
    // require this script to run in the first place. After `cargo clean --doc` (or deleting
    // `target/doc` by hand) run `touch build.rs` to force the copy.
    for pth in source_paths {
        println!("cargo:rerun-if-changed={}", pth.as_path().display());
    }
}

/// `(crate root, directory that doc images are copied into)`.
///
/// Images end up in `<target>/doc/static.files/<crate name>/`, which is what the `../static.files/`
/// links in doc comments resolve to. rustdoc writes its own assets into `static.files/` but doesn't
/// prune subdirectories it doesn't know about, so our copies survive `cargo doc`.
fn root_dirs() -> (PathBuf, PathBuf) {
    // Deliberately *not* `.replace('-', "_")`: doc comments link to the package name verbatim.
    let crate_name = env::var("CARGO_PKG_NAME").unwrap();

    let crate_root_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into();

    let docs_out_dir_root = cargo_target_dir()
        .join("doc/static.files")
        .join(&crate_name);

    fs::create_dir_all(&docs_out_dir_root).unwrap_or_else(|err| {
        panic!("create_dir_all(\"{}\") failed: {err}", docs_out_dir_root.display())
    });

    (crate_root_dir, docs_out_dir_root.canonicalize().unwrap())
}

/// Cargo's build directory, derived from `OUT_DIR`.
///
/// `OUT_DIR` is `<target>/[<triple>/]<profile>/build/<pkg>-<hash>/out`, so four parents up is
/// `<target>` (or `<target>/<triple>` when `--target` is given) - and `doc/` hangs off exactly that
/// directory in both cases.
///
/// Reading `CARGO_TARGET_DIR` instead would miss `build.target-dir` in `.cargo/config.toml` (not
/// exposed as an env var) and `--target <triple>`.
fn cargo_target_dir() -> PathBuf {
    let out_dir: PathBuf = env::var("OUT_DIR").unwrap().into();

    out_dir.ancestors().nth(4).map_or_else(
        || panic!("can't derive target dir from OUT_DIR=\"{}\"", out_dir.display()),
        Path::to_path_buf,
    )
}