Skip to content

Commit 9525bb2

Browse files
committed
feat: Allow custom change types.
BREAKING CHANGE: `BumpType` renamed to `ChangeType` with many relevant renamings throughout other structs.
1 parent 8200954 commit 9525bb2

8 files changed

Lines changed: 73 additions & 113 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ knope: minor
4444
This is a feature for Knope in the same PR
4545
```
4646

47-
When you release, the `knope` package would contain both summaries in its changelog (and apply the greatest bump type), and the `changesets` package would contain only the first summary in its changelog.
47+
When you release, the `knope` package would contain both summaries in its changelog (and bump the version based on the highest change type), and the `changesets` package would contain only the first summary in its changelog.
4848

4949
This works very similarly to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), but does not rely on Git. You can use this together _with_ conventional commits using a tool like [Knope].
5050

5151
## Terminology in this project
5252

53-
- **Change**: A single Markdown file (usually in the `.changeset` directory) describing a change to one or more packages. Note that this matches the original definition of [changesets]. A change contains a summary (in Markdown), a list of packages affected, and the semver "bump type" for each package.
53+
- **Change**: A single Markdown file (usually in the `.changeset` directory) describing a change to one or more packages. Note that this matches the original definition of [changesets]. A change contains a summary (in Markdown), a list of packages affected, and the semver "change type" for each package.
5454
- **Change summary**: The Markdown description of a change. This is the body of the change file. It should be included in the generated changelog.
55-
- **Bump type**: One of `none`, `patch`, `minor`, or `major`, describing which components of a semantic version are affected by the change.
55+
- **Change type**: A string describing which type of change this is. If it is one of `patch`, `minor`, or `major`, the version will be bumped accordingly. All other types of changes will have no effect on the version, but may be used in the generation of the changelog.
5656
- **Package**: A releasable unit of code. Examples include a Rust crate, a JavaScript package, a Go module. A change can affect multiple packages.
5757
- **Changeset**: A _set_ of _changes_ which will be released together. Notably, this differs from the original definition of [changesets], which is does not have a term for the bundle of multiple changes. A changeset may affect any number of packages.
5858
- **Release**: The part of a changeset that applies to a single package and determines how that package is released.

src/change.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use std::{
22
error::Error,
33
fmt::Display,
44
path::{Path, PathBuf},
5-
str::FromStr,
65
};
76

8-
use crate::{BuildVersioningError, BumpType, BumpTypeParsingError, PackageName, Versioning};
7+
use crate::{BuildVersioningError, ChangeType, PackageName, Versioning};
98

109
/// Represents a single [change](https://github.com/knope-dev/changesets#terminology) which is
1110
/// applicable to any number of packages.
@@ -73,10 +72,10 @@ impl Change {
7372
.split_once(':')
7473
.ok_or(ParsingError::InvalidFrontMatter)?;
7574
let package_name = PackageName::from(parts.0.trim());
76-
let bump_type = BumpType::from_str(parts.1.trim())?;
77-
Ok((package_name, bump_type))
75+
let change_type = ChangeType::from(parts.1.trim());
76+
Ok((package_name, change_type))
7877
})
79-
.collect::<Result<Vec<(String, BumpType)>, ParsingError>>()?;
78+
.collect::<Result<Vec<(String, ChangeType)>, ParsingError>>()?;
8079
let versioning = Versioning::try_from_iter(versioning_iter)?;
8180
let mut lines = lines.skip(versioning.len());
8281
let end_front_matter = lines.next().ok_or(ParsingError::InvalidFrontMatter)?;
@@ -98,8 +97,8 @@ impl Change {
9897
impl Display for Change {
9998
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10099
writeln!(f, "---")?;
101-
for (package_name, bump_type) in self.versioning.iter() {
102-
writeln!(f, "{package_name}: {bump_type}")?;
100+
for (package_name, change_type) in self.versioning.iter() {
101+
writeln!(f, "{package_name}: {change_type}")?;
103102
}
104103
writeln!(f, "---")?;
105104
writeln!(f)?;
@@ -120,12 +119,6 @@ impl From<BuildVersioningError> for ParsingError {
120119
}
121120
}
122121

123-
impl From<BumpTypeParsingError> for ParsingError {
124-
fn from(err: BumpTypeParsingError) -> Self {
125-
ParsingError::InvalidVersioning(err.into())
126-
}
127-
}
128-
129122
impl Display for ParsingError {
130123
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131124
match self {

src/changeset.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{collections::HashMap, path::Path};
22

3-
use crate::{change::LoadingError, BumpType, Change, PackageName};
3+
use crate::{change::LoadingError, Change, ChangeType, PackageName};
44

55
/// A set of [`Change`]s that combine to form [`Release`]s of one or more packages.
66
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -41,15 +41,15 @@ impl FromIterator<Change> for ChangeSet {
4141
fn from_iter<T: IntoIterator<Item = Change>>(iter: T) -> Self {
4242
let mut releases = HashMap::new();
4343
for change in iter {
44-
for (package_name, bump_type) in change.versioning {
44+
for (package_name, change_type) in change.versioning {
4545
let release = releases
4646
.entry(package_name.clone())
4747
.or_insert_with(|| Release {
4848
package_name,
4949
changes: Vec::new(),
5050
});
5151
release.changes.push(PackageChange {
52-
bump_type,
52+
change_type,
5353
summary: change.summary.clone(),
5454
});
5555
}
@@ -66,22 +66,18 @@ pub struct Release {
6666
}
6767

6868
impl Release {
69-
/// The overall bump type for the package's version based on all the [`Release::changes`].
69+
/// The overall [`ChangeType`] for the package's version based on all the [`Release::changes`].
7070
#[must_use]
71-
pub fn bump_type(&self) -> BumpType {
72-
self.changes
73-
.iter()
74-
.map(|change| change.bump_type)
75-
.max()
76-
.unwrap_or_default()
71+
pub fn change_type(&self) -> Option<&ChangeType> {
72+
self.changes.iter().map(|change| &change.change_type).max()
7773
}
7874
}
7975

8076
/// A [`Change`] as it applies to a single package for a [`Release`],
8177
#[derive(Clone, Debug, Eq, PartialEq)]
8278
pub struct PackageChange {
83-
/// The type of bump to apply to the package's version.
84-
pub bump_type: BumpType,
79+
/// The type of change, which determines how the version will be bumped (if at all).
80+
pub change_type: ChangeType,
8581
/// The details of the change, as a markdown-formatted string.
8682
pub summary: String,
8783
}

src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020

2121
pub use change::{Change, LoadingError as ChangeParsingError, ParsingError as ChangeLoadingError};
2222
pub use changeset::{ChangeSet, PackageChange, Release};
23-
pub use versioning::{
24-
BuildVersioningError, BumpType, BumpTypeParsingError, PackageName, Versioning,
25-
};
23+
pub use versioning::{BuildVersioningError, ChangeType, PackageName, Versioning};
2624

2725
mod change;
2826
mod changeset;

src/versioning.rs

Lines changed: 38 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,23 @@ use std::{
44
convert::Infallible,
55
error::Error,
66
fmt::{Display, Formatter},
7-
str::FromStr,
87
};
98

109
/// Describes how a [`crate::Change`] affects the version of relevant packages.
1110
///
1211
/// This is guaranteed to never be empty, as a changeset must always apply to at least one package.
1312
#[derive(Clone, Debug, Eq, PartialEq)]
14-
pub struct Versioning(HashMap<PackageName, BumpType>);
13+
pub struct Versioning(HashMap<PackageName, ChangeType>);
1514

16-
impl From<(&str, BumpType)> for Versioning {
17-
fn from(value: (&str, BumpType)) -> Self {
15+
impl From<(&str, ChangeType)> for Versioning {
16+
fn from(value: (&str, ChangeType)) -> Self {
1817
let value = (PackageName::from(value.0), value.1);
1918
Self::from(value)
2019
}
2120
}
2221

23-
impl From<(PackageName, BumpType)> for Versioning {
24-
fn from(value: (PackageName, BumpType)) -> Self {
22+
impl From<(PackageName, ChangeType)> for Versioning {
23+
fn from(value: (PackageName, ChangeType)) -> Self {
2524
let mut map = HashMap::new();
2625
map.insert(value.0, value.1);
2726
Self(map)
@@ -33,14 +32,13 @@ impl Versioning {
3332
///
3433
/// # Errors
3534
///
36-
/// 1. If the values type cannot be converted into a [`BumpType`], you'll get [`BuildVersioningError::BumpTypeParsingError`].
37-
/// 2. If the iterator is empty, you'll get [`BuildVersioningError::EmptyVersioningError`].
35+
/// 1. If the iterator is empty, you'll get [`BuildVersioningError::EmptyVersioningError`].
3836
pub fn try_from_iter<Key, Value, ParseError, Iter>(
3937
iter: Iter,
4038
) -> Result<Self, BuildVersioningError>
4139
where
4240
Key: Into<PackageName>,
43-
Value: TryInto<BumpType, Error = ParseError>,
41+
Value: TryInto<ChangeType, Error = ParseError>,
4442
ParseError: Into<BuildVersioningError>,
4543
Iter: IntoIterator<Item = (Key, Value)>,
4644
{
@@ -52,15 +50,15 @@ impl Versioning {
5250
.map_err(Into::into)
5351
.map(|value| (key.into(), value))
5452
})
55-
.collect::<Result<HashMap<PackageName, BumpType>, BuildVersioningError>>()?;
53+
.collect::<Result<HashMap<PackageName, ChangeType>, BuildVersioningError>>()?;
5654
if map.is_empty() {
5755
Err(BuildVersioningError::EmptyVersioningError)
5856
} else {
5957
Ok(Self(map))
6058
}
6159
}
6260

63-
pub fn iter(&self) -> impl Iterator<Item = (&PackageName, &BumpType)> {
61+
pub fn iter(&self) -> impl Iterator<Item = (&PackageName, &ChangeType)> {
6462
self.0.iter()
6563
}
6664

@@ -76,21 +74,19 @@ impl Versioning {
7674
}
7775

7876
impl IntoIterator for Versioning {
79-
type Item = (PackageName, BumpType);
80-
type IntoIter = std::collections::hash_map::IntoIter<PackageName, BumpType>;
77+
type Item = (PackageName, ChangeType);
78+
type IntoIter = std::collections::hash_map::IntoIter<PackageName, ChangeType>;
8179

8280
fn into_iter(self) -> Self::IntoIter {
8381
self.0.into_iter()
8482
}
8583
}
8684

8785
/// The error that occurs if you try to create a [`Versioning`] out of an iterator which has no items.
88-
#[derive(Debug)]
86+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8987
pub enum BuildVersioningError {
9088
/// The iterator was empty.
9189
EmptyVersioningError,
92-
/// The iterator contained an invalid [`BumpType`].
93-
BumpTypeParsingError(BumpTypeParsingError),
9490
}
9591

9692
impl Display for BuildVersioningError {
@@ -99,17 +95,10 @@ impl Display for BuildVersioningError {
9995
Self::EmptyVersioningError => {
10096
f.write_str("Versioning needs to contain at least one item.")
10197
}
102-
Self::BumpTypeParsingError(error) => error.fmt(f),
10398
}
10499
}
105100
}
106101

107-
impl From<BumpTypeParsingError> for BuildVersioningError {
108-
fn from(value: BumpTypeParsingError) -> Self {
109-
Self::BumpTypeParsingError(value)
110-
}
111-
}
112-
113102
impl From<Infallible> for BuildVersioningError {
114103
fn from(_: Infallible) -> Self {
115104
unreachable!()
@@ -123,71 +112,55 @@ pub type PackageName = String;
123112

124113
/// The [Semantic Versioning](https://semver.org/) component which should be incremented when a [`Change`]
125114
/// is applied.
126-
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
127-
pub enum BumpType {
128-
#[default]
129-
None,
115+
#[derive(Clone, Debug, Eq, PartialEq)]
116+
pub enum ChangeType {
130117
Patch,
131118
Minor,
132119
Major,
120+
Custom(String),
133121
}
134122

135-
impl Display for BumpType {
123+
impl Display for ChangeType {
136124
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
137125
match self {
138-
BumpType::None => write!(f, "none"),
139-
BumpType::Patch => write!(f, "patch"),
140-
BumpType::Minor => write!(f, "minor"),
141-
BumpType::Major => write!(f, "major"),
126+
ChangeType::Custom(label) => write!(f, "{label}"),
127+
ChangeType::Patch => write!(f, "patch"),
128+
ChangeType::Minor => write!(f, "minor"),
129+
ChangeType::Major => write!(f, "major"),
142130
}
143131
}
144132
}
145133

146-
impl FromStr for BumpType {
147-
type Err = BumpTypeParsingError;
148-
149-
fn from_str(s: &str) -> Result<Self, Self::Err> {
134+
impl From<&str> for ChangeType {
135+
fn from(s: &str) -> Self {
150136
match s {
151-
"none" => Ok(BumpType::None),
152-
"patch" => Ok(BumpType::Patch),
153-
"minor" => Ok(BumpType::Minor),
154-
"major" => Ok(BumpType::Major),
155-
_ => Err(BumpTypeParsingError(String::from(s))),
137+
"patch" => ChangeType::Patch,
138+
"minor" => ChangeType::Minor,
139+
"major" => ChangeType::Major,
140+
other => ChangeType::Custom(other.to_string()),
156141
}
157142
}
158143
}
159144

160-
impl Ord for BumpType {
145+
impl Ord for ChangeType {
161146
fn cmp(&self, other: &Self) -> Ordering {
162147
match (self, other) {
163-
(BumpType::None, BumpType::None)
164-
| (BumpType::Major, BumpType::Major)
165-
| (BumpType::Patch, BumpType::Patch)
166-
| (BumpType::Minor, BumpType::Minor) => Ordering::Equal,
167-
(BumpType::None, _) => Ordering::Less,
168-
(_, BumpType::None) => Ordering::Greater,
169-
(BumpType::Patch, _) => Ordering::Less,
170-
(_, BumpType::Patch) => Ordering::Greater,
171-
(BumpType::Minor, _) => Ordering::Less,
172-
(_, BumpType::Minor) => Ordering::Greater,
148+
(ChangeType::Custom(_), ChangeType::Custom(_))
149+
| (ChangeType::Major, ChangeType::Major)
150+
| (ChangeType::Patch, ChangeType::Patch)
151+
| (ChangeType::Minor, ChangeType::Minor) => Ordering::Equal,
152+
(ChangeType::Custom(_), _) => Ordering::Less,
153+
(_, ChangeType::Custom(_)) => Ordering::Greater,
154+
(ChangeType::Patch, _) => Ordering::Less,
155+
(_, ChangeType::Patch) => Ordering::Greater,
156+
(ChangeType::Minor, _) => Ordering::Less,
157+
(_, ChangeType::Minor) => Ordering::Greater,
173158
}
174159
}
175160
}
176161

177-
impl PartialOrd for BumpType {
162+
impl PartialOrd for ChangeType {
178163
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
179164
Some(self.cmp(other))
180165
}
181166
}
182-
183-
/// The error that occurs when [`BumpType::from_str`] fails due to an invalid input.
184-
#[derive(Debug)]
185-
pub struct BumpTypeParsingError(String);
186-
187-
impl Display for BumpTypeParsingError {
188-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
189-
write!(f, "{} is not a valid BumpType", self.0)
190-
}
191-
}
192-
193-
impl Error for BumpTypeParsingError {}

tests/change.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
use changesets::{BumpType, Change, Versioning};
1+
use changesets::{Change, ChangeType, Versioning};
22
use tempfile::tempdir;
33

44
#[test]
55
fn create_change() {
66
let basic_programmatic = Change {
77
unique_id: String::from("basic_programmatic"),
8-
versioning: Versioning::from(("my_package", BumpType::Minor)),
8+
versioning: Versioning::from(("my_package", ChangeType::Minor)),
99
summary: String::from("### This is a summary"),
1010
};
1111

1212
let multiple_packages = Change {
1313
unique_id: String::from("multiple_packages"),
1414
versioning: Versioning::try_from_iter([
15-
("my_package", BumpType::Minor),
16-
("my_other_package", BumpType::Major),
15+
("my_package", ChangeType::Minor),
16+
("my_other_package", ChangeType::Major),
1717
])
1818
.unwrap(),
1919
summary: String::from("### This is a summary"),
@@ -58,6 +58,6 @@ fn load_change() {
5858
assert_eq!(change.summary, "### This is a summary");
5959
assert_eq!(
6060
change.versioning,
61-
Versioning::from(("my_package", BumpType::Minor))
61+
Versioning::from(("my_package", ChangeType::Minor))
6262
);
6363
}

0 commit comments

Comments
 (0)