rustdoc_assets/
lib.rs

1//! Build script helper which copies media and assets from a source directory below your
2//! current crate to the target output directory of rustdoc.
3//!
4//! rustdoc currently does not support copying media files to the documentation output
5//! directory. Pictures can only be included if they can be referenced as an online resource.
6//!
7//! This crate mitigates the problem. Add a call to
8//! [`copy_assets_folder()`](fn.copy_assets_folder.html) to the build script `build.rs` of
9//! your crate. This will copy the specified source directory to the rustdoc output directory.
10//!
11//! Source: [https://mrh.io/cargo-build-images-in-rust-docs/](https://mrh.io/cargo-build-images-in-rust-docs/)
12//!
13//! # Example
14//!
15//! Consider the following directory structure of crate "foo":
16//! ```text
17//! .
18//! ├── build.rs
19//! ├── Cargo.toml
20//! ├── changelog.md
21//! ├── doc
22//! │   └── img
23//! │       └── it-works.svg
24//! ├── readme.md
25//! ├── src
26//! │   └── lib.rs
27//! └── target
28//! ```
29//!
30//! In this example, a call to `cargo doc` would create the API documentation in
31//! `./target/doc/foo`. We want to include the file `doc/img/it-works.svg` in the crate
32//! documentation directory.
33//!
34//! To do this, add a build dependency to `rustdoc-assets` in your `Cargo.toml`:
35//!
36//! ```toml
37//! [build-dependencies]
38//! rustdoc-assets = "0.2"
39//! ```
40//!
41//! In `build.rs` do:
42//! ```
43//! # // simulate cargo environment variables so that the test compiles
44//! # std::env::set_var("HOST", "x86_64-unknown-linux-gnu");
45//! # std::env::set_var("TARGET", "x86_64-unknown-linux-gnu");
46//! rustdoc_assets::copy_assets_folder("doc/img");
47//! ```
48//!
49//! This will copy `./doc/img` to `./target/doc/foo/img`. In the rustdoc comment the
50//! images can then be referenced through an HTML-tag as follows:
51//!
52//! ```html
53//! /// <div align="center">
54//! /// <img src="img/it-works.svg" width="200" />
55//! /// </div>
56//! ```
57//!
58//! <div align="center">
59//! <img src="img/it-works.svg" width="200" /><br/>
60//! <span style="font-size: small">Source: <a href="https://en.m.wikipedia.org/wiki/File:SMirC-thumbsup.svg">Wikipedia (CC)</a></span>
61//! </div>
62//!
63//! # Update 2021-10-16
64//!
65//! In Rust 1.55 cargo doc now auto-cleans the `target/doc`-directory before generating
66//! the documentation. However, rustdoc-assets uses the build script and only executes
67//! during calls to cargo build/check. If cargo doc is executed afterwards the folders
68//! subsequently get removed. I currently do not have a better solution than to at least
69//! run cargo check one more time after cargo doc.
70//!
71
72use std::convert::AsRef;
73use std::path::{Path, PathBuf};
74use std::{env, fs};
75
76const COPY_OPTS: fs_extra::dir::CopyOptions = fs_extra::dir::CopyOptions {
77    overwrite: true,
78    skip_exist: false,
79    buffer_size: 64000,
80    copy_inside: false,
81    content_only: false,
82    depth: 0,
83};
84
85/// Copy media files (images, etc) to your rustdoc output directory.
86///
87/// `source` is the path to your assets base-folder(i. e. "doc/images").
88/// The path is relative to the location of your <span style="text-decoration:
89/// underline">crate</span>'s manifest file Cargo.toml.
90///
91/// The default output directory for documentation is `target/doc/`.
92///
93/// rustdoc supports two ways of changing this output directory:
94/// The command line flag `--target-dir` and the environment variable `CARGO_TARGET_DIR`.
95/// `rustdoc-assets` cannot know about the runtime command line argument, but it does
96/// check the environment variable for changes to the default output directory.
97///
98/// If your package is part of a workspace, create the following file as part of your
99/// workspace `.cargo/config.toml`:
100///
101/// ```toml
102/// [env]
103/// ## points to the current working directory; required for rustdoc-assets
104/// CARGO_WORKSPACE_DIR = { value = "", relative = true }
105/// ```
106///
107/// This will cause `copy_assets_folder()` to use the workspace directory as base path,
108/// instead of your crate root directory.
109///
110/// See the [Crate documentation](index.html#example) for an example.
111///
112pub fn copy_assets_folder<T>(source: T)
113where
114    T: AsRef<Path>,
115{
116    // Should panic if these aren't a thing
117    let host = env::var("HOST").unwrap();
118    let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
119    let target = std::env::var("TARGET").unwrap();
120    let target_dir = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| String::from("target"));
121    let workspace_dir = std::env::var("CARGO_WORKSPACE_DIR").ok();
122    println!("host: {}", host);
123    println!("target: {}", target);
124    println!("target_dir: {:?}", target_dir);
125    println!("workspace_dir: {:?}", workspace_dir);
126
127    // If the target is the same as the host AND no target is
128    // _explicitly specified, the directory will be ./target/doc,
129    // Elsewhere, the directory will be ./target/{triple}/doc.
130    // FIXME: target == host WITH explicitly specified --target
131    //
132    // Also, Gotta deal with crates that might have a dash in their name
133    let sanitized_name = crate_name.replace('-', "_");
134    let mut docs_dir = if let Some(ws) = workspace_dir {
135        vec![ws]
136    } else {
137        Vec::new()
138    };
139    if host != target {
140        docs_dir.extend_from_slice(&[target_dir, target, String::from("doc"), sanitized_name]);
141    } else {
142        docs_dir.extend_from_slice(&[target_dir, String::from("doc"), sanitized_name])
143    }
144    let docs_dir = docs_dir.join("/");
145    let docs_dir_path = Path::new(&docs_dir);
146
147    // Pre-emptively create the directory and copy ./doc/img into there
148    fs::create_dir_all(&docs_dir).unwrap();
149    fs_extra::copy_items(&[&source], &docs_dir, &COPY_OPTS).unwrap();
150
151    let mut change_source = PathBuf::new();
152    change_source.push(&source);
153    // source directory changed
154    println!(
155        "cargo:rerun-if-changed={}",
156        change_source.as_path().display()
157    );
158    // destination directory changed (i. e. if cargo-doc was executed)
159    println!("cargo:rerun-if-changed={}", docs_dir_path.display());
160}