use crate::{AppendAsLine, PatternWriter};
use git2::Repository;
use std::{collections::HashMap, path::PathBuf};
use sysexits::{ExitCode, Result};
pub struct CommentChanges {
changes: HashMap<String, Vec<String>>,
delimiter: String,
depth: Option<usize>,
hyperlinks: Vec<(String, String)>,
repository: Option<Repository>,
}
impl CommentChanges {
pub fn branch_name(&mut self) -> Result<String> {
if self.repository.is_none() {
self.open_repository()?;
}
let Some(repository) = &self.repository else { unreachable!() };
repository.head().map_or_else(
|error| {
eprintln!("{error}");
Err(ExitCode::Unavailable)
},
|reference| {
reference
.name()
.map_or(Err(ExitCode::Unavailable), |name| Ok(name.to_string()))
},
)
}
#[must_use]
pub fn generate_changelog_fragment(&self) -> String {
let mut result = String::new();
for (link_name, target) in &self.hyperlinks {
result.append_as_line(format!(".. {link_name}: {target}"));
}
if !self.hyperlinks.is_empty() {
result.push('\n');
}
for (category, changes) in &self.changes {
result.append_as_line(format!(
"{category}\n{}\n",
".".repeat(category.len())
));
for change in changes {
result.append_as_line(format!("- {change}\n"));
}
}
result
}
pub fn main(&mut self, output_directory: &str) -> Result<()> {
self.update_changes()?;
self.report_changes(output_directory)
}
#[must_use]
pub fn new(
depth: Option<usize>,
delimiter: String,
hyperlinks: Vec<(String, String)>,
) -> Self {
Self {
changes: HashMap::new(),
delimiter,
depth,
hyperlinks,
repository: None,
}
}
pub fn open_repository(&mut self) -> Result<()> {
Repository::open(".").map_or_else(
|_| {
eprintln!("This is not a Git repository.");
Err(ExitCode::Usage)
},
|repository| {
self.repository = Some(repository);
Ok(())
},
)
}
pub fn query_last_n_commits(
&mut self,
) -> Result<HashMap<String, Vec<String>>> {
if self.repository.is_none() {
self.open_repository()?;
}
let Some(repository) = &self.repository else { unreachable!() };
let mut result = HashMap::new();
match repository.revwalk() {
Ok(mut revwalk) => match revwalk.push_head() {
Ok(()) => {
let mut count = 1;
for oid in revwalk {
if let Some(depth) = self.depth {
if count > depth {
break;
}
}
if let Ok(oid) = oid {
if let Ok(commit) = repository.find_commit(oid) {
match commit.summary() {
Some(summary) => {
if let Some((category, change)) =
summary.trim().split_once(&self.delimiter)
{
let category = category.trim().to_string();
let change = change.trim().to_string();
if !result.contains_key(&category) {
result.insert(category.clone(), Vec::new());
}
let mut changes = result[&category].clone();
changes.push(change);
result.insert(category, changes);
}
}
None => return Err(ExitCode::Unavailable),
}
} else {
eprintln!("Commit {oid} does not seem to exist.");
return Err(ExitCode::DataErr);
}
} else {
eprintln!("There were not enough commits fetched on checkout.");
return Err(ExitCode::Usage);
}
count += 1;
}
Ok(result)
}
Err(error) => {
eprintln!("{error}");
Err(ExitCode::Unavailable)
}
},
Err(error) => {
eprintln!("{error}");
Err(ExitCode::Unavailable)
}
}
}
pub fn report_changes(&mut self, output_directory: &str) -> Result<()> {
let branch = self.branch_name()?;
let user = self.who_am_i()?.replace(' ', "_");
PathBuf::from(format!(
"{output_directory}/{}_{user}_{}.rst",
chrono::Local::now().format("%Y%m%d_%H%M%S"),
branch.split('/').last().unwrap_or("HEAD")
))
.write(Box::new(self.generate_changelog_fragment()))
}
pub fn update_changes(&mut self) -> Result<()> {
self.changes = self.query_last_n_commits()?;
Ok(())
}
pub fn who_am_i(&mut self) -> Result<String> {
if self.repository.is_none() {
self.open_repository()?;
}
let Some(repository) = &self.repository else { unreachable!() };
repository.config().map_or_else(
|error| {
eprintln!("{error}");
Err(ExitCode::Unavailable)
},
|config| {
config.get_string("user.name").map_or_else(
|_| {
eprintln!("There is no Git username configured, yet.");
Err(ExitCode::DataErr)
},
Ok,
)
},
)
}
}