rustdoc-assets 0.2.1

Build script helper which copies media and assets from a source directory below your current crate to the target output directory of rustdoc.
Documentation
//! Build script helper which copies media and assets from a source directory below your
//! current crate to the target output directory of rustdoc.
//!
//! rustdoc currently does not support copying media files to the documentation output
//! directory. Pictures can only be included if they can be referenced as an online resource.
//!
//! This crate mitigates the problem. Add a call to
//! [`copy_assets_folder()`](fn.copy_assets_folder.html) to the build script `build.rs` of
//! your crate. This will copy the specified source directory to the rustdoc output directory.
//!
//! Source: [https://mrh.io/cargo-build-images-in-rust-docs/](https://mrh.io/cargo-build-images-in-rust-docs/)
//!
//! # Example
//!
//! Consider the following directory structure of crate "foo":
//! ```text
//! .
//! ├── build.rs
//! ├── Cargo.toml
//! ├── changelog.md
//! ├── doc
//! │   └── img
//! │       └── it-works.svg
//! ├── readme.md
//! ├── src
//! │   └── lib.rs
//! └── target
//! ```
//!
//! In this example, a call to `cargo doc` would create the API documentation in
//! `./target/doc/foo`. We want to include the file `doc/img/it-works.svg` in the crate
//! documentation directory.
//!
//! To do this, add a build dependency to `rustdoc-assets` in your `Cargo.toml`:
//!
//! ```toml
//! [build-dependencies]
//! rustdoc-assets = "0.2"
//! ```
//!
//! In `build.rs` do:
//! ```
//! # // simulate cargo environment variables so that the test compiles
//! # std::env::set_var("HOST", "x86_64-unknown-linux-gnu");
//! # std::env::set_var("TARGET", "x86_64-unknown-linux-gnu");
//! rustdoc_assets::copy_assets_folder("doc/img");
//! ```
//!
//! This will copy `./doc/img` to `./target/doc/foo/img`. In the rustdoc comment the
//! images can then be referenced through an HTML-tag as follows:
//!
//! ```html
//! /// <div align="center">
//! /// <img src="img/it-works.svg" width="200" />
//! /// </div>
//! ```
//!
//! <div align="center">
//! <img src="img/it-works.svg" width="200" /><br/>
//! <span style="font-size: small">Source: <a href="https://en.m.wikipedia.org/wiki/File:SMirC-thumbsup.svg">Wikipedia (CC)</a></span>
//! </div>
//!
//! # Update 2021-10-16
//!
//! In Rust 1.55 cargo doc now auto-cleans the `target/doc`-directory before generating
//! the documentation. However, rustdoc-assets uses the build script and only executes
//! during calls to cargo build/check. If cargo doc is executed afterwards the folders
//! subsequently get removed. I currently do not have a better solution than to at least
//! run cargo check one more time after cargo doc.
//!

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

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,
    depth: 0,
};

/// Copy media files (images, etc) to your rustdoc output directory.
///
/// `source` is the path to your assets base-folder(i. e. "doc/images").
/// The path is relative to the location of your <span style="text-decoration:
/// underline">crate</span>'s manifest file Cargo.toml.
///
/// The default output directory for documentation is `target/doc/`.
///
/// rustdoc supports two ways of changing this output directory:
/// The command line flag `--target-dir` and the environment variable `CARGO_TARGET_DIR`.
/// `rustdoc-assets` cannot know about the runtime command line argument, but it does
/// check the environment variable for changes to the default output directory.
///
/// If your package is part of a workspace, create the following file as part of your
/// workspace `.cargo/config.toml`:
///
/// ```toml
/// [env]
/// ## points to the current working directory; required for rustdoc-assets
/// CARGO_WORKSPACE_DIR = { value = "", relative = true }
/// ```
///
/// This will cause `copy_assets_folder()` to use the workspace directory as base path,
/// instead of your crate root directory.
///
/// See the [Crate documentation](index.html#example) for an example.
///
pub fn copy_assets_folder<T>(source: T)
where
    T: AsRef<Path>,
{
    // Should panic if these aren't a thing
    let host = env::var("HOST").unwrap();
    let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
    let target = std::env::var("TARGET").unwrap();
    let target_dir = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| String::from("target"));
    let workspace_dir = std::env::var("CARGO_WORKSPACE_DIR").ok();
    println!("host: {}", host);
    println!("target: {}", target);
    println!("target_dir: {:?}", target_dir);
    println!("workspace_dir: {:?}", workspace_dir);

    // If the target is the same as the host AND no target is
    // _explicitly specified, the directory will be ./target/doc,
    // Elsewhere, the directory will be ./target/{triple}/doc.
    // FIXME: target == host WITH explicitly specified --target
    //
    // Also, Gotta deal with crates that might have a dash in their name
    let sanitized_name = crate_name.replace('-', "_");
    let mut docs_dir = if let Some(ws) = workspace_dir {
        vec![ws]
    } else {
        Vec::new()
    };
    if host != target {
        docs_dir.extend_from_slice(&[target_dir, target, String::from("doc"), sanitized_name]);
    } else {
        docs_dir.extend_from_slice(&[target_dir, String::from("doc"), sanitized_name])
    }
    let docs_dir = docs_dir.join("/");
    let docs_dir_path = Path::new(&docs_dir);

    // Pre-emptively create the directory and copy ./doc/img into there
    fs::create_dir_all(&docs_dir).unwrap();
    fs_extra::copy_items(&[&source], &docs_dir, &COPY_OPTS).unwrap();

    let mut change_source = PathBuf::new();
    change_source.push(&source);
    // source directory changed
    println!(
        "cargo:rerun-if-changed={}",
        change_source.as_path().display()
    );
    // destination directory changed (i. e. if cargo-doc was executed)
    println!("cargo:rerun-if-changed={}", docs_dir_path.display());
}