1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use semver::Version;

use crate::VersionIncrement;

/// This struct allows to increment a version by
/// specifying a configuration.
///
/// Useful if you don't like the default increment rules of the crate.
///
// # Example
///
/// ```
/// use next_version::VersionUpdater;
/// use semver::Version;
///
/// let updated_version = VersionUpdater::new()
///     .with_features_always_increment_minor(false)
///     .with_breaking_always_increment_major(true)
///     .increment(&Version::new(1, 2, 3), ["feat: commit 1", "fix: commit 2"]);
///
/// assert_eq!(Version::new(1, 3, 0), updated_version);
/// ```
#[derive(Debug)]
pub struct VersionUpdater {
    pub(crate) features_always_increment_minor: bool,
    pub(crate) breaking_always_increment_major: bool,
}

impl Default for VersionUpdater {
    fn default() -> Self {
        Self::new()
    }
}

impl VersionUpdater {
    /// Constructs a new instance with the default rules of the crate.
    ///
    /// If you don't customize the struct further, it is equivalent to
    /// calling [`crate::NextVersion::next`].
    ///
    /// ```
    /// use next_version::{NextVersion, VersionUpdater};
    /// use semver::Version;
    ///
    /// let version = Version::new(1, 2, 3);
    /// let commits = ["feat: commit 1", "fix: commit 2"];
    /// let updated_version1 = VersionUpdater::new()
    ///     .increment(&version, &commits);
    /// let updated_version2 = version.next(&commits);
    ///
    /// assert_eq!(updated_version1, updated_version2);
    /// ```
    pub fn new() -> Self {
        Self {
            features_always_increment_minor: false,
            breaking_always_increment_major: false,
        }
    }

    /// Configures automatic minor version increments for feature changes.
    ///
    /// - When `true` is passed, a feature will always trigger a minor version update.
    /// - When `false` is passed, a feature will trigger:
    ///   - a patch version update if the major version is 0.
    ///   - a minor version update otherwise.
    ///
    /// Default: `false`.
    ///
    /// ```rust
    /// use semver::Version;
    /// use next_version::VersionUpdater;
    ///
    /// let commits = ["feat: make coffee"];
    /// let version = Version::new(0, 2, 3);
    /// assert_eq!(
    ///     VersionUpdater::new()
    ///         .with_features_always_increment_minor(true)
    ///         .increment(&version, &commits),
    ///     Version::new(0, 3, 0)
    /// );
    /// assert_eq!(
    ///     VersionUpdater::new()
    ///         .increment(&version, &commits),
    ///     Version::new(0, 2, 4)
    /// );
    /// ```
    pub fn with_features_always_increment_minor(
        mut self,
        features_always_increment_minor: bool,
    ) -> Self {
        self.features_always_increment_minor = features_always_increment_minor;
        self
    }

    /// Configures `0 -> 1` major version increments for breaking changes.
    ///
    /// - When `true` is passed, a breaking change commit will always trigger a major version update
    ///   (including the transition from version 0 to 1)
    /// - When `false` is passed, a breaking change commit will trigger:
    ///   - a minor version update if the major version is 0.
    ///   - a major version update otherwise.
    ///
    /// Default: `false`.
    ///
    /// ```rust
    /// use semver::Version;
    /// use next_version::VersionUpdater;
    ///
    /// let commits = ["feat!: incompatible change"];
    /// let version = Version::new(0, 2, 3);
    /// assert_eq!(
    ///     VersionUpdater::new()
    ///         .with_breaking_always_increment_major(true)
    ///         .increment(&version, &commits),
    ///     Version::new(1, 0, 0)
    /// );
    /// assert_eq!(
    ///     VersionUpdater::new()
    ///         .increment(&version, &commits),
    ///     Version::new(0, 3, 0)
    /// );
    /// ```
    pub fn with_breaking_always_increment_major(
        mut self,
        breaking_always_increment_major: bool,
    ) -> Self {
        self.breaking_always_increment_major = breaking_always_increment_major;
        self
    }

    /// Analyze commits and determine the next version.
    pub fn increment<I>(self, version: &Version, commits: I) -> Version
    where
        I: IntoIterator,
        I::Item: AsRef<str>,
    {
        let increment = VersionIncrement::from_commits_with_updater(&self, version, commits);
        match increment {
            Some(increment) => increment.bump(version),
            None => version.clone(),
        }
    }
}