1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
//! 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, ©_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());
}