2015-07-07 03:12:24 +08:00
extern crate mdbook ;
2015-08-01 12:59:05 +08:00
#[ macro_use ]
extern crate clap ;
2016-08-14 21:55:10 +08:00
extern crate log ;
extern crate env_logger ;
2017-01-02 01:42:47 +08:00
extern crate open ;
2015-11-09 21:31:00 +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 ;
2017-01-02 01:42:47 +08:00
use std ::ffi ::OsStr ;
2015-08-01 12:59:05 +08:00
use std ::io ::{ self , Write } ;
use std ::path ::{ Path , PathBuf } ;
2016-03-20 00:45:58 +08:00
use clap ::{ App , ArgMatches , SubCommand , AppSettings } ;
2015-07-07 03:12:24 +08:00
2017-06-26 03:41:23 +08:00
#[ cfg(feature = " serve " ) ]
pub mod serve ;
2015-09-27 20:38:37 +08:00
#[ cfg(feature = " watch " ) ]
2017-06-26 05:44:28 +08:00
pub mod watch ;
2015-11-09 21:31:00 +08:00
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 ( ) {
2016-08-14 21:55:10 +08:00
env_logger ::init ( ) . unwrap ( ) ;
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! ( ) ) )
2016-03-20 00:45:58 +08:00
. setting ( AppSettings ::SubcommandRequired )
2016-06-12 07:08:48 +08:00
. after_help ( " For more information about a specific command, try `mdbook <command> --help` \n Source code for mdbook available at: https://github.com/azerupi/mdBook " )
2015-08-01 12:59:05 +08:00
. 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
2017-01-12 20:23:39 +08:00
. arg_from_usage ( " [dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)' " )
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 " )
2017-01-02 01:42:47 +08:00
. arg_from_usage ( " -o, --open 'Open the compiled book in a web browser' " )
2017-01-12 20:26:22 +08:00
. arg_from_usage ( " -d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)' " )
2017-04-18 09:53:27 +08:00
. arg_from_usage ( " --no-create 'Will not create non-existent files linked from SUMMARY.md' " )
2017-06-01 13:28:08 +08:00
. arg_from_usage ( " --curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans' " )
2017-01-12 20:23:39 +08:00
. arg_from_usage ( " [dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)' " ) )
2015-08-01 12:59:05 +08:00
. subcommand ( SubCommand ::with_name ( " watch " )
2015-09-27 20:38:37 +08:00
. about ( " Watch the files for changes " )
2017-01-02 01:42:47 +08:00
. arg_from_usage ( " -o, --open 'Open the compiled book in a web browser' " )
2017-01-12 20:26:22 +08:00
. arg_from_usage ( " -d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)' " )
2017-06-01 13:28:08 +08:00
. arg_from_usage ( " --curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans' " )
2017-01-12 20:23:39 +08:00
. arg_from_usage ( " [dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)' " ) )
2016-04-02 10:46:05 +08:00
. subcommand ( SubCommand ::with_name ( " serve " )
. about ( " Serve the book at http://localhost:3000. Rebuild and reload on change. " )
2017-01-12 20:23:39 +08:00
. arg_from_usage ( " [dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)' " )
2017-01-12 20:26:22 +08:00
. arg_from_usage ( " -d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)' " )
2017-06-01 13:28:08 +08:00
. arg_from_usage ( " --curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans' " )
2016-04-02 10:46:05 +08:00
. arg_from_usage ( " -p, --port=[port] 'Use another port{n}(Defaults to 3000)' " )
2017-05-26 19:18:32 +08:00
. arg_from_usage ( " -w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)' " )
2016-08-06 05:40:00 +08:00
. arg_from_usage ( " -i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)' " )
2017-01-02 01:42:47 +08:00
. arg_from_usage ( " -a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface address)' " )
. arg_from_usage ( " -o, --open 'Open the book server in a web browser' " ) )
2015-12-16 02:55:23 +08:00
. subcommand ( SubCommand ::with_name ( " test " )
. about ( " Test that code samples compile " ) )
2015-08-01 12:59:05 +08:00
. get_matches ( ) ;
// Check which subcomamnd the user ran...
let res = match matches . subcommand ( ) {
2016-03-18 05:31:28 +08:00
( " init " , Some ( sub_matches ) ) = > init ( sub_matches ) ,
2015-08-01 12:59:05 +08:00
( " build " , Some ( sub_matches ) ) = > build ( sub_matches ) ,
2015-09-27 20:38:37 +08:00
#[ cfg(feature = " watch " ) ]
2017-06-26 05:44:28 +08:00
( " watch " , Some ( sub_matches ) ) = > watch ::watch ( sub_matches ) ,
2016-04-02 10:46:05 +08:00
#[ cfg(feature = " serve " ) ]
2017-06-14 20:43:43 +08:00
( " serve " , Some ( sub_matches ) ) = > serve ::serve ( sub_matches ) ,
2015-12-16 02:55:23 +08:00
( " test " , Some ( sub_matches ) ) = > test ( sub_matches ) ,
2016-03-18 05:31:28 +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 ( ) ;
2016-08-07 02:54:07 +08:00
::std ::process ::exit ( 101 ) ;
2015-07-07 03:12:24 +08:00
}
}
2015-11-09 21:31:00 +08:00
// Simple function that user comfirmation
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 ,
2016-03-18 05:31:28 +08:00
_ = > false ,
2015-08-11 22:42:19 +08:00
}
}
2015-11-09 21:31:00 +08:00
// Init command implementation
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
2017-05-19 19:04:37 +08:00
book . init ( ) ? ;
2015-08-11 22:13:41 +08:00
// If flag `--theme` is present, copy theme to src
if args . is_present ( " theme " ) {
2016-02-28 23:28:11 +08:00
// Skip this if `--force` is present
2015-08-11 22:42:19 +08:00
if ! args . is_present ( " force " ) {
// Print warning
2017-05-19 05:52:38 +08:00
print! ( " \n Copying the default theme to {:?} " , book . get_source ( ) ) ;
2015-08-11 22:42:19 +08:00
println! ( " could potentially overwrite files already present in that directory. " ) ;
print! ( " \n Are 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! ( " \n Skipping... \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
2017-05-19 19:04:37 +08:00
book . copy_theme ( ) ? ;
2015-08-11 22:42:19 +08:00
println! ( " \n Theme copied. " ) ;
2015-08-11 22:13:41 +08:00
}
2016-03-07 16:52:19 +08:00
// Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root`
2017-05-19 05:52:38 +08:00
let is_dest_inside_root = book . get_destination ( )
. map ( | p | p . starts_with ( book . get_root ( ) ) )
. unwrap_or ( false ) ;
2016-02-28 23:28:11 +08:00
if ! args . is_present ( " force " ) & & is_dest_inside_root {
2016-03-03 02:38:39 +08:00
println! ( " \n Do you want a .gitignore to be created? (y/n) " ) ;
2016-02-28 23:28:11 +08:00
if confirm ( ) {
book . create_gitignore ( ) ;
println! ( " \n .gitignore created. " ) ;
2016-03-18 05:31:28 +08:00
}
2016-02-28 23:28:11 +08:00
}
2015-08-13 16:46:56 +08:00
println! ( " \n All done, no errors... " ) ;
2015-08-11 22:13:41 +08:00
Ok ( ( ) )
2015-07-07 03:12:24 +08:00
}
2015-11-09 21:31:00 +08:00
// Build command implementation
2015-08-01 12:59:05 +08:00
fn build ( args : & ArgMatches ) -> Result < ( ) , Box < Error > > {
let book_dir = get_book_dir ( args ) ;
2017-05-19 05:52:38 +08:00
let book = MDBook ::new ( & book_dir ) . read_config ( ) ? ;
2017-01-12 20:26:22 +08:00
let mut book = match args . value_of ( " dest-dir " ) {
2017-06-06 17:58:08 +08:00
Some ( dest_dir ) = > book . with_destination ( dest_dir ) ,
2017-05-19 19:04:37 +08:00
None = > book ,
2017-01-12 20:26:22 +08:00
} ;
2015-07-07 08:56:19 +08:00
2017-04-18 09:53:27 +08:00
if args . is_present ( " no-create " ) {
book . create_missing = false ;
}
2017-06-01 13:28:08 +08:00
if args . is_present ( " curly-quotes " ) {
book = book . with_curly_quotes ( true ) ;
}
2017-05-19 19:04:37 +08:00
book . build ( ) ? ;
2015-08-11 22:13:41 +08:00
2017-05-19 05:52:38 +08:00
if let Some ( d ) = book . get_destination ( ) {
if args . is_present ( " open " ) {
open ( d . join ( " index.html " ) ) ;
}
2017-01-02 01:42:47 +08:00
}
2015-08-11 22:13:41 +08:00
Ok ( ( ) )
2015-07-07 03:12:24 +08:00
}
2015-12-16 02:55:23 +08:00
fn test ( args : & ArgMatches ) -> Result < ( ) , Box < Error > > {
let book_dir = get_book_dir ( args ) ;
2017-05-19 05:52:38 +08:00
let mut book = MDBook ::new ( & book_dir ) . read_config ( ) ? ;
2015-12-16 02:55:23 +08:00
2017-05-19 19:04:37 +08:00
book . test ( ) ? ;
2015-12-16 02:55:23 +08:00
2015-09-27 20:38:37 +08:00
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 ( ) {
2016-03-18 05:31:28 +08:00
env ::current_dir ( ) . unwrap ( ) . join ( dir )
2015-08-01 12:59:05 +08:00
} else {
2016-03-18 05:31:28 +08:00
p . to_path_buf ( )
2015-08-01 12:59:05 +08:00
}
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
}
}
2016-04-02 10:46:05 +08:00
2017-01-02 01:42:47 +08:00
fn open < P : AsRef < OsStr > > ( path : P ) {
if let Err ( e ) = open ::that ( path ) {
println! ( " Error opening web browser: {} " , e ) ;
}
}