1 #[macro_use]
2 extern crate clap;
3 #[macro_use]
4 extern crate log;
5
6 use anyhow::anyhow;
7 use chrono::Local;
8 use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand};
9 use env_logger::Builder;
10 use log::LevelFilter;
11 use mdbook::utils;
12 use std::env;
13 use std::ffi::OsStr;
14 use std::io::Write;
15 use std::path::{Path, PathBuf};
16
17 mod cmd;
18
19 const VERSION: &str = concat!("v", crate_version!());
20
main()21 fn main() {
22 init_logger();
23
24 let app = create_clap_app();
25
26 // Check which subcomamnd the user ran...
27 let res = match app.get_matches().subcommand() {
28 ("init", Some(sub_matches)) => cmd::init::execute(sub_matches),
29 ("build", Some(sub_matches)) => cmd::build::execute(sub_matches),
30 ("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches),
31 #[cfg(feature = "watch")]
32 ("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches),
33 #[cfg(feature = "serve")]
34 ("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches),
35 ("test", Some(sub_matches)) => cmd::test::execute(sub_matches),
36 ("completions", Some(sub_matches)) => (|| {
37 let shell: Shell = sub_matches
38 .value_of("shell")
39 .ok_or_else(|| anyhow!("Shell name missing."))?
40 .parse()
41 .map_err(|s| anyhow!("Invalid shell: {}", s))?;
42
43 create_clap_app().gen_completions_to("mdbook", shell, &mut std::io::stdout().lock());
44 Ok(())
45 })(),
46 (_, _) => unreachable!(),
47 };
48
49 if let Err(e) = res {
50 utils::log_backtrace(&e);
51
52 std::process::exit(101);
53 }
54 }
55
56 /// Create a list of valid arguments and sub-commands
create_clap_app<'a, 'b>() -> App<'a, 'b>57 fn create_clap_app<'a, 'b>() -> App<'a, 'b> {
58 let app = App::new(crate_name!())
59 .about(crate_description!())
60 .author("Mathieu David <mathieudavid@mathieudavid.org>")
61 .version(VERSION)
62 .setting(AppSettings::GlobalVersion)
63 .setting(AppSettings::ArgRequiredElseHelp)
64 .setting(AppSettings::ColoredHelp)
65 .after_help(
66 "For more information about a specific command, try `mdbook <command> --help`\n\
67 The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
68 )
69 .subcommand(cmd::init::make_subcommand())
70 .subcommand(cmd::build::make_subcommand())
71 .subcommand(cmd::test::make_subcommand())
72 .subcommand(cmd::clean::make_subcommand())
73 .subcommand(
74 SubCommand::with_name("completions")
75 .about("Generate shell completions for your shell to stdout")
76 .arg(
77 Arg::with_name("shell")
78 .takes_value(true)
79 .possible_values(&Shell::variants())
80 .help("the shell to generate completions for")
81 .value_name("SHELL")
82 .required(true),
83 ),
84 );
85
86 #[cfg(feature = "watch")]
87 let app = app.subcommand(cmd::watch::make_subcommand());
88 #[cfg(feature = "serve")]
89 let app = app.subcommand(cmd::serve::make_subcommand());
90
91 app
92 }
93
init_logger()94 fn init_logger() {
95 let mut builder = Builder::new();
96
97 builder.format(|formatter, record| {
98 writeln!(
99 formatter,
100 "{} [{}] ({}): {}",
101 Local::now().format("%Y-%m-%d %H:%M:%S"),
102 record.level(),
103 record.target(),
104 record.args()
105 )
106 });
107
108 if let Ok(var) = env::var("RUST_LOG") {
109 builder.parse_filters(&var);
110 } else {
111 // if no RUST_LOG provided, default to logging at the Info level
112 builder.filter(None, LevelFilter::Info);
113 // Filter extraneous html5ever not-implemented messages
114 builder.filter(Some("html5ever"), LevelFilter::Error);
115 }
116
117 builder.init();
118 }
119
get_book_dir(args: &ArgMatches) -> PathBuf120 fn get_book_dir(args: &ArgMatches) -> PathBuf {
121 if let Some(dir) = args.value_of("dir") {
122 // Check if path is relative from current dir, or absolute...
123 let p = Path::new(dir);
124 if p.is_relative() {
125 env::current_dir().unwrap().join(dir)
126 } else {
127 p.to_path_buf()
128 }
129 } else {
130 env::current_dir().expect("Unable to determine the current directory")
131 }
132 }
133
open<P: AsRef<OsStr>>(path: P)134 fn open<P: AsRef<OsStr>>(path: P) {
135 info!("Opening web browser");
136 if let Err(e) = opener::open(path) {
137 error!("Error opening web browser: {}", e);
138 }
139 }
140