use std::{fs, io, path::PathBuf};
use clap::{ArgGroup, Parser};
use eyre::bail;
use rustdoc_md::rustdoc_json_to_markdown;
use rustdoc_types::Crate;
use ureq::http::StatusCode;
use zstd::decode_all;
#[derive(Parser)]
#[command(version, about, long_about = None)]
#[command(group(
ArgGroup::new("input")
.required(true)
.args(&["path", "crate_name"]),
))]
struct Cli {
#[arg(short, long)]
path: Option<PathBuf>,
#[arg(long)]
crate_name: Option<String>,
#[arg(long, default_value = "latest", requires = "crate_name")]
crate_version: String,
#[arg(
long,
default_value = "x86_64-unknown-linux-gnu",
requires = "crate_name"
)]
target: String,
#[arg(short, long)]
output: PathBuf,
}
fn main() -> eyre::Result<()> {
let cli = Cli::parse();
let data: Crate = if let Some(path) = cli.path {
let file = fs::File::open(path)?;
let reader = io::BufReader::new(file);
serde_json::from_reader(reader)?
} else if let Some(crate_name) = cli.crate_name {
let url = format!(
"https://docs.rs/crate/{crate_name}/{}/{}/json",
cli.crate_version, cli.target
);
let resp = ureq::get(&url)
.header(
"user-agent",
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")),
)
.call()?;
let status = resp.status();
if !status.is_success() {
match status {
StatusCode::NOT_FOUND => {
bail!("crate or version not found, or doesn't provide rustdocs as json");
}
_ => {
bail!("failed to fetch crate json: {status}");
}
}
}
let reader = resp.into_body().into_reader();
let body = decode_all(reader)?;
serde_json::from_reader(body.as_slice())?
} else {
unreachable!("neither --path nor --crate-name set");
};
let md = rustdoc_json_to_markdown(data);
fs::write(&cli.output, md)?;
println!("successfully wrote to file {}", cli.output.display());
Ok(())
}