1 use clap::crate_version;
4 use std::path::{Path, PathBuf};
6 use clap::{App, AppSettings, ArgMatches, SubCommand};
8 use mdbook::errors::Result as Result3;
12 let d_message = "-d, --dest-dir=[dest-dir]
13 'The output directory for your book{n}(Defaults to ./book when omitted)'";
14 let dir_message = "[dir]
15 'A directory for your book{n}(Defaults to Current Directory when omitted)'";
17 let matches = App::new("rustbook")
18 .about("Build a book with mdBook")
19 .author("Steve Klabnik <steve@steveklabnik.com>")
20 .version(&*format!("v{}", crate_version!()))
21 .setting(AppSettings::SubcommandRequired)
23 SubCommand::with_name("build")
24 .about("Build the book from the markdown files")
25 .arg_from_usage(d_message)
26 .arg_from_usage(dir_message),
29 SubCommand::with_name("test")
30 .about("Tests that a book's Rust code samples compile")
31 .arg_from_usage(dir_message),
34 SubCommand::with_name("linkcheck")
35 .about("Run linkcheck with mdBook 3")
36 .arg_from_usage(dir_message),
40 // Check which subcomamnd the user ran...
41 match matches.subcommand() {
42 ("build", Some(sub_matches)) => {
43 if let Err(e) = build(sub_matches) {
47 ("test", Some(sub_matches)) => {
48 if let Err(e) = test(sub_matches) {
52 ("linkcheck", Some(sub_matches)) => {
53 #[cfg(feature = "linkcheck")]
55 let (diags, files) = linkcheck(sub_matches).expect("Error while linkchecking.");
56 if !diags.is_empty() {
57 let color = codespan_reporting::term::termcolor::ColorChoice::Auto;
59 codespan_reporting::term::termcolor::StandardStream::stderr(color);
60 let cfg = codespan_reporting::term::Config::default();
63 codespan_reporting::term::emit(&mut writer, &cfg, &files, &diag)
64 .expect("Unable to emit linkcheck error.");
67 std::process::exit(101);
71 #[cfg(not(feature = "linkcheck"))]
73 // This avoids the `unused_binding` lint.
75 "mdbook-linkcheck is disabled, but arguments were passed: {:?}",
80 (_, _) => unreachable!(),
84 #[cfg(feature = "linkcheck")]
86 args: &ArgMatches<'_>,
87 ) -> Result<(Vec<codespan_reporting::diagnostic::Diagnostic>, codespan::Files), failure::Error> {
88 use mdbook_linkcheck::Reason;
90 let book_dir = get_book_dir(args);
91 let src_dir = book_dir.join("src");
92 let book = MDBook::load(&book_dir).unwrap();
93 let linkck_cfg = mdbook_linkcheck::get_config(&book.config)?;
94 let mut files = codespan::Files::new();
95 let target_files = mdbook_linkcheck::load_files_into_memory(&book.book, &mut files);
96 let cache = mdbook_linkcheck::Cache::default();
98 let (links, incomplete) = mdbook_linkcheck::extract_links(target_files, &files);
101 mdbook_linkcheck::validate(&links, &linkck_cfg, &src_dir, &cache, &files, incomplete)?;
103 let mut is_real_error = false;
105 for link in outcome.invalid_links.iter() {
107 Reason::FileNotFound | Reason::TraversesParentDirectories => {
108 is_real_error = true;
110 Reason::UnsuccessfulServerResponse(status) => {
111 if status.is_client_error() {
112 is_real_error = true;
114 eprintln!("Unsuccessful server response for link `{}`", link.link.uri);
117 Reason::Client(err) => {
118 if err.is_timeout() {
119 eprintln!("Timeout for link `{}`", link.link.uri);
120 } else if err.is_server_error() {
121 eprintln!("Server error for link `{}`", link.link.uri);
122 } else if !err.is_http() {
123 eprintln!("Non-HTTP-related error for link: {} {}", link.link.uri, err);
125 is_real_error = true;
132 Ok((outcome.generate_diagnostics(&files, linkck_cfg.warning_policy), files))
138 // Build command implementation
139 pub fn build(args: &ArgMatches<'_>) -> Result3<()> {
140 let book_dir = get_book_dir(args);
141 let mut book = MDBook::load(&book_dir)?;
143 // Set this to allow us to catch bugs in advance.
144 book.config.build.create_missing = false;
146 if let Some(dest_dir) = args.value_of("dest-dir") {
147 book.config.build.build_dir = PathBuf::from(dest_dir);
155 fn test(args: &ArgMatches<'_>) -> Result3<()> {
156 let book_dir = get_book_dir(args);
157 let mut book = MDBook::load(&book_dir)?;
161 fn get_book_dir(args: &ArgMatches<'_>) -> PathBuf {
162 if let Some(dir) = args.value_of("dir") {
163 // Check if path is relative from current dir, or absolute...
164 let p = Path::new(dir);
165 if p.is_relative() { env::current_dir().unwrap().join(dir) } else { p.to_path_buf() }
167 env::current_dir().unwrap()
171 fn handle_error(error: mdbook::errors::Error) -> ! {
172 eprintln!("Error: {}", error);
174 for cause in error.iter().skip(1) {
175 eprintln!("\tCaused By: {}", cause);
178 ::std::process::exit(101);