use crate::core::{Convention, Matcher};
use crate::error::Error;
use serde::Deserialize;
use std::path::{Path, PathBuf};
use std::str::FromStr;
#[derive(Debug, Deserialize)]
struct CargoManifest {
package: Option<MetadataWrapper>,
workspace: Option<MetadataWrapper>,
}
#[derive(Debug, Deserialize)]
struct MetadataWrapper {
metadata: Option<MetadataSection>,
}
#[derive(Debug, Deserialize)]
struct MetadataSection {
#[serde(rename = "convention-lint")]
convention_lint: Option<ConventionLintTable>,
}
#[derive(Debug, Deserialize)]
struct ConventionLintTable {
checks: Vec<RawCheck>,
}
const fn default_recursive() -> bool {
true
}
#[derive(Debug, Deserialize)]
struct RawCheck {
dirs: Vec<PathBuf>,
#[serde(default)]
include: Vec<String>,
#[serde(default)]
exclude: Vec<String>,
format: String,
#[serde(default = "default_recursive")]
recursive: bool,
}
#[derive(Debug)]
pub struct Config {
pub rules: Vec<ResolvedRule>,
}
#[derive(Debug)]
pub struct ResolvedRule {
pub dirs: Vec<PathBuf>,
pub matcher: Matcher,
pub convention: Convention,
pub recursive: bool,
}
pub fn load_config(manifest_path: &Path) -> Result<Config, Error> {
let content = std::fs::read_to_string(manifest_path).map_err(|source| Error::Io {
path: manifest_path.to_owned(),
source,
})?;
let manifest: CargoManifest = toml::from_str(&content).map_err(|source| Error::Toml {
path: manifest_path.to_owned(),
source,
})?;
let mut all_raw_checks = Vec::new();
if let Some(checks) = manifest
.package
.and_then(|p| p.metadata)
.and_then(|m| m.convention_lint)
.map(|cl| cl.checks)
{
all_raw_checks.extend(checks);
}
if let Some(checks) = manifest
.workspace
.and_then(|w| w.metadata)
.and_then(|m| m.convention_lint)
.map(|cl| cl.checks)
{
all_raw_checks.extend(checks);
}
if all_raw_checks.is_empty() {
return Err(Error::MissingSection(manifest_path.to_owned()));
}
let mut rules = Vec::new();
for raw in all_raw_checks {
if raw.dirs.is_empty() {
return Err(Error::EmptyDirs);
}
let error_context = if raw.include.is_empty() {
"all files".to_string()
} else {
raw.include.join(", ")
};
let convention =
Convention::from_str(&raw.format).map_err(|_| Error::UnknownConvention {
context: error_context,
value: raw.format.clone(),
})?;
let matcher =
Matcher::new(&raw.include, &raw.exclude).map_err(|_| Error::InvalidSection)?;
rules.push(ResolvedRule {
dirs: raw.dirs,
matcher,
convention,
recursive: raw.recursive,
});
}
Ok(Config { rules })
}