Add format version manifest, changelog file format tweaks

cl-status
Ondřej Hruška 2 days ago
parent d0af91c8e1
commit a3cd827cf6
  1. 7
      README.md
  2. 76
      src/store.rs

@ -10,10 +10,11 @@ This tool aims to make change logging as streamlined as possible to encourage de
automatically for every bugfix and feature. This can be enforced as part of a CI pipeline.
The entry format is kept simple and readable, so entries can be added manually as well - e.g. if some
contributors can't or don't want to use this tool.
contributors can't or don't want to use this tool. The generated CHANGELOG.md file can be freely edited after clpack
updates it - just keep the header unchanged.
_The tool is meant to be used with Git, but it is not required - it's just more convenient,
as it can pre-fill some information from branch names._
_clpack is meant to be used with Git, where it can pick up information from branch names. Git is, however, optional.
You can use this tool with other VCS or none at all._
## Advantages over keeping the changelog manually

@ -1,18 +1,26 @@
use crate::AppContext;
use crate::config::{ChannelName, Config, EntryName, VersionName};
use anyhow::bail;
use anyhow::{Context, bail};
use colored::Colorize;
use faccess::PathExt;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fs::{OpenOptions, read_to_string};
use std::fs::{File, OpenOptions, read_to_string};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use indexmap::IndexMap;
const DIR_ENTRIES: &str = "entries";
const DIR_CHANNELS: &str = "channels";
const SUPPORTED_FORMAT_VERSION: usize = 1;
#[derive(Debug, Deserialize, Serialize)]
struct Manifest {
/// Versionm of the format
format_version: usize,
}
/// Changelog store struct
pub struct Store<'a> {
/// App context, including config
@ -30,8 +38,8 @@ impl<'a> Store<'a> {
if !store_path.is_dir() {
if init {
// Try to create it
eprintln!("Creating changelog dir: {}", store_path.display());
std::fs::create_dir_all(&store_path)?;
std::fs::create_dir_all(&store_path)
.with_context(|| format!("Creating changelog dir: {}", store_path.display()))?;
} else {
bail!(
"Changelog directory does not exist: {}. Use `{} init` to create it.",
@ -48,6 +56,43 @@ impl<'a> Store<'a> {
);
}
let manifest_path = store_path.join("manifest.json");
if manifest_path.is_file() {
let manifest_file = OpenOptions::new()
.read(true)
.open(&manifest_path)
.with_context(|| format!("Opening manifest file: {}", manifest_path.display()))?;
let manifest: Manifest = serde_json::from_reader(manifest_file)
.with_context(|| format!("Reading manifest file: {}", manifest_path.display()))?;
if manifest.format_version != 1 {
bail!(
"clpack store is in format {}. This version of clpack requires format {}",
manifest.format_version,
SUPPORTED_FORMAT_VERSION
);
}
} else {
println!("Creating clpack manifest file: {}", manifest_path.display());
let manifest_file = OpenOptions::new()
.create_new(true)
.write(true)
.open(&manifest_path)
.with_context(|| {
format!(
"Opening manifest file for writing: {}",
manifest_path.display()
)
})?;
let manifest = Manifest {
format_version: SUPPORTED_FORMAT_VERSION,
};
serde_json::to_writer_pretty(manifest_file, &manifest)
.with_context(|| format!("Writing manifest file: {}", manifest_path.display()))?;
}
let mut store = Self {
store_path,
ctx,
@ -71,8 +116,7 @@ impl<'a> Store<'a> {
/// Check if a changelog entry exists. Filename is passed without extension.
/// This only checks within the current epoch as older files are no longer present.
pub fn entry_exists(&self, name: &str) -> bool {
let path = self.make_entry_path(name);
path.exists()
self.make_entry_path(name).exists()
}
/// Load release lists for all channels
@ -112,8 +156,8 @@ impl<'a> Store<'a> {
subdir.display()
);
}
eprintln!("Creating changelog subdir: {}", subdir.display());
std::fs::create_dir_all(&subdir)?;
std::fs::create_dir_all(&subdir)
.with_context(|| format!("Creating subdir: {}", subdir.display()))?;
}
if !subdir.writable() {
@ -121,7 +165,7 @@ impl<'a> Store<'a> {
}
if gitkeep {
std::fs::File::create(subdir.join(".gitkeep"))?;
File::create(subdir.join(".gitkeep"))?;
}
Ok(())
@ -132,9 +176,10 @@ impl<'a> Store<'a> {
let path = self.make_entry_path(name.as_str());
let mut file = OpenOptions::new().write(true).create(true).open(&path)?;
eprintln!("Writing changelog entry to file: {}", path.display());
println!("Writing changelog entry to file: {}", path.display());
file.write_all(content.as_bytes())?;
file.write_all(content.as_bytes())
.with_context(|| format!("Writing file {}", path.display()))?;
Ok(())
}
@ -268,7 +313,9 @@ impl Release {
}
if line_trimmed.starts_with('#') {
// It is a section name
current_section = line.trim_start_matches(|c| c == '#' || c == ' ').to_string();
current_section = line
.trim_start_matches(|c| c == '#' || c == ' ')
.to_string();
} else {
if let Some(buffer) = entries_per_section.get_mut(&current_section) {
buffer.push('\n');
@ -308,12 +355,13 @@ impl Release {
for (section_name, content) in reordered_sections {
if !section_name.is_empty() {
buffer.push_str(&format!("\n### {}\n\n", section_name));
buffer.push_str(&format!("\n### {}\n", section_name));
}
buffer.push_str(content.trim_end());
buffer.push_str("\n");
}
buffer.push_str("\n");
Ok(buffer)
}
}

Loading…
Cancel
Save