rusty-man 0.4.1

Command-line viewer for rustdoc documentation
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

//! Utility functions for executing tests with the output of different rustdoc versions.

use std::env;
use std::fs;
use std::path;
use std::process;
use std::sync;

static mut VERSIONS: Option<Vec<(semver::Version, path::PathBuf)>> = None;
static INIT: sync::Once = sync::Once::new();

fn get_versions() -> &'static [(semver::Version, path::PathBuf)] {
    INIT.call_once(|| {
        let mut versions = find_rust_versions("tests/html");
        if env::var("RUSTY_MAN_GENERATE").is_ok() {
            versions.push(generate_docs());
        }
        unsafe {
            VERSIONS = Some(versions);
        }
    });

    unsafe { VERSIONS.as_ref().unwrap() }
}

fn find_rust_versions(path: impl AsRef<path::Path>) -> Vec<(semver::Version, path::PathBuf)> {
    fs::read_dir(path)
        .unwrap()
        .map(Result::unwrap)
        .filter(|e| e.file_type().unwrap().is_dir())
        .map(|e| {
            (
                semver::Version::parse(&e.file_name().into_string().unwrap()).unwrap(),
                e.path(),
            )
        })
        .collect()
}

fn generate_docs() -> (semver::Version, path::PathBuf) {
    // TODO: check which rustc/rustdoc $CARGO is using
    let version = rustc_version::version().unwrap();
    let mut path = tempfile::tempdir().unwrap().into_path();
    process::Command::new(env::var_os("CARGO").unwrap())
        .arg("doc")
        .args(&["--package", "anyhow"])
        .args(&["--package", "kuchiki"])
        .args(&["--package", "log"])
        .args(&["--package", "rand_core"])
        .arg("--target-dir")
        .arg(&path)
        .arg("--no-deps")
        .output()
        .unwrap();
    path.push("doc");
    (version, path)
}

/// Calls the given function with all paths to rustdoc documentation generated by rustdoc versions
/// that match the given requirement string.
pub fn with_rustdoc<F>(version: &str, f: F)
where
    F: Fn(&semver::Version, &path::Path),
{
    let version_req = semver::VersionReq::parse(version).unwrap();
    let versions = get_versions();
    for (version, path) in versions {
        if version_req.matches(&version) {
            log::warn!(
                "Executing test case for version {} at {}",
                version,
                path.display()
            );
            f(version, &path);
        }
    }
}