| #!/bin/sh | |
| # | |
| # This is a script that can be used in each book's CI to validate links using | |
| # the same tool as rust-lang/rust. | |
| # | |
| # This requires the rust-docs rustup component to be installed in the nightly | |
| # toolchain. | |
| # | |
| # Usage: | |
| # ./linkcheck.sh <name-of-book> | |
| # | |
| # Options: | |
| # | |
| # -i "Iterative" mode. The script will not clean up after it is done so | |
| # you can inspect the result, and re-run more quickly. | |
| # | |
| # --all Check all books. This can help make sure you don't break links | |
| # from other books into your book. | |
| # | |
| # --path <book-path> | |
| # Path to the root directory for the book. Default to the current | |
| # working directory if omitted. | |
| set -e | |
| # Fetches the contents of a file from GitHub using the REST API. | |
| # Usage: fetch_github_file OWNER REPO PATH OUTPUT_PATH | |
| fetch_github_file() { | |
| owner="$1" | |
| repo="$2" | |
| path="$3" | |
| output="$4" | |
| # Hack to repurpose the function's positional parameters to conditionally | |
| # pass curl headers. The GITHUB_TOKEN should be set to help avoid rate | |
| # limits. | |
| if [ -n "${GITHUB_TOKEN:-}" ] | |
| then | |
| set -- -H "Authorization: Bearer $GITHUB_TOKEN" | |
| else | |
| set -- | |
| fi | |
| curl -L -o "$output" \ | |
| -H "Accept: application/vnd.github.raw" \ | |
| "$@" \ | |
| -H "X-GitHub-Api-Version: 2026-03-10" \ | |
| "https://api.github.com/repos/$owner/$repo/contents/$path" | |
| } | |
| html_dir="$(rustc +nightly --print sysroot)/share/doc/rust/html" | |
| if [ ! -d "$html_dir" ] | |
| then | |
| echo "HTML docs are missing from sysroot: $html_dir" | |
| echo "Make sure the nightly rust-docs rustup component is installed." | |
| exit 1 | |
| fi | |
| # Avoid failure caused by newer mdbook. | |
| export MDBOOK_OUTPUT__HTML__INPUT_404="" | |
| book_name="" | |
| # Default to the current directory | |
| book_path="." | |
| # Iterative will avoid cleaning up, so you can quickly run it repeatedly. | |
| iterative=0 | |
| # If "1", test all books, else only this book. | |
| all_books=0 | |
| while [ "$1" != "" ] | |
| do | |
| case "$1" in | |
| -i) | |
| iterative=1 | |
| ;; | |
| --all) | |
| all_books=1 | |
| ;; | |
| --path) | |
| book_path="${2:-.}" | |
| shift | |
| ;; | |
| *) | |
| if [ -n "$book_name" ] | |
| then | |
| echo "only one argument allowed" | |
| exit 1 | |
| fi | |
| book_name="$1" | |
| ;; | |
| esac | |
| shift | |
| done | |
| if [ -z "$book_name" ] | |
| then | |
| echo "usage: $0 <name-of-book>" | |
| exit 1 | |
| fi | |
| if [ ! -f "$book_path/book.toml" ] && [ ! -f "$book_path/src/SUMMARY.md" ] | |
| then | |
| echo "Run command in root directory of the book or provide a path to the book" | |
| exit 1 | |
| fi | |
| if [ ! -d "$html_dir/$book_name" ] | |
| then | |
| echo "book name \"$book_name\" not found in sysroot \"$html_dir\"" | |
| exit 1 | |
| fi | |
| if [ "$iterative" = "0" ] | |
| then | |
| echo "Cleaning old directories..." | |
| rm -rf linkcheck linkchecker | |
| fi | |
| if [ ! -e "linkchecker/main.rs" ] || [ "$iterative" = "0" ] | |
| then | |
| echo "Downloading linkchecker source..." | |
| mkdir linkchecker | |
| fetch_github_file rust-lang rust Cargo.lock linkchecker/Cargo.lock | |
| fetch_github_file rust-lang rust src/tools/linkchecker/Cargo.toml linkchecker/Cargo.toml | |
| fetch_github_file rust-lang rust src/tools/linkchecker/main.rs linkchecker/main.rs | |
| fi | |
| echo "Building book \"$book_name\"..." | |
| mdbook build "$book_path" | |
| cp -R "$html_dir" linkcheck | |
| rm -rf "linkcheck/$book_name" | |
| cp -R "$book_path/book" "linkcheck/$book_name" | |
| if [ "$all_books" = "1" ] | |
| then | |
| check_path="linkcheck" | |
| else | |
| check_path="linkcheck/$book_name" | |
| fi | |
| echo "Running linkchecker on \"$check_path\"..." | |
| cargo run --release --manifest-path=linkchecker/Cargo.toml -- "$check_path" | |
| if [ "$iterative" = "0" ] | |
| then | |
| rm -rf linkcheck linkchecker | |
| fi | |
| echo "Link check completed successfully!" |