1use cargo::core::compiler::{CompileMode, Executor};
2use cargo::core::{PackageId, Shell, Target, Verbosity, Workspace};
3use cargo::ops::{compile_with_exec, CompileOptions};
4use cargo::util::errors::CargoResult;
5use cargo::util::{homedir, GlobalContext};
6use cargo_util::ProcessBuilder;
7use http::response::Builder as ResponseBuilder;
8use http::{header, StatusCode};
9use hyper::server::conn::http1;
10use hyper::service::service_fn;
11use hyper::{Request, Response};
12use hyper_staticfile::Body;
13use hyper_staticfile::Static;
14use hyper_util::rt::tokio::TokioIo;
15use std::path::PathBuf;
16use std::sync::Arc;
17use tokio::net::TcpListener;
18
19#[allow(dead_code)]
21pub async fn run_cargo_doc(args: &Vec<String>) -> std::process::ExitStatus {
22 let mut cmd = tokio::process::Command::new("cargo");
25 cmd.arg("doc").args(args);
26 let stdcmd = cmd.as_std();
27 log::info!(
28 "Running {} {}",
29 stdcmd.get_program().to_string_lossy(),
30 stdcmd
31 .get_args()
32 .map(|s| s.to_string_lossy().to_string())
33 .collect::<Vec<String>>()
34 .join(" ")
35 );
36 let mut child = tokio::process::Command::new("cargo")
37 .arg("doc")
38 .args(args)
39 .spawn()
40 .expect("failed to run `cargo doc`");
41 child.wait().await.expect("failed to wait")
42}
43
44#[allow(dead_code)]
48pub async fn handle_crate_request<B>(
49 req: Request<B>,
50 static_: Static,
51 crate_name: String,
52) -> Result<Response<Body>, std::io::Error> {
53 let target = if let Some(query) = req.uri().query() {
54 format!("/{crate_name}/?{query}")
55 } else {
56 format!("/{crate_name}/")
57 };
58 match req.uri().path() {
59 "/" => Ok(ResponseBuilder::new()
60 .status(StatusCode::FOUND)
61 .header(header::LOCATION, target)
62 .body(Body::Empty)
63 .expect("unable to build response")),
64 _ => static_.clone().serve(req).await,
65 }
66}
67
68#[allow(dead_code)]
70pub async fn serve_rust_doc(addr: &std::net::SocketAddr) -> Result<(), anyhow::Error> {
71 Ok(serve_rustbook(addr).await?)
72}
73
74#[allow(dead_code)]
76pub fn get_crate_info(manifest_path: &PathBuf) -> Result<(String, PathBuf), anyhow::Error> {
77 let mut shell = Shell::default();
78 shell.set_verbosity(Verbosity::Quiet);
79 let cwd = std::env::current_dir()?;
80 let cargo_home_dir = homedir(&cwd).expect("Errror locating homedir");
81 let config = GlobalContext::new(shell, cwd, cargo_home_dir);
82 let workspace = Workspace::new(manifest_path, &config).expect("Error making workspace");
83
84 let mut compile_opts = CompileOptions::new(
85 &config,
86 CompileMode::Doc {
87 deps: true,
88 json: false,
89 },
90 )
91 .expect("Making CompileOptions");
92
93 compile_opts.spec = cargo::ops::Packages::Default;
98
99 #[derive(Copy, Clone)]
104 struct DefaultExecutor;
105
106 impl Executor for DefaultExecutor {
107 fn exec(
108 &self,
109 _cmd: &ProcessBuilder,
110 _id: PackageId,
111 _target: &Target,
112 _mode: CompileMode,
113 _on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
114 _on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
115 ) -> CargoResult<()> {
116 Ok(())
118 }
119 }
120
121 let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
122 let compilation = compile_with_exec(&workspace, &compile_opts, &exec)?;
123 let root_crate_names = &compilation.root_crate_names;
124 let crate_doc_dir = workspace.target_dir().join("doc").into_path_unlocked();
125 let crate_name = root_crate_names
126 .get(0)
127 .ok_or_else(|| anyhow::anyhow!("no crates with documentation"))?;
128 Ok((crate_name.to_string(), crate_doc_dir))
129}
130
131#[allow(dead_code)]
133pub async fn serve_crate_doc(
134 manifest_path: &PathBuf,
135 addr: &std::net::SocketAddr,
136) -> Result<(), anyhow::Error> {
137 let (crate_name, crate_doc_dir) = get_crate_info(manifest_path)?;
138 let crate_doc_dir = Static::new(crate_doc_dir.clone());
139 let crate_name = crate_name.clone();
140 let handler =
141 service_fn(move |req| handle_crate_request(req, crate_doc_dir.clone(), crate_name.clone()));
142
143 let listener = TcpListener::bind(addr)
144 .await
145 .expect("Failed to create TCP listener");
146
147 loop {
148 let (tcp, _) = listener.accept().await?;
149 let io = TokioIo::new(tcp);
150 let service = handler.clone();
151 tokio::task::spawn(async move {
152 if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
153 println!("Failed to serve connection: {:?}", err);
154 }
155 });
156 }
157}
158
159pub fn find_rustdoc() -> Option<PathBuf> {
163 let output = std::process::Command::new("rustup")
164 .arg("which")
165 .arg("rustdoc")
166 .output()
167 .ok()?;
168 if output.status.success() {
169 Some(PathBuf::from(String::from_utf8(output.stdout).ok()?))
170 } else {
171 None
172 }
173 .and_then(|rustdoc| {
174 Some(
175 rustdoc
176 .parent()?
177 .parent()?
178 .join("share")
179 .join("doc")
180 .join("rust")
181 .join("html"),
182 )
183 })
184}
185
186#[allow(dead_code)]
190pub async fn handle_request<B>(
191 req: Request<B>,
192 static_: Static,
193) -> Result<Response<Body>, std::io::Error> {
194 static_.clone().serve(req).await
195}
196
197#[allow(dead_code)]
199pub async fn serve_rustbook(addr: &std::net::SocketAddr) -> Result<(), anyhow::Error> {
200 let rustdoc_dir = find_rustdoc().expect("Error locating rustdoc");
201 Ok(serve_dir(&rustdoc_dir, addr).await?)
202}
203
204#[allow(dead_code)]
206pub async fn serve_dir(dir: &PathBuf, addr: &std::net::SocketAddr) -> Result<(), anyhow::Error> {
207 let dir = Static::new(dir.clone());
208 let handler = service_fn(move |req| handle_request(req, dir.clone()));
209
210 let listener = TcpListener::bind(addr)
211 .await
212 .expect("Failed to create TCP listener");
213
214 loop {
215 let (tcp, _) = listener.accept().await?;
216 let io = TokioIo::new(tcp);
217 let service = handler.clone();
218 tokio::task::spawn(async move {
219 if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
220 println!("Failed to serve connection: {:?}", err);
221 }
222 });
223 }
224}