use chrono::Local;
use commit::Commit;
use config::Configuration;
use handlebars::Handlebars;
use std::collections::HashMap;
use serde_json::to_string_pretty;
#[derive(Serialize)]
pub struct Report<'a> {
pub date: String,
pub title: String,
pub scopes: Vec<Scope>,
pub commits: &'a [Commit],
}
#[derive(Serialize)]
pub struct Scope {
pub title: String,
pub categories: Vec<Category>,
}
#[derive(Serialize)]
pub struct Category {
pub title: String,
pub changes: Vec<Text>,
}
#[derive(Serialize, Clone)]
pub struct Text {
pub opening: String,
pub rest: Vec<String>,
}
pub fn generate<'a>(commits: &'a [Commit], config: &'a Configuration) -> Report<'a> {
Report {
commits,
scopes: organize(config, commits),
title: config.report.title.clone(),
date: Local::now().format(&config.report.date_format).to_string(),
}
}
pub fn render(report: &Report, config: &Configuration) -> String {
Handlebars::new()
.template_render(&config.report.template, report)
.unwrap()
.trim()
.to_string()
}
type RawReport = HashMap<String, HashMap<String, Vec<Text>>>;
#[derive(Default, Clone, Serialize)]
struct State {
text: Vec<String>,
scope: Option<String>,
category: Option<String>,
}
fn record(raw: &mut RawReport, config: &Configuration, mut state: State) {
let category = config.categories.validate(state.category);
let scope = config.scopes.validate(state.scope);
if category.is_some() && scope.is_some() && !state.text.is_empty() {
let opening = state.text.remove(0);
let rest = state.text;
raw.entry(scope.unwrap())
.or_insert_with(HashMap::new)
.entry(category.unwrap())
.or_insert_with(Vec::new)
.push(Text { opening, rest });
}
}
fn organize(config: &Configuration, commits: &[Commit]) -> Vec<Scope> {
let report = &mut RawReport::new();
for commit in commits {
let mut current = State::default();
for line in &commit.lines {
if line.category.is_some() {
record(report, config, current.clone());
current.text = Vec::new();
current.scope = line.scope.clone();
current.category = line.category.clone();
}
current.text.push(line.text.clone().unwrap_or_default());
}
record(report, config, current);
}
info!("{}", to_string_pretty(&report).unwrap());
finish(report, config)
}
fn finish(report: &RawReport, config: &Configuration) -> Vec<Scope> {
let mut scopes = Vec::new();
for scope in &config.scopes.tags {
if let Some(categorized) = report.get(scope) {
let mut categories = Vec::new();
for category in &config.categories.tags {
if let Some(changes) = categorized.get(category) {
categories.push(Category {
title: config.categories.get_title(category),
changes: changes.clone(),
});
}
}
scopes.push(Scope {
title: config.scopes.get_title(scope),
categories,
});
}
}
scopes
}