Skip to content

Commit

Permalink
Add ability to sort changelog entries alphabetically (#147)
Browse files Browse the repository at this point in the history
* Add ability to sort changelog entries alphabetically

Signed-off-by: Thane Thomson <[email protected]>

* Refactor parameter into [change_set_sections] config section

Signed-off-by: Thane Thomson <[email protected]>

* Document new section/parameter in README

Signed-off-by: Thane Thomson <[email protected]>

* Add changelog entry

Signed-off-by: Thane Thomson <[email protected]>

---------

Signed-off-by: Thane Thomson <[email protected]>
  • Loading branch information
thanethomson authored Dec 2, 2023
1 parent 467a945 commit 7878802
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 10 deletions.
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

0 comments on commit 7878802

Please sign in to comment.