use crate::cli::get_cli_args;
use crate::config::{get_config, get_config_args};
use crate::logging::{record_file_log, Log};
use crate::search::find_files;
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: usize,
pub wrapmin: usize,
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 wrap_chars: Vec<char>,
pub verbosity: LevelFilter,
pub arguments: bool,
pub files: Vec<PathBuf>,
pub recursive: bool,
pub format_tables: bool,
}
#[derive(Clone, Debug, Merge)]
#[allow(clippy::missing_docs_in_private_items)]
pub struct OptionArgs {
#[merge(strategy= merge::option::overwrite_none)]
pub check: Option<bool>,
#[merge(strategy= merge::option::overwrite_none)]
pub print: Option<bool>,
#[merge(strategy= merge::option::overwrite_none)]
pub fail_on_change: Option<bool>,
#[merge(strategy= merge::option::overwrite_none)]
pub wrap: Option<bool>,
#[merge(strategy= merge::option::overwrite_none)]
pub wraplen: Option<usize>,
#[merge(strategy= merge::option::overwrite_none)]
pub wrapmin: Option<usize>,
#[merge(strategy= merge::option::overwrite_none)]
pub tabsize: Option<u8>,
#[merge(strategy= merge::option::overwrite_none)]
pub tabchar: Option<TabChar>,
#[merge(strategy= merge::option::overwrite_none)]
pub stdin: Option<bool>,
#[merge(strategy= merge::option::overwrite_none)]
pub config: Option<PathBuf>,
#[merge(strategy= merge::option::overwrite_none)]
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>,
#[merge(strategy = merge_wrap_chars)]
pub wrap_chars: Vec<char>,
#[merge(strategy= merge::option::overwrite_none)]
pub verbosity: Option<LevelFilter>,
#[merge(strategy= merge::option::overwrite_none)]
pub arguments: Option<bool>,
#[merge(strategy = merge::vec::append)]
pub files: Vec<PathBuf>,
#[merge(strategy= merge::option::overwrite_none)]
pub recursive: Option<bool>,
#[merge(strategy= merge::option::overwrite_none)]
pub format_tables: Option<bool>,
}
fn merge_wrap_chars(left: &mut Vec<char>, right: Vec<char>) {
if !left.is_empty() {
return;
}
*left = right;
}
#[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();
let wrap_chars = vec![' '];
Self {
check: Some(false),
print: Some(false),
fail_on_change: Some(false),
wrap: Some(true),
wraplen: Some(80),
wrapmin: None,
tabsize: Some(2),
tabchar: Some(TabChar::Space),
stdin: Some(false),
config: None,
noconfig: Some(false),
lists,
verbatims,
no_indent_envs,
wrap_chars,
verbosity: Some(LevelFilter::Warn),
arguments: Some(false),
files: vec![],
recursive: Some(false),
format_tables: Some(false),
}
}
}
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![],
wrap_chars: vec![],
verbosity: None,
arguments: None,
files: vec![],
recursive: None,
format_tables: None,
}
}
}
#[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 {
let wraplen = args.wraplen.unwrap();
let wrapmin = if let Some(w) = args.wrapmin {
w.min(wraplen)
} else if wraplen >= 50 {
wraplen - 10
} else {
wraplen
};
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,
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,
wrap_chars: args.wrap_chars,
verbosity: args.verbosity.unwrap(),
arguments: args.arguments.unwrap(),
files: args.files,
recursive: args.recursive.unwrap(),
format_tables: args.format_tables.unwrap(),
}
}
pub fn resolve(&mut self, logs: &mut Vec<Log>) -> u8 {
let mut exit_code = 0;
let empty_path = PathBuf::from("");
self.print |= self.stdin;
for file in &mut self.files {
if !file.is_dir() && file.extension().is_none() {
file.set_extension("tex");
}
}
if self.recursive {
let files_tmp = if self.files.is_empty() {
vec![PathBuf::from("./")]
} else {
self.files.clone()
};
for file in &files_tmp {
if file.is_dir() {
find_files(file, &mut self.files);
}
}
self.files.retain(|e| e.is_file());
}
if !self.recursive && self.files.iter().any(|e| e.is_dir()) {
record_file_log(
logs,
Level::Error,
&empty_path,
"A directory was passed without --recursive.",
);
exit_code = 1;
}
if !self.stdin && self.files.is_empty() {
record_file_log(
logs,
Level::Error,
&empty_path,
"No files specified. Provide filenames, or pass --recursive or --stdin.",
);
exit_code = 1;
}
if self.stdin && !self.files.is_empty() {
record_file_log(
logs,
Level::Error,
&empty_path,
"Do not provide file name(s) when using --stdin.",
);
exit_code = 1;
}
self.lists.dedup();
self.verbatims.dedup();
self.no_indent_envs.dedup();
self.wrap_chars.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 {
let wrap_chars: Vec<String> = self
.wrap_chars
.iter()
.map(std::string::ToString::to_string)
.collect();
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, "verbatims", f)?;
display_args_list(&self.no_indent_envs, "no-indent-envs", f)?;
display_args_list(&wrap_chars, "wrap-chars", f)?;
display_arg_line(f, "format-tables", &self.format_tables.to_string())?;
display_args_list(
&self
.files
.clone()
.into_iter()
.map(|e| e.into_os_string().into_string().unwrap())
.collect::<Vec<String>>(),
"files",
f,
)?;
Ok(())
}
}