use clap::{Arg, ArgAction, ArgMatches, Command};
use console::style;
use crowbook::Book;
use rust_i18n::t;
use std::env;
use std::fs;
use std::io::{self, Write};
use std::process::exit;
static BIRD: &str = "🐦 ";
static ERROR: &str = "💣 ";
static WARNING: &str = "⚠️ ";
static BOOK: &str = "📚 ";
pub fn print_warning(msg: &str, emoji: bool) {
if emoji {
eprint!("{}", style(WARNING).yellow());
}
eprintln!("{} {}", style(t!("error.warning")).bold().yellow(), msg);
}
pub fn print_error(s: &str, emoji: bool) {
if emoji {
eprint!("{}", style(ERROR).red());
}
eprintln!("{} {}", style(t!("error.error")).bold().red(), s);
}
pub fn print_error_and_exit(s: &str, emoji: bool) -> ! {
print_error(s, emoji);
exit(0);
}
pub fn display_header(emoji: bool) {
if emoji {
eprint!("{}", style(BIRD).magenta());
}
eprint!("{}", style("CROWBOOK ").magenta().bold());
if emoji {
eprint!("{}", style(BOOK).magenta());
}
eprintln!("{}", style(env!("CARGO_PKG_VERSION")).blue());
}
pub fn get_lang() -> Option<String> {
let mut found = false;
for arg in env::args() {
if found {
return Some(arg);
} else if arg == "--lang" || arg == "-L" {
found = true;
}
}
None
}
pub fn get_book_options(matches: &ArgMatches) -> Vec<(&str, &str)> {
let mut output = vec![];
if let Some(iter) = matches.get_many::<String>("set") {
let v: Vec<_> = iter.collect();
if v.len() % 2 != 0 {
print_error_and_exit(
&t!("error.odd_number"),
false,
);
}
for i in 0..v.len() / 2 {
let key = v[i * 2];
let value = v[i * 2 + 1];
output.push((key.as_str(), value.as_str()));
}
}
output
}
pub fn set_book_options(book: &mut Book, matches: &ArgMatches) -> String {
let mut output = String::new();
let options = get_book_options(matches);
for (key, value) in options {
let res = book.options.set(key, value);
if let Err(err) = res {
print_error_and_exit(&t!("error.set_key", key = key, error = err), false);
}
output.push_str(&format!("{key}: {value}\n"));
}
output
}
pub fn create_book(matches: &ArgMatches) -> ! {
let mut f: Box<dyn Write> = if let Some(book) = matches.get_one::<String>("BOOK") {
if fs::metadata(book).is_ok() {
print_error_and_exit(
&t!("error.create", file = book),
false,
);
}
Box::new(fs::File::create(book).unwrap())
} else {
Box::new(io::stdout())
};
if let Some(values) = matches.get_many::<String>("files") {
if matches.get_many::<String>("set").is_some() {
let mut book = Book::new();
let s = set_book_options(&mut book, matches);
f.write_all(s.as_bytes()).unwrap();
} else {
f.write_all(
t!("msg.default_book")
.as_bytes(),
)
.unwrap();
}
f.write_all(t!("msg.chapter_list").as_bytes())
.unwrap();
for file in values {
f.write_all(format!("+ {file}\n").as_bytes()).unwrap();
}
if let Some(s) = matches.get_one::<String>("BOOK") {
println!(
"{}",
t!("msg.created", file = s)
);
}
exit(0);
} else {
unreachable!(); }
}
pub fn create_matches() -> ArgMatches {
app().get_matches()
}
fn app() -> clap::Command {
lazy_static! {
static ref ABOUT: String = t!("cmd.about").into_owned();
static ref SINGLE: String = t!("cmd.single").into_owned();
static ref EMOJI: String = t!("cmd.emoji").into_owned();
static ref VERBOSE: String = t!("cmd.verbose").into_owned();
static ref QUIET: String = t!("cmd.quiet").into_owned();
static ref CREATE: String = t!("cmd.create").into_owned();
static ref AUTOGRAPH: String = t!("cmd.autograph").into_owned();
static ref OUTPUT: String = t!("cmd.output").into_owned();
static ref LANG: String = t!("cmd.lang").into_owned();
static ref TO: String = t!("cmd.to").into_owned();
static ref SET: String = t!("cmd.set").into_owned();
static ref NO_FANCY: String = t!("cmd.no_fancy").into_owned();
static ref LIST_OPTIONS: String = t!("cmd.list_options").into_owned();
static ref LIST_OPTIONS_MD: String = t!("cmd.list_options_md").into_owned();
static ref PRINT_TEMPLATE: String = t!("cmd.template").into_owned();
static ref BOOK: String = t!("cmd.book").into_owned();
static ref STATS: String = t!("cmd.stats").into_owned();
static ref TEMPLATE: String = t!("clap.template").into_owned();
}
let app = Command::new("crowbook")
.version(env!("CARGO_PKG_VERSION"))
.author("Élisabeth Henry <liz.henry@ouvaton.org>")
.hide_possible_values(true)
.about(ABOUT.as_str())
.arg(
Arg::new("force-emoji")
.short('f')
.long("force-emoji")
.action(ArgAction::SetTrue)
.help(EMOJI.as_str()),
)
.arg(
Arg::new("single")
.short('s')
.long("single")
.action(ArgAction::SetTrue)
.help(SINGLE.as_str()),
)
.arg(
Arg::new("no-fancy")
.short('n')
.long("no-fancy")
.action(ArgAction::SetTrue)
.help(NO_FANCY.as_str()),
)
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.action(ArgAction::SetTrue)
.help(VERBOSE.as_str()),
)
.arg(
Arg::new("autograph")
.short('a')
.long("autograph")
.action(ArgAction::SetTrue)
.help(AUTOGRAPH.as_str()),
)
.arg(
Arg::new("quiet")
.short('q')
.long("quiet")
.action(ArgAction::SetTrue)
.help(QUIET.as_str())
.conflicts_with("verbose"),
)
.arg(
Arg::new("files")
.short('c')
.long("create")
.action(ArgAction::Set)
.num_args(1..)
.help(CREATE.as_str()),
)
.arg(
Arg::new("output")
.short('o')
.long("output")
.action(ArgAction::Set)
.num_args(1)
.help(OUTPUT.as_str())
.requires("to"),
)
.arg(
Arg::new("to")
.short('t')
.long("to")
.action(ArgAction::Set)
.value_parser([
"epub",
"pdf",
"html",
"tex",
"odt",
"html.dir",
])
.help(TO.as_str()),
)
.arg(
Arg::new("set")
.long("set")
.action(ArgAction::Set)
.num_args(2..)
.help(SET.as_str()),
)
.arg(
Arg::new("list-options")
.short('l')
.long("list-options")
.action(ArgAction::SetTrue)
.help(LIST_OPTIONS.as_str()),
)
.arg(
Arg::new("list-options-md")
.long("list-options-md")
.action(ArgAction::SetTrue)
.help(LIST_OPTIONS_MD.as_str())
.hide(true),
)
.arg(
Arg::new("lang")
.short('L')
.long("lang")
.action(ArgAction::Set)
.num_args(1)
.help(LANG.as_str()),
)
.arg(
Arg::new("print-template")
.long("print-template")
.action(ArgAction::Set)
.num_args(1)
.help(PRINT_TEMPLATE.as_str()),
)
.arg(
Arg::new("stats")
.short('S')
.long("stats")
.action(ArgAction::SetTrue)
.help(STATS.as_str()),
)
.arg(
Arg::new("BOOK")
.index(1)
.action(ArgAction::Set)
.help(BOOK.as_str()),
)
.help_template(TEMPLATE.as_str());
app
}
#[cfg(test)]
mod tests {
use super::app;
#[test]
fn verify_app() {
app().debug_assert();
}
}