use std::path::PathBuf;
use config::Config;
use serde::Deserialize;
use crate::parser::Commit;
pub mod scope_empty;
pub mod scope_enum;
pub mod scope_max_length;
pub trait Rule {
fn run(&self, commit: &Commit) -> Option<miette::Report>;
}
#[derive(Debug, Deserialize, PartialEq)]
pub enum Severity {
#[serde(rename = "off")]
Off,
#[serde(rename = "warning")]
Warning,
#[serde(rename = "error")]
Error,
}
#[derive(Debug, Deserialize)]
pub enum Condition {
#[serde(rename = "never")]
Never,
#[serde(rename = "always")]
Always,
}
#[derive(Debug, Deserialize)]
enum TargetCase {
#[serde(rename = "lower-case")]
Lower,
#[serde(rename = "upper-case")]
Upper,
#[serde(rename = "pascal-case")]
Pascal,
#[serde(rename = "camel-case")]
Camel,
#[serde(rename = "kebab-case")]
Kebab,
#[serde(rename = "snake-case")]
Snake,
#[serde(rename = "start-case")]
Start,
#[serde(rename = "sentence-case")]
Sentence,
}
#[derive(Debug, Deserialize)]
pub struct NoOpts(Severity, Condition);
#[derive(Debug, Deserialize)]
pub struct EnumOpts(Severity, Condition, Vec<String>);
#[derive(Debug, Deserialize)]
pub struct LengthOpts(Severity, usize);
#[derive(Debug, Deserialize)]
pub struct CaseOpts(Severity, Condition, TargetCase);
#[derive(Debug, Deserialize)]
struct RulesDetails {
#[serde(rename = "scope-empty")]
scope_empty: NoOpts,
#[serde(rename = "scope-enum")]
scope_enum: EnumOpts,
#[serde(rename = "scope-max-length")]
scope_max_length: LengthOpts,
}
#[derive(Debug, Deserialize)]
struct RulesConfig {
rules: RulesDetails,
}
pub struct LintResult {
errors: Option<Vec<miette::Report>>,
warnings: Option<Vec<miette::Report>>,
}
impl LintResult {
pub fn errors(&self) -> Option<&Vec<miette::Report>> {
self.errors.as_ref()
}
pub fn errors_len(&self) -> usize {
match self.errors() {
None => 0,
Some(errors) => errors.len(),
}
}
pub fn has_errors(&self) -> bool {
self.errors.is_some() && !self.errors().unwrap().is_empty()
}
pub fn warnings(&self) -> Option<&Vec<miette::Report>> {
self.warnings.as_ref()
}
pub fn warnings_len(&self) -> usize {
match self.warnings() {
None => 0,
Some(warnings) => warnings.len(),
}
}
pub fn has_warnings(&self) -> bool {
self.warnings.is_some() && !self.warnings().unwrap().is_empty()
}
}
pub fn run(commit: &Commit, config_path: PathBuf) -> LintResult {
let settings = Config::builder()
.add_source(config::File::from_str(
r#"
[rules]
scope-empty = ["error", "never"]
scope-enum = ["error", "always", ["foo", "bar", "baz"]]
scope-max-length = ["error", 20]
scope-case = ["error", "always", "lower-case"]
"#,
config::FileFormat::Toml,
))
.add_source(config::File::from(config_path).required(false))
.build()
.unwrap();
let config: RulesConfig = settings.try_deserialize::<RulesConfig>().unwrap();
println!("{:?}", config);
let rules: Vec<Box<dyn Rule>> = vec![
Box::new(scope_empty::ScopeEmptyRule {
opts: config.rules.scope_empty,
}),
Box::new(scope_enum::ScopeEnumRule {
opts: config.rules.scope_enum,
}),
Box::new(scope_max_length::ScopeMaxLengthRule {
opts: config.rules.scope_max_length,
}),
];
let mut lint_result = LintResult {
errors: None,
warnings: None,
};
for rule in rules {
if let Some(report) = rule.run(commit) {
match report.severity() {
Some(miette::Severity::Error) => {
if lint_result.errors.is_none() {
lint_result.errors = Some(vec![]);
}
lint_result.errors.as_mut().unwrap().push(report);
}
Some(miette::Severity::Warning) => {
if lint_result.warnings.is_none() {
lint_result.warnings = Some(vec![]);
}
lint_result.warnings.as_mut().unwrap().push(report);
}
_ => {}
}
}
}
lint_result
}