Skip to content
This repository was archived by the owner on May 28, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This is just the result of me fooling around with what would be the minimal HTTP

`cargo docserver -p <port>`

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

Expand Down
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
merge_imports = true
119 changes: 85 additions & 34 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<String>,
},
}

type ResponseFuture = Box<Future<Item = Response<Body>, Error = io::Error> + Send>;
type ResponseFuture = Box<dyn Future<Item = Response<Body>, Error = io::Error> + Send>;

#[derive(Debug)]
struct CrateInfo {
Expand All @@ -40,37 +37,26 @@ 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)
}
}

fn make_relative(path: &str) -> String {
path.trim_start_matches("/").to_string()
path.trim_start_matches('/').to_string()
}

fn serve_docs(req: Request<Body>) -> ResponseFuture {
Expand Down Expand Up @@ -127,17 +113,82 @@ fn not_found() -> ResponseFuture {
))
}

fn setup_recompilation_watcher(recompile_args: Option<String>) {
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<String>) {
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<String>) -> 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"));
}
}