Initial commit
This commit is contained in:
commit
4b5ab8f0ac
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
3428
Cargo.lock
generated
Normal file
3428
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "opendocs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.75"
|
||||||
|
axum = { version = "0.6.20", features = ["tracing"] }
|
||||||
|
cargo = "0.73.1"
|
||||||
|
clap = { version = "4.4.4", features = ["derive"] }
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
serde_json = "1.0.107"
|
||||||
|
tokio = { version = "1.32.0", features = ["net", "rt", "macros", "rt-multi-thread", "process", "io-util"] }
|
||||||
|
tower = "0.4.13"
|
||||||
|
tower-http = { version = "0.4.4", features = ["fs", "tracing", "trace"] }
|
||||||
|
tracing = "0.1.37"
|
||||||
|
tracing-subscriber = "0.3.17"
|
83
index.html
Normal file
83
index.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Rust Docs</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<!-- Make the device look good on phones -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#links {
|
||||||
|
overflow-x: scroll;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
position:fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 2.5rem);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="links">
|
||||||
|
<a href="std/std/" id="std-docs" target="main" onclick="focusFrame()">STD</a>
|
||||||
|
<a href="local/tracing/" id="local-docs" target="main" onclick="focusFrame()">Cargo</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<iframe name="main" id="main" src="local/tracing/"></iframe>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
let links = {
|
||||||
|
"std": document.getElementById("std-docs"),
|
||||||
|
"local": document.getElementById("local-docs"),
|
||||||
|
};
|
||||||
|
let frame = document.getElementById("main");
|
||||||
|
let lastLoaded = "local";
|
||||||
|
|
||||||
|
function toggleLastLoaded() {
|
||||||
|
if (lastLoaded === "std") {
|
||||||
|
lastLoaded = "local";
|
||||||
|
} else {
|
||||||
|
lastLoaded = "std";
|
||||||
|
}
|
||||||
|
links[lastLoaded].click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusFrame() {
|
||||||
|
frame.contentWindow.focus();
|
||||||
|
frame.addEventListener("load", function() {
|
||||||
|
if (this.contentWindow.window.searchState) {
|
||||||
|
this.contentWindow.window.searchState.focus();
|
||||||
|
}
|
||||||
|
frame.contentWindow.removeEventListener("keydown", cl);
|
||||||
|
frame.contentWindow.addEventListener("keydown", cl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cl(e) {
|
||||||
|
if ((e.which == 219 || e.which == 221) && !e.ctrlKey && e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleLastLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focusFrame();
|
||||||
|
</script>
|
||||||
|
</html>
|
187
src/main.rs
Normal file
187
src/main.rs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
#![feature(try_blocks)]
|
||||||
|
use anyhow::bail;
|
||||||
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::http::header::CONTENT_SECURITY_POLICY;
|
||||||
|
use axum::http::header::X_FRAME_OPTIONS;
|
||||||
|
use axum::http::HeaderMap;
|
||||||
|
use axum::response::Html;
|
||||||
|
use axum::routing::get;
|
||||||
|
use clap::Parser;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::os::unix::prelude::OsStringExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Stdio;
|
||||||
|
use std::{net::SocketAddr, str::FromStr};
|
||||||
|
use tokio::io::AsyncBufReadExt;
|
||||||
|
use tokio::io::BufReader;
|
||||||
|
use tokio::process::Command;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(default_values = ["--all"])]
|
||||||
|
doc_args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(tracing::Level::INFO)
|
||||||
|
.with_writer(std::io::stderr)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
info!("Getting standard docs path");
|
||||||
|
let std_docs_path = get_std_docs_path().await?;
|
||||||
|
info!("Found: {std_docs_path:?}");
|
||||||
|
info!("Building docs...");
|
||||||
|
let build_docs_path = build_docs().await?;
|
||||||
|
info!("Done: {build_docs_path:?}");
|
||||||
|
|
||||||
|
start_http(std_docs_path, build_docs_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_http(std_docs_path: PathBuf, build_docs_path: PathBuf) -> Result<()> {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(get_index))
|
||||||
|
.nest_service("/local", ServeDir::new(build_docs_path))
|
||||||
|
.nest_service("/std", ServeDir::new(std_docs_path))
|
||||||
|
.layer(TraceLayer::new_for_http());
|
||||||
|
|
||||||
|
let addr = SocketAddr::from_str("127.0.0.1:8888").unwrap();
|
||||||
|
info!("Listening on address {}", addr);
|
||||||
|
axum::Server::bind(&addr)
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_index() -> (HeaderMap, Html<&'static [u8]>) {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(X_FRAME_OPTIONS, "SAMEORIGIN".parse().unwrap());
|
||||||
|
headers.insert(CONTENT_SECURITY_POLICY, "child-src 'self'".parse().unwrap());
|
||||||
|
(headers, Html(include_bytes!("../index.html")))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn build_docs() -> Result<PathBuf> {
|
||||||
|
// let options = DocOptions {
|
||||||
|
// open_result: false,
|
||||||
|
// compile_opts: CompileOptions {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut child = Command::new("cargo")
|
||||||
|
.args(["doc", "--keep-going", "--message-format=json"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.spawn()
|
||||||
|
.context("Could not spawn cargo doc")?;
|
||||||
|
|
||||||
|
let stdout = child
|
||||||
|
.stdout
|
||||||
|
.take()
|
||||||
|
.context("Stdout could not be taken from Command")?;
|
||||||
|
let join_handle: JoinHandle<Result<Result<PathBuf>>> = tokio::spawn(async move {
|
||||||
|
let mut lines = BufReader::new(stdout).lines();
|
||||||
|
let result = try {
|
||||||
|
let mut latest_artifact = None;
|
||||||
|
|
||||||
|
while let Some(line) = lines.next_line().await? {
|
||||||
|
let response = serde_json::from_str::<CargoDocLine>(&line)
|
||||||
|
.context("Failed to deserialize cargo output")?;
|
||||||
|
|
||||||
|
// info!("Deserialized response: {response:?}");
|
||||||
|
match response {
|
||||||
|
CargoDocLine::CompilerArtifact { filenames } => {
|
||||||
|
// info!("Got artifacts: {filenames:#?}");
|
||||||
|
latest_artifact = filenames
|
||||||
|
.into_iter()
|
||||||
|
.find(|a| a.file_name() == Some(OsStr::new("index.html")))
|
||||||
|
.or(latest_artifact);
|
||||||
|
// info!("Latest artifact: {latest_artifact:?}");
|
||||||
|
}
|
||||||
|
CargoDocLine::BuildFinished { success: false } => {
|
||||||
|
// TODO: Maybe we should stop on some failures
|
||||||
|
|
||||||
|
return Ok(latest_artifact.context("No artifacts generated"));
|
||||||
|
|
||||||
|
// bail!("Cargo doc did not complete successfully");
|
||||||
|
}
|
||||||
|
CargoDocLine::BuildFinished { success: true } => {
|
||||||
|
return Ok(latest_artifact.context("No artifacts generated"));
|
||||||
|
}
|
||||||
|
CargoDocLine::BuildScriptExecuted(_) | CargoDocLine::CompilerMessage(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Never got build-finished reason")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Continue reading stdout so cargo doc doesn't crash
|
||||||
|
// Errors can be ignored; this is just for draining stdout
|
||||||
|
while let Ok(Some(_line)) = lines.next_line().await {}
|
||||||
|
|
||||||
|
result
|
||||||
|
});
|
||||||
|
|
||||||
|
let _status = child.wait().await.context("Cargo doc runtime error")?;
|
||||||
|
|
||||||
|
let output_path = join_handle
|
||||||
|
.await
|
||||||
|
.context("Failed to join reader handle")???;
|
||||||
|
// info!("with output path: {:?}", output_path);
|
||||||
|
|
||||||
|
let output_path = output_path
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.parent())
|
||||||
|
.flatten()
|
||||||
|
.context("Crate docs directory is invalid")?;
|
||||||
|
|
||||||
|
if !output_path.is_dir() {
|
||||||
|
bail!("Crate docs directory does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output_path.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(tag = "reason")]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
enum CargoDocLine {
|
||||||
|
CompilerArtifact { filenames: Vec<PathBuf> },
|
||||||
|
BuildFinished { success: bool },
|
||||||
|
BuildScriptExecuted(Value),
|
||||||
|
CompilerMessage(Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_std_docs_path() -> Result<PathBuf> {
|
||||||
|
// rustup docs --path --std
|
||||||
|
let path = PathBuf::from(OsString::from_vec(
|
||||||
|
Command::new("rustup")
|
||||||
|
.args(["docs", "--path", "--std"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.context("Could not spawn cargo doc")?
|
||||||
|
.stdout,
|
||||||
|
));
|
||||||
|
let path = path
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.parent())
|
||||||
|
.flatten()
|
||||||
|
.context("std docs directory is invalid")?;
|
||||||
|
|
||||||
|
if !path.is_dir() {
|
||||||
|
bail!("std docs directory does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(path.to_owned())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user