use crate::cli::get_cli_args;
use crate::config::{get_config, get_config_args};
use crate::logging::{record_file_log, Log};
use colored::Colorize;
use log::Level;
use log::LevelFilter;
use merge::Merge;
use std::fmt;
use std::path::PathBuf;
const DISPLAY_HEADER_WIDTH: usize = 24;
#[derive(Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct Args {
pub check: bool,
pub print: bool,
pub fail_on_change: bool,
pub wrap: bool,
pub wraplen: u8,
pub wrapmin: u8,
pub tabsize: u8,
pub tabchar: TabChar,
pub stdin: bool,
pub config: Option<PathBuf>,
pub lists: Vec<String>,
pub verbatims: Vec<String>,
pub no_indent_envs: Vec<String>,
pub verbosity: LevelFilter,
pub arguments: bool,
pub files: Vec<String>,
}
#[derive(Clone, Debug, Merge)]
#[allow(clippy::missing_docs_in_private_items)]
pub struct OptionArgs {
pub check: Option<bool>,
pub print: Option<bool>,
pub fail_on_change: Option<bool>,
pub wrap: Option<bool>,
pub wraplen: Option<u8>,
pub wrapmin: Option<u8>,
pub tabsize: Option<u8>,
pub tabchar: Option<TabChar>,
pub stdin: Option<bool>,
pub config: Option<PathBuf>,
pub noconfig: Option<bool>,
#[merge(strategy = merge::vec::append)]
pub lists: Vec<String>,
#[merge(strategy = merge::vec::append)]
pub verbatims: Vec<String>,
#[merge(strategy = merge::vec::append)]
pub no_indent_envs: Vec<String>,
pub verbosity: Option<LevelFilter>,
pub arguments: Option<bool>,
#[merge(strategy = merge::vec::append)]
pub files: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TabChar {
Tab,
Space,
}
impl fmt::Display for TabChar {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Tab => write!(f, "tab"),
Self::Space => write!(f, "space"),
}
}
}
impl Default for OptionArgs {
fn default() -> Self {
let lists = vec![
"itemize",
"enumerate",
"description",
"inlineroman",
"inventory",
]
.into_iter()
.map(std::borrow::ToOwned::to_owned)
.collect();
let verbatims =
vec!["verbatim", "Verbatim", "lstlisting", "minted", "comment"]
.into_iter()
.map(std::borrow::ToOwned::to_owned)
.collect();
let no_indent_envs = vec!["document"]
.into_iter()
.map(std::borrow::ToOwned::to_owned)
.collect();
Self {
check: Some(false),
print: Some(false),
fail_on_change: Some(false),
wrap: Some(true),
wraplen: Some(80),
wrapmin: Some(70),
tabsize: Some(2),
tabchar: Some(TabChar::Space),
stdin: Some(false),
config: None,
noconfig: Some(false),
lists,
verbatims,
no_indent_envs,
verbosity: Some(LevelFilter::Warn),
arguments: Some(false),
files: vec![],
}
}
}
impl OptionArgs {
#[must_use]
pub fn new() -> Self {
Self {
check: None,
print: None,
fail_on_change: None,
wrap: None,
wraplen: None,
wrapmin: None,
tabsize: None,
tabchar: None,
stdin: None,
config: None,
noconfig: None,
lists: vec![],
verbatims: vec![],
no_indent_envs: vec![],
verbosity: None,
arguments: None,
files: vec![],
}
}
}
#[must_use]
pub fn get_args() -> Args {
let mut args: OptionArgs = get_cli_args(None);
let config = get_config(&args);
let config_args: Option<OptionArgs> = get_config_args(config);
if let Some(c) = config_args {
args.merge(c);
}
args.merge(OptionArgs::default());
Args::from(args)
}
impl Args {
#[must_use]
pub fn from(args: OptionArgs) -> Self {
Self {
check: args.check.unwrap(),
print: args.print.unwrap(),
fail_on_change: args.fail_on_change.unwrap(),
wrap: args.wrap.unwrap(),
wraplen: args.wraplen.unwrap(),
wrapmin: args.wrapmin.unwrap(),
tabsize: args.tabsize.unwrap(),
tabchar: args.tabchar.unwrap(),
stdin: args.stdin.unwrap(),
config: args.config,
lists: args.lists,
verbatims: args.verbatims,
no_indent_envs: args.no_indent_envs,
verbosity: args.verbosity.unwrap(),
arguments: args.arguments.unwrap(),
files: args.files,
}
}
pub fn resolve(&mut self, logs: &mut Vec<Log>) -> u8 {
let mut exit_code = 0;
self.print |= self.stdin;
self.wrapmin = if self.wraplen >= 50 {
self.wraplen - 10
} else {
self.wraplen
};
if !self.stdin && self.files.is_empty() {
record_file_log(
logs,
Level::Error,
"",
"No files specified. Provide filenames or pass --stdin.",
);
exit_code = 1;
}
if self.stdin && !self.files.is_empty() {
record_file_log(
logs,
Level::Error,
"",
"Do not provide file name(s) when using --stdin.",
);
exit_code = 1;
}
self.lists.dedup();
self.verbatims.dedup();
self.no_indent_envs.dedup();
self.files.dedup();
if self.arguments {
println!("{self}");
std::process::exit(0);
}
exit_code
}
}
impl Default for Args {
fn default() -> Self {
Self::from(OptionArgs::default())
}
}
fn display_arg_line(
f: &mut fmt::Formatter,
name: &str,
value: &str,
) -> fmt::Result {
let width = DISPLAY_HEADER_WIDTH;
let name_fmt = format!("{}{}", name.bold(), ":");
write!(f, "\n {name_fmt:<width$} {value}")?;
Ok(())
}
fn display_args_list(
v: &[String],
name: &str,
f: &mut fmt::Formatter,
) -> fmt::Result {
if !v.is_empty() {
display_arg_line(f, name, &v[0])?;
for x in &v[1..] {
write!(
f,
"\n {:<width$} {}",
"".bold().to_string(),
x,
width = DISPLAY_HEADER_WIDTH
)?;
}
}
Ok(())
}
impl fmt::Display for Args {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", "tex-fmt".magenta().bold())?;
display_arg_line(f, "check", &self.check.to_string())?;
display_arg_line(f, "print", &self.print.to_string())?;
display_arg_line(f, "fail-on-change", &self.print.to_string())?;
display_arg_line(f, "wrap", &self.wrap.to_string())?;
display_arg_line(f, "wraplen", &self.wraplen.to_string())?;
display_arg_line(f, "wrapmin", &self.wrapmin.to_string())?;
display_arg_line(f, "tabsize", &self.tabsize.to_string())?;
display_arg_line(f, "tabchar", &self.tabchar.to_string())?;
display_arg_line(f, "stdin", &self.stdin.to_string())?;
match &self.config {
None => display_arg_line(f, "config", "None")?,
Some(c) => display_arg_line(f, "config", &c.display().to_string())?,
}
display_arg_line(
f,
"verbosity",
&self.verbosity.to_string().to_lowercase(),
)?;
display_args_list(&self.lists, "lists", f)?;
display_args_list(&self.verbatims, "lists", f)?;
display_args_list(&self.no_indent_envs, "no-indent-envs", f)?;
display_args_list(&self.files, "files", f)?;
Ok(())
}
}