use config;
use commit::Commit;
use config::Configuration;
use std::collections::{HashMap, HashSet};
#[derive(Serialize, Debug)]
pub struct Report<'a> {
pub scopes: Vec<Scope>,
pub commits: &'a [Commit],
}
#[derive(Serialize, Debug)]
pub struct Scope {
pub title: String,
pub categories: Vec<Category>,
}
#[derive(Serialize, Debug)]
pub struct Category {
pub title: String,
pub changes: Vec<Text>,
}
#[derive(Serialize, Clone, Debug)]
pub struct Text {
pub sequence: u32,
pub opening: String,
pub rest: Vec<String>,
}
type RawReport = HashMap<String, HashMap<String, Vec<Text>>>;
#[derive(Default, Clone, Serialize)]
struct State {
text: Vec<String>,
scope: Option<String>,
category: Option<String>,
}
pub fn generate<'a>(config: &'a Configuration, commits: &'a [Commit]) -> Report<'a> {
let raw_report = first_pass(config, commits);
let scopes = second_pass(config, &raw_report);
Report { commits, scopes }
}
fn first_pass(config: &Configuration, commits: &[Commit]) -> RawReport {
let mut raw_report = RawReport::new();
let mut sequence = 0;
for commit in commits {
let mut current = State::default();
for line in &commit.lines {
if line.category.is_some() {
record(&mut raw_report, config, current.clone(), &mut sequence);
current.text = Vec::new();
current.scope = line.scope.clone();
current.category = line.category.clone();
}
current.text.push(line.text.clone().unwrap_or_default());
}
record(&mut raw_report, config, current, &mut sequence);
}
debug!("RAW_REPORT: {:#?}", raw_report);
raw_report
}
fn second_pass(config: &Configuration, report: &RawReport) -> Vec<Scope> {
let mut scopes = Vec::new();
let mut processed_scopes = HashSet::new();
for scope in &config.scopes {
if let Some(categorized) = report.get(&scope.title) {
let mut categories = Vec::new();
let mut processed_categories = HashSet::new();
if processed_scopes.contains(&scope.title) {
continue;
}
processed_scopes.insert(&scope.title);
for category in &config.categories {
if processed_categories.contains(&category.title) {
continue;
}
processed_categories.insert(&category.title);
if let Some(changes) = categorized.get(&category.title) {
let title = category.title.clone();
let mut changes = changes.clone();
changes.sort_by(|a, b| a.sequence.cmp(&b.sequence));
categories.push(Category { title, changes });
}
}
scopes.push(Scope {
title: scope.title.clone(),
categories,
});
}
}
scopes
}
fn record(raw: &mut RawReport, config: &Configuration, mut state: State, seq: &mut u32) {
let scope = config::report_title(&config.scopes, &state.scope);
let category = config::report_title(&config.categories, &state.category);
if category.is_some() && scope.is_some() && !state.text.is_empty() {
let mut opening = state.text.remove(0);
while opening.trim().is_empty() {
opening = state.text.remove(0)
}
if !opening.starts_with(' ') {
opening.insert(0, ' ');
}
let rest = state.text;
*seq += 1;
raw.entry(scope.unwrap())
.or_insert_with(HashMap::new)
.entry(category.unwrap())
.or_insert_with(Vec::new)
.push(Text {
sequence: *seq,
opening,
rest,
});
}
}