rustdoc-llms 0.4.0

Rust documentation helper to generate file llms.txt to help training AI large language models (LLMs).
//! # rustdoc-llms
//!
//! Rust documentation helper that helps a Rust crate developer generate a file
//! `llms.txt` that helps provide context to large language models (LLMs).
//!
//! Thanks to excellent work by the Rust team, rustdoc team, and rustdoc-md team.
//!
//! ## How to use this
//!
//! Install:
//!
//! ```sh
//! cargo install rustdoc-llms
//! ```
//!
//! Use the tool when you're working on a crate and you want to document it:
//!
//! ```sh
//! rustdoc-llms
//! ```
//!
//! The tool creates two files:
//!
//! * `target/doc/foo_bar.json` (this uses your own crate naming convention)
//!
//! * `target/doc/llms.txt` (this is a markdown file created by rustdoc-md)
//!
//! If you like, you can copy these files to the top level of your repository, which
//! can make the files easier to find for search engines and AI systems:
//!
//! ```sh
//! cp target/doc/foo_bar.json llms.json
//! cp target/doc/llms.txt llms.txt
//! ```
//!
//! ## Scope
//!
//! This Rust crate is deliberately small and simple. The purpose is to make it
//! slightly-easier to generate `llms` based on what's currently easy and available
//! for Rust crate developers.
//!
//! Currently this tool calls existing command line interfaces, using `cargo` and
//! `rustdoc-md` with specialized compiler flags. If this tool is useful in
//! practice, then I intend to iterate to add command line arguments, options,
//! tests, and the like.
//!
//! ## Contributing
//!
//! Pull requests are welcome. Issues are welcome.
//!
//! If you want to help in other ways, or provide constructive criticism, or ask
//! questions directly, then please feel free to contact me. I'm Joel Parker
//! Henderson and my email is <joel@joelparkerhenderson.com>.
//!
//! ## What this does
//!
//! This implements the help description [here](https://crates.io/crates/rustdoc-md).
//!
//! Step 1: Generate JSON documentation:
//!
//! ```sh
//! RUSTC_BOOTSTRAP=1 RUSTDOCFLAGS="-Z unstable-options --output-format json" cargo doc --no-deps
//! ```
//!
//! Step 2: Convert from JSON into Markdown:
//!
//! ```sh
//! rustdoc-md --path target/doc/foo_bar.json --output target/doc/foo_bar.md
//! ```
//!
//! Step 3: Copy from Markdown file into LLMs file:
//!
//! ```sh
//! cp target/doc/foo_bar.json llms.json
//! cp target/doc/foo_bar.md llms.txt
//! ```

use std::process::Command;
use std::path::{Path, PathBuf};

mod cargo_helpers;

/// Run this command to generate the documentation LLMS file.
///
/// Example:
///
/// ```sh
/// rustdoc-llms
/// ```
///
/// You may wish to copy the output files to the root of your crate:
///
/// ```sh
/// cp target/doc/foo_bar.json llms.json
/// cp target/doc/llms.txt llms.txt
/// ```
///
fn main() {
    let documentation_json_path = documentation_json_path();
    let documentation_llms_path = documentation_llms_path();
    generate_documentation_json_file();
    generate_documentation_llms_file(documentation_json_path, documentation_llms_path);
}

/// Get the path to the file `Cargo.toml`.
pub fn cargo_toml_path() -> PathBuf {
    PathBuf::from("Cargo.toml")
}

/// Get the target JSON file base name; this file is what cargo doc generates.
/// The name is the crate's lib target's name with all - chars turned into _.
///
/// Example:
///
/// ```rust
/// use rustdoc_llms::*;
/// let path = rustdoc_md::documentation_json_path();
/// ```
///
pub fn documentation_json_path() -> PathBuf {
    let lib_name = crate::cargo_helpers::lib_name(cargo_toml_path()).expect("lib_name");
    PathBuf::from(format!("target/doc/{}.json", str::replace(&lib_name, "-", "_")))
}

/// This is the name of the goal LLMS file that will be generated by rustdoc-md.
/// The industry standard file name is `llms.txt` though you can use anything.
///
/// Example:
///
/// ```rust
/// use rustdoc_llms::*;
/// let path = rustdoc_md::documentation_llms_path();
/// ```
///
pub fn documentation_llms_path() -> PathBuf {
    PathBuf::from("target/doc/llms.txt")
}

/// Run cargo doc with args to output one JSON file with all the combined documentation.
///
/// Example:
///
/// ```rust
/// use rustdoc_llms::*;
/// let path = rustdoc_md::generate_documentation_json_file();
/// ```
///
pub fn generate_documentation_json_file() {
    let path = documentation_json_path();
    println!("Generate documentation JSON file: {}", path.display());
    let _output = Command::new("cargo")
        .env("RUSTC_BOOTSTRAP", "1")
        .env("RUSTDOCFLAGS", "-Z unstable-options --output-format json")
        .arg("doc")
        .arg("--no-deps")
        .output()
    .expect("command failed to start");
    // println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    // println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

/// Run rustdoc-md with an input file path and output file path.
///
/// Example:
///
/// ```rust
/// use rustdoc_llms::*;
/// let input_json_path = rustdoc_md::documentation_json_path();
/// let output_llms_path = rustdoc_md::documentation_llms_path();
/// let path = rustdoc_md::generate_documentation_llms_file(input_json_path, output_llms_path);
/// ```
///
pub fn generate_documentation_llms_file(input_json_path: impl AsRef<Path>, output_llms_path: impl AsRef<Path>) {
    println!("Generate documentation LLMS file: {}", output_llms_path.as_ref().display());
    let _output = Command::new("rustdoc-md")
        .arg("--path")
        .arg(input_json_path.as_ref())
        .arg("--output")
        .arg(output_llms_path.as_ref())
        .output()
        .expect("command failed to start");
    // println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    // println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}