Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to sort changelog entries alphabetically #147

Merged
merged 4 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changelog/unreleased/features/147-sort-changelog-entries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- Add configuration file section `[change_set_sections]` with parameter
`sort_entries_by` to sort entries in change set sections either by issue/PR
number (`id`; default), or alphabetically (`entry-text`)
([\#147](https://github.com/informalsystems/unclog/pull/147))
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ folder = "unreleased"
heading = "## Unreleased"


# Settings relating to sets (groups) of changes in the changelog. For example,
# the "BREAKING CHANGES" section would be considered a change set.
# Settings relating to sets (groups) of changes in the changelog. For example, a
# particular version of the software (e.g. "v1.0.0") is typically a change set.
[change_sets]

# The filename containing a summary of the intended changes. Relative to the
Expand All @@ -296,6 +296,16 @@ summary_filename = "summary.md"
entry_ext = "md"


# Settings relating to all sections within a change set. For example, the
# "BREAKING CHANGES" section for a particular release is a change set section.
[change_set_sections]

# Sort entries by a particular property. Possible values include:
# - `id` : The issue/PR number (the default value).
# - `entry-text` : The entry text itself.
sort_entries_by = "id"


# Settings related to components/sub-modules. Only relevant if you make use of
# components/sub-modules.
[components]
Expand Down
2 changes: 1 addition & 1 deletion src/changelog/change_set_section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl ChangeSetSection {
// Component sections must be sorted by ID
component_sections.sort_by(|a, b| a.id.cmp(&b.id));
let entry_files = read_and_filter_dir(path, |e| entry_filter(config, e))?;
let entries = read_entries_sorted(entry_files)?;
let entries = read_entries_sorted(entry_files, config)?;
Ok(Self {
title,
entries,
Expand Down
2 changes: 1 addition & 1 deletion src/changelog/component_section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl ComponentSection {
None => warn!("No path for component \"{}\"", id),
}
let entry_files = read_and_filter_dir(path, |e| entry_filter(config, e))?;
let entries = read_entries_sorted(entry_files)?;
let entries = read_entries_sorted(entry_files, config)?;
Ok(Self {
id,
name,
Expand Down
28 changes: 25 additions & 3 deletions src/changelog/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ pub struct Config {
/// Configuration relating to sets of changes.
#[serde(default, skip_serializing_if = "is_default")]
pub change_sets: ChangeSetsConfig,
/// Configuration relating to change set sections.
#[serde(default, skip_serializing_if = "is_default")]
pub change_set_sections: ChangeSetSectionsConfig,
/// Configuration relating to components/submodules.
#[serde(default, skip_serializing_if = "is_default")]
pub components: ComponentsConfig,
Expand All @@ -93,9 +96,10 @@ impl Default for Config {
empty_msg: Self::default_empty_msg(),
prologue_filename: Self::default_prologue_filename(),
epilogue_filename: Self::default_epilogue_filename(),
unreleased: UnreleasedConfig::default(),
change_sets: ChangeSetsConfig::default(),
components: ComponentsConfig::default(),
unreleased: Default::default(),
change_sets: Default::default(),
change_set_sections: Default::default(),
components: Default::default(),
}
}
}
Expand Down Expand Up @@ -298,6 +302,13 @@ impl ChangeSetsConfig {
}
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ChangeSetSectionsConfig {
/// Sort entries in change set sections by a specific property.
#[serde(default, skip_serializing_if = "is_default")]
pub sort_entries_by: SortEntriesBy,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ComponentsConfig {
#[serde(
Expand Down Expand Up @@ -349,3 +360,14 @@ where
{
D::default().eq(v)
}

/// Allows for configuration of how entries are to be sorted within change set
/// sections.
#[derive(Debug, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SortEntriesBy {
#[serde(rename = "id")]
#[default]
ID,
#[serde(rename = "entry-text")]
EntryText,
}
14 changes: 11 additions & 3 deletions src/changelog/entry.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use crate::changelog::fs_utils::{path_to_str, read_to_string};
use crate::changelog::parsing_utils::trim_newlines;
use crate::{Error, Result};
use crate::{Config, Error, Result};
use log::debug;
use std::ffi::OsStr;
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use super::config::SortEntriesBy;

/// A single entry in a set of changes.
#[derive(Debug, Clone)]
pub struct Entry {
Expand Down Expand Up @@ -49,13 +51,19 @@ fn extract_entry_id<S: AsRef<str>>(s: S) -> Result<u64> {
Ok(u64::from_str(digits)?)
}

pub(crate) fn read_entries_sorted(entry_files: Vec<PathBuf>) -> Result<Vec<Entry>> {
pub(crate) fn read_entries_sorted(
entry_files: Vec<PathBuf>,
config: &Config,
) -> Result<Vec<Entry>> {
let mut entries = entry_files
.into_iter()
.map(Entry::read_from_file)
.collect::<Result<Vec<Entry>>>()?;
// Sort entries by ID in ascending numeric order.
entries.sort_by(|a, b| a.id.cmp(&b.id));
entries.sort_by(|a, b| match config.change_set_sections.sort_entries_by {
SortEntriesBy::ID => a.id.cmp(&b.id),
SortEntriesBy::EntryText => a.details.cmp(&b.details),
});
Ok(entries)
}

Expand Down
92 changes: 92 additions & 0 deletions tests/full/expected-sorted-by-entry-text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# CHANGELOG

This goes at the BEGINNING of the changelog.

## Unreleased

### FEATURES

- Travel through space as a beneficial example

### IMPROVEMENTS

- Eat the profile

## v0.2.1

*31 Mar 2021*

### BREAKING CHANGES

- [Component 2](2nd-component)
- Gargle the truffle
- Laugh at the gaggle
- Travel the gravel

### FEATURES

- General
- Carry the wobbles
- Nibble the bubbles
- component1
- Fasten the handles
- Hasten the sandals
- [Component 2](2nd-component)
- Drizzle the funnel
- Waggle the juggle

## v0.2.0

*27 Feb 2021*

It's finally out, yay!

### BREAKING CHANGES

- Educate the specialist vigorously
- Let the tune meet the unlawful disaster

### FEATURES

- Attend the entry with an ambitious blank
- Stir the engineer with the foolish sound

## v0.2.0-beta

*13 Feb 2021*

This is the second pre-release of v0.2.0.

### FEATURES

- Balance the antique garbage
- Spark the chair in the storm

### IMPROVEMENTS

- Allow the fan to meet his shoe

## v0.2.0-alpha

*3 Feb 2021*

This is the first pre-release of our upcoming v0.2.0 release.

### BREAKING CHANGES

- Add serene brown drops to the scattered magazine
- Eat the resort and cry
- Tick the effect in actual chemicals

### IMPROVEMENTS

- Hover over the historian with a melodic mix
that travels over multiple lines.

## v0.1.0

*8 Jan 2021*

This is our first release!

This goes at the end of the CHANGELOG.
19 changes: 19 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ component2 = { name = "Component 2", path = "2nd-component" }
assert_eq!(expected, changelog.render_released(&config));
}

#[test]
fn full_sorted_by_entry_text() {
const CONFIG_FILE: &str = r#"
[change_set_sections]
sort_entries_by = "entry-text"

[components.all]
component1 = { name = "component1" }
component2 = { name = "Component 2", path = "2nd-component" }
"#;

init_logger();
let config = toml::from_str(CONFIG_FILE).unwrap();
let changelog = Changelog::read_from_dir(&config, "./tests/full").unwrap();
let expected =
std::fs::read_to_string("./tests/full/expected-sorted-by-entry-text.md").unwrap();
assert_eq!(expected, changelog.render_all(&config));
}

#[test]
fn change_template_rendering() {
init_logger();
Expand Down
Loading