Skip to content
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
150 changes: 150 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//! The mdbook CLI library

#![allow(unreachable_pub, reason = "not needed in a bin crate")]

use anyhow::anyhow;
use clap::{Arg, ArgMatches, Command};
use clap_complete::Shell;
use mdbook_core::utils;
use std::env;
use std::ffi::OsStr;
use std::path::PathBuf;
use tracing::{error, info};

mod cmd;

const VERSION: &str = concat!("v", clap::crate_version!());

/// The main entrypoint
pub fn main() {
init_logger();

let command = create_clap_command();

// Check which subcommand the user ran...
let res = match command.get_matches().subcommand() {
Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
#[cfg(feature = "watch")]
Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
Some(("completions", sub_matches)) => (|| {
let shell = sub_matches
.get_one::<Shell>("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?;

let mut complete_app = create_clap_command();
clap_complete::generate(
*shell,
&mut complete_app,
"mdbook",
&mut std::io::stdout().lock(),
);
Ok(())
})(),
_ => unreachable!(),
};

if let Err(e) = res {
utils::log_backtrace(&e);

std::process::exit(101);
}
}

/// Create a list of valid arguments and sub-commands
fn create_clap_command() -> Command {
let app = Command::new(clap::crate_name!())
.about(clap::crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION)
.propagate_version(true)
.arg_required_else_help(true)
.after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
)
.subcommand(cmd::init::make_subcommand())
.subcommand(cmd::build::make_subcommand())
.subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand())
.subcommand(
Command::new("completions")
.about("Generate shell completions for your shell to stdout")
.arg(
Arg::new("shell")
.value_parser(clap::value_parser!(Shell))
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true),
),
);

#[cfg(feature = "watch")]
let app = app.subcommand(cmd::watch::make_subcommand());
#[cfg(feature = "serve")]
let app = app.subcommand(cmd::serve::make_subcommand());

app
}

fn init_logger() {
let filter = tracing_subscriber::EnvFilter::builder()
.with_env_var("MDBOOK_LOG")
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.from_env_lossy();
let log_env = std::env::var("MDBOOK_LOG");
// Silence some particularly noisy dependencies unless the user
// specifically asks for them.
let silence_unless_specified = |filter: tracing_subscriber::EnvFilter, target| {
if !log_env.as_ref().map_or(false, |s| {
s.split(',').any(|directive| directive.starts_with(target))
}) {
filter.add_directive(format!("{target}=warn").parse().unwrap())
} else {
filter
}
};
let filter = silence_unless_specified(filter, "handlebars");
let filter = silence_unless_specified(filter, "html5ever");

// Don't show the target by default, since it generally isn't useful
// unless you are overriding the level.
let with_target = log_env.is_ok();

tracing_subscriber::fmt()
.without_time()
.with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr()))
.with_writer(std::io::stderr)
.with_env_filter(filter)
.with_target(with_target)
.init();
}

fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(p) = args.get_one::<PathBuf>("dir") {
// Check if path is relative from current dir, or absolute...
if p.is_relative() {
env::current_dir().unwrap().join(p)
} else {
p.to_path_buf()
}
} else {
env::current_dir().expect("Unable to determine the current directory")
}
}

fn open<P: AsRef<OsStr>>(path: P) {
info!("Opening web browser");
if let Err(e) = opener::open(path) {
error!("Error opening web browser: {}", e);
}
}

#[test]
fn verify_app() {
create_clap_command().debug_assert();
}
150 changes: 5 additions & 145 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,149 +1,9 @@
//! The mdbook CLI.

#![allow(unreachable_pub, reason = "not needed in a bin crate")]

use anyhow::anyhow;
use clap::{Arg, ArgMatches, Command};
use clap_complete::Shell;
use mdbook_core::utils;
use std::env;
use std::ffi::OsStr;
use std::path::PathBuf;
use tracing::{error, info};

mod cmd;

const VERSION: &str = concat!("v", clap::crate_version!());

fn main() {
init_logger();

let command = create_clap_command();

// Check which subcommand the user ran...
let res = match command.get_matches().subcommand() {
Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
#[cfg(feature = "watch")]
Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")]
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
Some(("completions", sub_matches)) => (|| {
let shell = sub_matches
.get_one::<Shell>("shell")
.ok_or_else(|| anyhow!("Shell name missing."))?;

let mut complete_app = create_clap_command();
clap_complete::generate(
*shell,
&mut complete_app,
"mdbook",
&mut std::io::stdout().lock(),
);
Ok(())
})(),
_ => unreachable!(),
};

if let Err(e) = res {
utils::log_backtrace(&e);

std::process::exit(101);
}
}

/// Create a list of valid arguments and sub-commands
fn create_clap_command() -> Command {
let app = Command::new(clap::crate_name!())
.about(clap::crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION)
.propagate_version(true)
.arg_required_else_help(true)
.after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\
The source code for mdBook is available at: https://github.com/rust-lang/mdBook",
)
.subcommand(cmd::init::make_subcommand())
.subcommand(cmd::build::make_subcommand())
.subcommand(cmd::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand())
.subcommand(
Command::new("completions")
.about("Generate shell completions for your shell to stdout")
.arg(
Arg::new("shell")
.value_parser(clap::value_parser!(Shell))
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true),
),
);

#[cfg(feature = "watch")]
let app = app.subcommand(cmd::watch::make_subcommand());
#[cfg(feature = "serve")]
let app = app.subcommand(cmd::serve::make_subcommand());

app
}

fn init_logger() {
let filter = tracing_subscriber::EnvFilter::builder()
.with_env_var("MDBOOK_LOG")
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.from_env_lossy();
let log_env = std::env::var("MDBOOK_LOG");
// Silence some particularly noisy dependencies unless the user
// specifically asks for them.
let silence_unless_specified = |filter: tracing_subscriber::EnvFilter, target| {
if !log_env.as_ref().map_or(false, |s| {
s.split(',').any(|directive| directive.starts_with(target))
}) {
filter.add_directive(format!("{target}=warn").parse().unwrap())
} else {
filter
}
};
let filter = silence_unless_specified(filter, "handlebars");
let filter = silence_unless_specified(filter, "html5ever");

// Don't show the target by default, since it generally isn't useful
// unless you are overriding the level.
let with_target = log_env.is_ok();

tracing_subscriber::fmt()
.without_time()
.with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr()))
.with_writer(std::io::stderr)
.with_env_filter(filter)
.with_target(with_target)
.init();
}

fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(p) = args.get_one::<PathBuf>("dir") {
// Check if path is relative from current dir, or absolute...
if p.is_relative() {
env::current_dir().unwrap().join(p)
} else {
p.to_path_buf()
}
} else {
env::current_dir().expect("Unable to determine the current directory")
}
}

fn open<P: AsRef<OsStr>>(path: P) {
info!("Opening web browser");
if let Err(e) = opener::open(path) {
error!("Error opening web browser: {}", e);
}
}

#[test]
fn verify_app() {
create_clap_command().debug_assert();
// Note that `main` lives in a library to allow consumers
// to access the mdbook entrypoint without the need for
// a nightly toolchain to use `-Z bindeps`
// https://github.com/rust-lang/cargo/issues/9096
mdbook::main()
}
Loading