use std::{collections::BTreeMap, io};
use time::OffsetDateTime;
use crate::{clog::Clog, error::Result, fmt::FormatWriter, git::Commit, sectionmap::SectionMap};
pub struct MarkdownWriter<'a>(&'a mut dyn io::Write);
impl<'a> MarkdownWriter<'a> {
pub fn new<T: io::Write + 'a>(writer: &'a mut T) -> MarkdownWriter<'a> {
MarkdownWriter(writer)
}
fn write_header(&mut self, options: &Clog) -> Result<()> {
let subtitle = options.subtitle.clone().unwrap_or_default();
let version = options.version.clone().unwrap_or_default();
let version_text = if options.patch_ver {
format!("### {version} {subtitle}")
} else {
format!("## {version} {subtitle}")
};
let now = OffsetDateTime::now_utc();
let date = now.format(&time::format_description::parse("[year]-[month]-[day]").unwrap())?;
writeln!(
self.0,
"<a name=\"{version}\"></a>\n{version_text} ({date})\n",
)
.map_err(Into::into)
}
fn write_section(
&mut self,
options: &Clog,
title: &str,
section: &BTreeMap<&String, &Vec<Commit>>,
) -> Result<()> {
if section.is_empty() {
return Ok(());
}
self.0
.write_all(format!("\n#### {title}\n\n")[..].as_bytes())?;
for (component, entries) in section.iter() {
let nested = (entries.len() > 1) && !component.is_empty();
let prefix = if nested {
writeln!(self.0, "* **{component}:**")?;
" *".to_owned()
} else if !component.is_empty() {
format!("* **{component}:**")
} else {
"* ".to_string()
};
for entry in entries.iter() {
write!(
self.0,
"{prefix} {} ([{}]({})",
entry.subject,
&entry.hash[0..8],
options
.link_style
.commit_link(&*entry.hash, options.repo.as_deref())
)?;
if !entry.closes.is_empty() {
let closes_string = entry
.closes
.iter()
.map(|s| {
format!(
"[#{s}]({})",
options.link_style.issue_link(s, options.repo.as_ref())
)
})
.collect::<Vec<String>>()
.join(", ");
write!(self.0, ", closes {closes_string}")?;
}
if !entry.breaks.is_empty() {
let breaks_string = entry
.breaks
.iter()
.map(|s| {
format!(
"[#{s}]({})",
options.link_style.issue_link(s, options.repo.as_ref())
)
})
.collect::<Vec<String>>()
.join(", ");
if breaks_string.len() != 5 {
write!(self.0, ", breaks {breaks_string}")?;
}
}
writeln!(self.0, ")")?;
}
}
Ok(())
}
#[allow(dead_code)]
fn write(&mut self, content: &str) -> Result<()> {
write!(self.0, "\n\n\n")?;
write!(self.0, "{}", content).map_err(Into::into)
}
}
impl<'a> FormatWriter for MarkdownWriter<'a> {
fn write_changelog(&mut self, options: &Clog, sm: &SectionMap) -> Result<()> {
self.write_header(options)?;
let s_it = options
.section_map
.keys()
.filter_map(|sec| sm.sections.get(sec).map(|secmap| (sec, secmap)));
for (sec, secmap) in s_it {
self.write_section(
options,
&sec[..],
&secmap.iter().collect::<BTreeMap<_, _>>(),
)?;
}
self.0.flush().map_err(Into::into)
}
}