diff --git a/Cargo.lock b/Cargo.lock index 72ce64d..3667b6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "ansi_term" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index ea04ea1..644750f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ description = "starts an embedded http server for your cargo doc output" homepage = "https://github.com/qmx/cargo-docserver" repository = "https://github.com/qmx/cargo-docserver" license = "MIT/Apache-2.0" +edition = "2018" [dependencies] structopt = "0.2.15" diff --git a/README.md b/README.md index 70435d5..a3b63dc 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ This is just the result of me fooling around with what would be the minimal HTTP `cargo docserver -p ` +Additionally you can compile the docs while the server is running by pressing ENTER. You can pass arguments to `cargo doc` with `-r "--some-arg"`. For example `cargo docserver -r "--no-deps -j 2"` + ### sidenote diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..7d2cf54 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +merge_imports = true diff --git a/src/main.rs b/src/main.rs index 92c907c..b755b1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,10 @@ -extern crate structopt; - -extern crate cargo_metadata; -extern crate hyper; - -extern crate futures; -extern crate mime_guess; -extern crate tokio_fs; -extern crate tokio_io; -use hyper::service::service_fn; -use hyper::{Body, Method, Request, Response, Server, StatusCode}; -use std::io; -use std::path::{Path, PathBuf}; -use std::str; +use hyper::{service::service_fn, Body, Method, Request, Response, Server, StatusCode}; +use std::{ + io, + path::{Path, PathBuf}, + process::Command, + str, thread, +}; use structopt::StructOpt; use futures::{future, Future}; @@ -23,10 +16,14 @@ enum Cargo { Docserver { #[structopt(short = "p", long = "port", default_value = "4000")] port: u16, + + #[structopt(long, short, allow_hyphen_values = true)] + /// The arguments that will be sent to `cargo doc` when recompiling the docs. + recompile_args: Option, }, } -type ResponseFuture = Box, Error = io::Error> + Send>; +type ResponseFuture = Box, Error = io::Error> + Send>; #[derive(Debug)] struct CrateInfo { @@ -40,29 +37,18 @@ impl CrateInfo { let package_name = &meta.packages[0].name; let package_name_sanitized = str::replace(&package_name, "-", "_"); let doc_path = Path::new(&meta.target_directory).join("doc"); + CrateInfo { - name: package_name_sanitized.clone(), - doc_path: doc_path.clone(), + name: package_name_sanitized, + doc_path, } } } -#[test] -fn test_make_relative() { - assert_eq!("foo/bar/baz", make_relative("/foo/bar/baz")); - assert_eq!("foo/bar/baz", make_relative("///foo/bar/baz")); -} - -#[test] -fn test_make_root_document() { - assert_eq!("/foo/hello/index.html", make_index("/foo/hello")); - assert_eq!("/foo/hello/index.html", make_index("/foo/hello/")); - assert_eq!("/foo/hello.foo", make_index("/foo/hello.foo")); -} - fn make_index(path: &str) -> String { - let sanitized_path = path.trim_end_matches("/"); - if sanitized_path.contains(".") { + let sanitized_path = path.trim_end_matches('/'); + + if sanitized_path.contains('.') { sanitized_path.to_string() } else { format!("{}/index.html", sanitized_path) @@ -70,7 +56,7 @@ fn make_index(path: &str) -> String { } fn make_relative(path: &str) -> String { - path.trim_start_matches("/").to_string() + path.trim_start_matches('/').to_string() } fn serve_docs(req: Request) -> ResponseFuture { @@ -127,17 +113,82 @@ fn not_found() -> ResponseFuture { )) } +fn setup_recompilation_watcher(recompile_args: Option) { + thread::spawn(move || { + let mut input = String::new(); + + println!("Press ENTER to recompile the docs"); + loop { + match io::stdin().read_line(&mut input) { + Ok(_) => { + compile_docs(&recompile_args); + } + Err(error) => eprintln!("Error reading from stdin: {}", error), + } + } + }); +} + +fn compile_docs(recompile_args: &Option) { + let recompile_args = into_command_args(&recompile_args); + + println!( + "Compiling docs with `cargo doc {}`", + recompile_args.join(" ") + ); + + let mut child = Command::new("cargo") + .args(&["doc"]) + .args(recompile_args) + .spawn() + .expect("failed to compile docs"); + + child.wait().expect("failed to compile docs"); +} + +fn into_command_args(args: &Option) -> Vec<&str> { + match args.as_ref() { + Some(args) => args.split(' ').collect(), + None => Vec::new(), + } +} + fn main() { match Cargo::from_args() { - Cargo::Docserver { port } => { + Cargo::Docserver { + port, + recompile_args, + } => { let addr = ([0, 0, 0, 0], port).into(); let svc = || service_fn(serve_docs); let server = Server::bind(&addr) .serve(svc) .map_err(|e| eprintln!("server error {}", e)); + compile_docs(&recompile_args); + setup_recompilation_watcher(recompile_args); + println!("Listening on http://{}", addr); hyper::rt::run(server); } } } + +#[cfg(test)] +mod test { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_make_relative() { + assert_eq!("foo/bar/baz", make_relative("/foo/bar/baz")); + assert_eq!("foo/bar/baz", make_relative("///foo/bar/baz")); + } + + #[test] + fn test_make_root_document() { + assert_eq!("/foo/hello/index.html", make_index("/foo/hello")); + assert_eq!("/foo/hello/index.html", make_index("/foo/hello/")); + assert_eq!("/foo/hello.foo", make_index("/foo/hello.foo")); + } +}