use crate::cli::Cli; use crate::generator::config::{ Config as GeneratorConfig, ProvidedConfig as ProvidedGeneratorConfig, }; use crate::parser::clang::config::{Config as ClangConfig, ProvidedConfig as ProvidedClangConfig}; 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, Serialize)] pub(crate) struct Config { pub(crate) name: String, pub(crate) generator: GeneratorConfig, pub(crate) clang: ClangConfig, } impl Default for Config { fn default() -> Self { Config { name: String::from("My Project"), clang: ClangConfig::default(), generator: GeneratorConfig::default(), } } } impl Config { pub(crate) fn from_merge(cli: Cli, conf: ProvidedConfig) -> Result { Ok(Config { name: cli .common_options .name .or(conf.name) .unwrap_or_else(|| String::from("My Project")), clang: ClangConfig::from_merge(cli.common_options.clang, conf.clang)?, generator: GeneratorConfig::from_merge(cli.common_options.generator, conf.generator), }) } } /// Same as ProvidedConfig but with options for when not user-provided #[derive(Debug, Clone, Default, StructOpt, Deserialize)] #[structopt(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")] pub(crate) struct ProvidedConfig { #[structopt(skip)] pub(crate) name: Option, #[structopt(flatten)] #[serde(default)] pub(crate) clang: ProvidedClangConfig, #[structopt(flatten)] #[serde(default)] pub(crate) generator: ProvidedGeneratorConfig, } 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, } } }