2015-07-07 03:12:24 +08:00
|
|
|
extern crate mdbook;
|
2015-08-01 12:59:05 +08:00
|
|
|
#[macro_use]
|
|
|
|
extern crate clap;
|
2015-09-27 20:38:37 +08:00
|
|
|
#[cfg(feature = "watch")]
|
|
|
|
extern crate notify;
|
2015-08-01 12:59:05 +08:00
|
|
|
|
2015-07-07 03:12:24 +08:00
|
|
|
use std::env;
|
2015-08-01 12:59:05 +08:00
|
|
|
use std::error::Error;
|
|
|
|
use std::io::{self, Write};
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
|
|
use clap::{App, ArgMatches, SubCommand};
|
2015-07-07 03:12:24 +08:00
|
|
|
|
2015-09-27 20:38:37 +08:00
|
|
|
#[cfg(feature = "watch")]
|
|
|
|
use notify::Watcher;
|
|
|
|
#[cfg(feature = "watch")]
|
|
|
|
use std::sync::mpsc::channel;
|
|
|
|
|
2015-07-07 08:56:19 +08:00
|
|
|
use mdbook::MDBook;
|
2015-07-07 03:12:24 +08:00
|
|
|
|
|
|
|
const NAME: &'static str = "mdbook";
|
|
|
|
|
|
|
|
fn main() {
|
2015-08-01 12:59:05 +08:00
|
|
|
// Create a list of valid arguments and sub-commands
|
|
|
|
let matches = App::new(NAME)
|
|
|
|
.about("Create a book in form of a static website from markdown files")
|
|
|
|
.author("Mathieu David <mathieudavid@mathieudavid.org>")
|
|
|
|
// Get the version from our Cargo.toml using clap's crate_version!() macro
|
|
|
|
.version(&*format!("v{}", crate_version!()))
|
|
|
|
.subcommand_required(true)
|
|
|
|
.after_help("For more information about a specific command, try `mdbook <command> --help`")
|
|
|
|
.subcommand(SubCommand::with_name("init")
|
|
|
|
.about("Create boilerplate structure and files in the directory")
|
2015-08-11 22:13:41 +08:00
|
|
|
// the {n} denotes a newline which will properly aligned in all help messages
|
|
|
|
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")
|
2015-08-11 22:42:19 +08:00
|
|
|
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
|
|
|
|
.arg_from_usage("--force 'skip confirmation prompts'"))
|
2015-08-01 12:59:05 +08:00
|
|
|
.subcommand(SubCommand::with_name("build")
|
|
|
|
.about("Build the book from the markdown files")
|
|
|
|
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
|
|
|
|
.subcommand(SubCommand::with_name("watch")
|
2015-09-27 20:38:37 +08:00
|
|
|
.about("Watch the files for changes")
|
|
|
|
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
|
2015-08-01 12:59:05 +08:00
|
|
|
.get_matches();
|
|
|
|
|
|
|
|
// Check which subcomamnd the user ran...
|
|
|
|
let res = match matches.subcommand() {
|
|
|
|
("init", Some(sub_matches)) => init(sub_matches),
|
|
|
|
("build", Some(sub_matches)) => build(sub_matches),
|
2015-09-27 20:38:37 +08:00
|
|
|
#[cfg(feature = "watch")]
|
|
|
|
("watch", Some(sub_matches)) => watch(sub_matches),
|
2015-08-01 12:59:05 +08:00
|
|
|
(_, _) => unreachable!()
|
2015-07-07 03:12:24 +08:00
|
|
|
};
|
|
|
|
|
2015-08-01 12:59:05 +08:00
|
|
|
if let Err(e) = res {
|
2015-08-11 22:13:41 +08:00
|
|
|
writeln!(&mut io::stderr(), "An error occured:\n{}", e).ok();
|
2015-07-07 03:12:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-11 22:42:19 +08:00
|
|
|
fn confirm() -> bool {
|
|
|
|
io::stdout().flush().unwrap();
|
|
|
|
let mut s = String::new();
|
|
|
|
io::stdin().read_line(&mut s).ok();
|
|
|
|
match &*s.trim() {
|
|
|
|
"Y" | "y" | "yes" | "Yes" => true,
|
|
|
|
_ => false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-01 12:59:05 +08:00
|
|
|
fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
|
2015-08-11 22:13:41 +08:00
|
|
|
|
2015-08-01 12:59:05 +08:00
|
|
|
let book_dir = get_book_dir(args);
|
2015-08-30 00:51:23 +08:00
|
|
|
let mut book = MDBook::new(&book_dir);
|
2015-07-07 03:12:24 +08:00
|
|
|
|
2015-08-11 22:13:41 +08:00
|
|
|
// Call the function that does the initialization
|
|
|
|
try!(book.init());
|
|
|
|
|
|
|
|
// If flag `--theme` is present, copy theme to src
|
|
|
|
if args.is_present("theme") {
|
|
|
|
|
2015-08-11 22:42:19 +08:00
|
|
|
// Skip this id `--force` is present
|
|
|
|
if !args.is_present("force") {
|
|
|
|
// Print warning
|
|
|
|
print!("\nCopying the default theme to {:?}", book.get_src());
|
|
|
|
println!("could potentially overwrite files already present in that directory.");
|
|
|
|
print!("\nAre you sure you want to continue? (y/n) ");
|
|
|
|
|
|
|
|
// Read answer from user and exit if it's not 'yes'
|
|
|
|
if !confirm() {
|
2015-08-13 16:46:56 +08:00
|
|
|
println!("\nSkipping...\n");
|
|
|
|
println!("All done, no errors...");
|
2015-08-11 22:42:19 +08:00
|
|
|
::std::process::exit(0);
|
|
|
|
}
|
|
|
|
}
|
2015-08-11 22:13:41 +08:00
|
|
|
|
|
|
|
// Call the function that copies the theme
|
|
|
|
try!(book.copy_theme());
|
2015-08-11 22:42:19 +08:00
|
|
|
println!("\nTheme copied.");
|
2015-08-11 22:13:41 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-08-13 16:46:56 +08:00
|
|
|
println!("\nAll done, no errors...");
|
|
|
|
|
2015-08-11 22:13:41 +08:00
|
|
|
Ok(())
|
2015-07-07 03:12:24 +08:00
|
|
|
}
|
|
|
|
|
2015-08-01 12:59:05 +08:00
|
|
|
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
|
|
|
|
let book_dir = get_book_dir(args);
|
2015-08-03 07:37:13 +08:00
|
|
|
let mut book = MDBook::new(&book_dir).read_config();
|
2015-07-07 08:56:19 +08:00
|
|
|
|
2015-08-11 22:13:41 +08:00
|
|
|
try!(book.build());
|
|
|
|
|
|
|
|
Ok(())
|
2015-07-07 03:12:24 +08:00
|
|
|
}
|
|
|
|
|
2015-09-27 20:38:37 +08:00
|
|
|
#[cfg(feature = "watch")]
|
|
|
|
fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
|
|
|
|
let book_dir = get_book_dir(args);
|
|
|
|
let book = MDBook::new(&book_dir).read_config();
|
|
|
|
|
|
|
|
// Create a channel to receive the events.
|
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
|
|
|
let w: Result<notify::RecommendedWatcher, notify::Error> = notify::Watcher::new(tx);
|
|
|
|
|
|
|
|
match w {
|
|
|
|
Ok(mut watcher) => {
|
|
|
|
|
|
|
|
watcher.watch(book.get_src()).unwrap();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match rx.recv() {
|
|
|
|
Ok(event) => {
|
|
|
|
if let Some(path) = event.path {
|
|
|
|
println!("File changed: {:?}\nBuilding book...\n", path);
|
|
|
|
try!(build(args));
|
|
|
|
println!("");
|
|
|
|
// Hack to prevent receiving the event 4 times, probably a bug in notify
|
|
|
|
return watch(args);
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
println!("An error occured: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
println!("Error while trying to watch the files:\n\n\t{:?}", e);
|
|
|
|
::std::process::exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-01 12:59:05 +08:00
|
|
|
fn get_book_dir(args: &ArgMatches) -> PathBuf {
|
|
|
|
if let Some(dir) = args.value_of("dir") {
|
|
|
|
// Check if path is relative from current dir, or absolute...
|
|
|
|
let p = Path::new(dir);
|
|
|
|
if p.is_relative() {
|
|
|
|
env::current_dir().unwrap().join(dir)
|
|
|
|
} else {
|
|
|
|
p.to_path_buf()
|
|
|
|
}
|
2015-07-18 06:04:20 +08:00
|
|
|
} else {
|
2015-08-01 12:59:05 +08:00
|
|
|
env::current_dir().unwrap()
|
2015-07-07 03:12:24 +08:00
|
|
|
}
|
|
|
|
}
|