use std::{
collections::BTreeMap,
env, fs,
path::{Path, PathBuf},
};
use anyhow::Context;
use serde::{ser, Deserialize, Serialize};
use typeshare_model::Language;
use crate::serde::{args::ArgsSetSerializer, config::ConfigDeserializer, empty::EmptyDeserializer};
pub use crate::serde::args::CliArgsSet;
const DEFAULT_CONFIG_FILE_NAME: &str = "typeshare.toml";
#[derive(Debug, Clone, Default, Deserialize)]
pub struct GlobalConfig {
#[serde(default)]
pub target_os: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Config {
#[serde(skip)]
empty: toml::Table,
#[serde(flatten)]
raw_data: BTreeMap<String, toml::Table>,
#[serde(default)]
typeshare: GlobalConfig,
}
impl Config {
pub fn config_for_language(&self, language: &str) -> &toml::Table {
self.raw_data.get(language).unwrap_or(&self.empty)
}
pub fn store_config_for_language<T: Serialize>(
&mut self,
language: &str,
config: &T,
) -> anyhow::Result<()> {
todo!()
}
pub fn global_config(&self) -> &GlobalConfig {
&self.typeshare
}
}
impl Serialize for Config {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
self.raw_data.serialize(serializer)
}
}
pub fn compute_args_set<'a, L: Language<'a>>() -> anyhow::Result<CliArgsSet> {
let empty_config = L::Config::deserialize(EmptyDeserializer).context(
"failed to create empty config; \
did you forget `#[serde(default)]`?",
)?;
let args_set = empty_config
.serialize(ArgsSetSerializer::new(L::NAME))
.context("failed to compute CLI arguments from language configuration type")?;
Ok(args_set)
}
pub fn load_config(file_path: Option<&Path>) -> anyhow::Result<Config> {
let file_path_buf;
let file_path = match file_path {
Some(path) => path,
None => match find_configuration_file() {
None => return Ok(Config::default()),
Some(path) => {
file_path_buf = path;
&file_path_buf
}
},
};
let config_string = fs::read_to_string(file_path).with_context(|| {
format!(
"i/o error reading typeshare config from '{path}'",
path = file_path.display()
)
})?;
toml::from_str(&config_string).with_context(|| {
format!(
"error loading typeshare config from '{path}'",
path = file_path.display()
)
})
}
fn find_configuration_file() -> Option<PathBuf> {
let mut path = env::current_dir().ok()?;
let file = Path::new(DEFAULT_CONFIG_FILE_NAME);
loop {
path.push(file);
if path.is_file() {
break Some(path);
} else if !(path.pop() && path.pop()) {
break None;
}
}
}
pub fn load_language_config<'a, 'config, L: Language<'config>>(
config_file_entry: &'config toml::Table,
cli_matches: &'config clap::ArgMatches,
spec: &'a CliArgsSet,
) -> anyhow::Result<L::Config> {
L::Config::deserialize(ConfigDeserializer::new(
&config_file_entry,
&cli_matches,
spec,
))
.context("error deserializing config")
}
pub fn load_language_config_from_file_and_args<'a, 'config, L: Language<'config>>(
config: &'config Config,
cli_matches: &'config clap::ArgMatches,
spec: &'a CliArgsSet,
) -> anyhow::Result<L::Config> {
load_language_config::<L>(config.config_for_language(L::NAME), cli_matches, spec)
}