use crate::args::{OptionArgs, TabChar};
use dirs::config_dir;
use log::LevelFilter;
use std::env::current_dir;
use std::fs::{metadata, read_to_string};
use std::path::PathBuf;
use toml::Table;
const CONFIG: &str = "tex-fmt.toml";
fn resolve_config_path(args: &OptionArgs) -> Option<PathBuf> {
if args.noconfig == Some(true) {
return None;
}
if args.config.is_some() {
return args.config.clone();
}
if let Ok(mut config) = current_dir() {
config.push(CONFIG);
if config.exists() {
return Some(config);
}
}
if let Some(mut config) = find_git_root() {
config.push(CONFIG);
if config.exists() {
return Some(config);
}
}
if let Some(mut config) = config_dir() {
config.push("tex-fmt");
config.push(CONFIG);
if config.exists() {
return Some(config);
}
}
None
}
fn find_git_root() -> Option<PathBuf> {
let mut depth = 0;
let mut current_dir = current_dir().unwrap();
while depth < 100 {
depth += 1;
if metadata(current_dir.join(".git"))
.map(|m| m.is_dir())
.unwrap_or(false)
{
return Some(current_dir);
}
if !current_dir.pop() {
break;
}
}
None
}
#[must_use]
pub fn get_config(args: &OptionArgs) -> Option<(PathBuf, String, String)> {
let config_path = resolve_config_path(args);
config_path.as_ref()?;
let config_path_string = config_path
.clone()
.unwrap()
.into_os_string()
.into_string()
.unwrap();
let config = read_to_string(config_path.clone().unwrap()).unwrap();
Some((config_path.unwrap(), config_path_string, config))
}
fn parse_array_string(name: &str, config: &Table) -> Vec<String> {
config
.get(name)
.and_then(|v| v.as_array())
.unwrap_or(&vec![])
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
}
fn string_to_char(s: &str) -> char {
let mut chars = s.chars();
let c = chars.next().expect("String is empty");
assert!(
chars.next().is_none(),
"String contains more than one character",
);
c
}
#[must_use]
pub fn get_config_args(
config: Option<(PathBuf, String, String)>,
) -> Option<OptionArgs> {
config.as_ref()?;
let (config_path, config_path_string, config) = config.unwrap();
let config = config.parse::<Table>().unwrap_or_else(|_| {
panic!("Failed to read config file at {config_path_string}")
});
let verbosity = match config.get("verbosity").map(|x| x.as_str().unwrap()) {
Some("error" | "quiet") => Some(LevelFilter::Error),
Some("warn") => Some(LevelFilter::Warn),
Some("info" | "verbose") => Some(LevelFilter::Info),
Some("trace") => Some(LevelFilter::Trace),
_ => None,
};
let tabchar = match config.get("tabchar").map(|x| x.as_str().unwrap()) {
Some("tab") => Some(TabChar::Tab),
Some("space") => Some(TabChar::Space),
_ => None,
};
let wrap_chars: Vec<char> = parse_array_string("wrap-chars", &config)
.iter()
.map(|c| string_to_char(c))
.collect();
let args = OptionArgs {
check: config.get("check").map(|x| x.as_bool().unwrap()),
print: config.get("print").map(|x| x.as_bool().unwrap()),
fail_on_change: config
.get("fail-on-change")
.map(|x| x.as_bool().unwrap()),
wrap: config.get("wrap").map(|x| x.as_bool().unwrap()),
wraplen: config
.get("wraplen")
.map(|x| x.as_integer().unwrap().try_into().unwrap()),
wrapmin: config
.get("wrapmin")
.map(|x| x.as_integer().unwrap().try_into().unwrap()),
tabsize: config
.get("tabsize")
.map(|x| x.as_integer().unwrap().try_into().unwrap()),
tabchar,
stdin: config.get("stdin").map(|x| x.as_bool().unwrap()),
config: Some(config_path),
noconfig: None,
lists: parse_array_string("lists", &config),
verbatims: parse_array_string("verbatims", &config),
no_indent_envs: parse_array_string("no-indent-envs", &config),
wrap_chars,
verbosity,
arguments: None,
files: vec![],
recursive: None,
format_tables: config
.get("format-tables")
.map(|x| x.as_bool().unwrap()),
};
Some(args)
}