use crate::cli::Cli; use anyhow::{anyhow, Context, Result}; use codemap::CodeMap; use codemap_diagnostic::Diagnostic; use serde_derive::{Deserialize, Serialize}; use structopt::StructOpt; use thiserror::Error; use std::path::{Path, PathBuf}; use std::sync::Arc; pub(super) const DEFAULT_PROJECT_CONFIGURATION_FILE_NAME: &str = "poseidoc.toml"; #[derive(Debug, Clone, StructOpt, Deserialize, Serialize)] #[structopt(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case", default)] pub(crate) struct Config { #[structopt(skip)] pub(crate) name: Option, #[structopt(long)] pub(crate) compile_commands_location: Option, #[structopt(skip = vec![])] pub(crate) extra_clang_args: Vec, #[structopt(flatten)] pub(crate) class: ClassConfig, } impl Config { pub(crate) fn merge_cli(self, cli: Cli) -> Self { Config { name: cli.common_options.name.or(self.name), compile_commands_location: cli .common_options .compile_commands_location .or(self.compile_commands_location), extra_clang_args: cli .extra_arg .into_iter() .flatten() .chain(self.extra_clang_args) .collect(), class: self.class.merge_cli(cli.common_options.class), } } } impl Default for Config { fn default() -> Self { Config { name: Some(String::from("My Project")), compile_commands_location: Some(PathBuf::from(r".")), extra_clang_args: Vec::new(), class: ClassConfig::default(), } } } #[derive(Debug, Clone, StructOpt, Deserialize, Serialize)] #[structopt(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case", default)] pub(crate) struct ClassConfig { } impl ClassConfig { fn merge_cli(self, _cli: ClassConfig) -> Self { ClassConfig { } } } impl Default for ClassConfig { fn default() -> Self { ClassConfig { } } } pub(super) fn load_config(location: impl AsRef, codemap: &mut CodeMap) -> Result { let location = location.as_ref(); let final_path = if location.is_dir() { location.join(DEFAULT_PROJECT_CONFIGURATION_FILE_NAME) } else if !location.exists() { return Err(anyhow!("File {:?} does not exists", location)) .with_context(|| format!("Failed to open project configuration: {:?}", location)); } else { location.to_owned() }; let config_str = std::fs::read_to_string(&final_path).map_err(|err| ConfigLoadError::IoError { file_name: final_path.clone(), source: err, })?; Ok( toml::from_str(&config_str).map_err(|err| ConfigLoadError::TomlDeserialzation { file: codemap.add_file(final_path.to_string_lossy().into(), config_str), source: err, })?, ) } #[derive(Debug, Error)] #[error("Error loading project configuration")] pub(crate) enum ConfigLoadError { #[error("Failed to read project configuration: {:?}", file_name)] IoError { file_name: PathBuf, source: std::io::Error, }, #[error("Failed to parse project configuration: {:?}", file.name())] TomlDeserialzation { file: Arc, source: toml::de::Error, }, } impl From<&ConfigLoadError> for Diagnostic { fn from(err: &ConfigLoadError) -> Diagnostic { use codemap_diagnostic::{Level, SpanLabel, SpanStyle}; use std::convert::TryInto; let message = err.to_string(); let spans = match err { ConfigLoadError::IoError { .. } => vec![], ConfigLoadError::TomlDeserialzation { file, source, .. } => { if let Some((line, col)) = source.line_col() { let line_span = file.line_span(line); let col = col.try_into().unwrap(); let span = line_span.subspan(col, col); vec![SpanLabel { span, label: None, style: SpanStyle::Primary, }] } else { vec![] } } }; Diagnostic { level: Level::Error, message, code: None, spans, } } }