use crate::{
Fragment, FromRon, PatternReader, PatternWriter, RonlogAction,
RonlogReferences, RonlogSection, ToRon,
};
use std::path::PathBuf;
use sysexits::{ExitCode, Result};
#[derive(serde::Deserialize, serde::Serialize)]
struct Changelog {
references: RonlogReferences,
introduction: Option<String>,
sections: Vec<RonlogSection>,
}
impl Changelog {
fn add_section(&mut self, section: RonlogSection) {
for s in &mut self.sections {
if s == §ion {
s.merge(section);
return;
}
}
self
.sections
.insert(self.sections.partition_point(|s| s > §ion), section);
}
fn init(
path: &PathBuf,
message: Option<String>,
references: RonlogReferences,
force: bool,
) -> Result<bool> {
let result = !path.exists();
if result || force {
path.truncate(Box::new(Self::new(message, references).to_ron(2)?))?;
}
Ok(result)
}
#[must_use]
const fn new(
introduction: Option<String>,
references: RonlogReferences,
) -> Self {
Self {
references,
introduction,
sections: Vec::new(),
}
}
}
struct Logic {
cli: Ronlog,
hyperlinks: RonlogReferences,
}
impl Logic {
fn init(&self, message: Option<String>) -> Result<()> {
if Changelog::init(
&self.cli.output_file,
message,
self.hyperlinks.clone(),
self.cli.force,
)? {
println!(
"Successfully initialised new CHANGELOG in '{}'.",
self.cli.output_file.display()
);
Ok(())
} else if self.cli.force {
println!(
"Successfully re-initialised CHANGELOG in '{}'.",
self.cli.output_file.display()
);
Ok(())
} else {
println!(
"Use `--force` to overwrite the existing CHANGELOG in '{}'.",
self.cli.output_file.display()
);
Err(ExitCode::Usage)
}
}
fn main(&mut self) -> Result<()> {
self.hyperlinks = self
.cli
.link
.iter()
.zip(self.cli.target.iter())
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect();
match self.cli.action {
RonlogAction::Init => self.init(self.cli.message.clone()),
RonlogAction::Release => self.release(),
}
}
fn release(&self) -> Result<()> {
if let Some(version) = &self.cli.version {
let mut section = RonlogSection::new(
Fragment::default(),
version,
self.cli.message.clone(),
if self.hyperlinks.is_empty() {
None
} else {
Some(self.hyperlinks.clone())
},
)?;
if !self.cli.output_file.exists() {
self.init(None)?;
}
for entry in std::fs::read_dir(&self.cli.input_directory)? {
let entry = entry?.path();
if entry
.extension()
.map_or(false, |e| e.to_str().map_or(false, |e| e == "ron"))
{
if let Ok(fragment) =
Fragment::from_ron(&entry.read()?.try_into_string()?)
{
section.add_changes(fragment);
std::fs::remove_file(entry)?;
}
}
}
let mut ronlog =
Changelog::from_ron(&self.cli.output_file.read()?.try_into_string()?)?;
for (link, target) in section.move_references() {
ronlog
.references
.entry(link)
.and_modify(|t| *t = target.clone())
.or_insert(target);
}
ronlog.add_section(section);
self.cli.output_file.truncate(Box::new(ronlog.to_ron(2)?))
} else {
eprintln!("No `--version` information provided for this mode.");
Err(ExitCode::Usage)
}
}
}
#[derive(clap::Parser, Clone)]
pub struct Ronlog {
action: RonlogAction,
#[arg(long, short)]
force: bool,
#[arg(default_value = ".", long = "input", short)]
input_directory: String,
#[arg(long, short)]
message: Option<String>,
#[arg(aliases = ["hyperlink"], long, short)]
link: Vec<String>,
#[arg(default_value = "CHANGELOG.ron", long = "output", short)]
output_file: PathBuf,
#[arg(long, short)]
target: Vec<String>,
#[arg(long, short)]
version: Option<String>,
}
impl Ronlog {
pub fn main(&self) -> Result<()> {
self.wrap().main()
}
fn wrap(&self) -> Logic {
Logic {
cli: self.clone(),
hyperlinks: RonlogReferences::new(),
}
}
}