1use super::*;
2
3fn parse_version(version_str: &str) -> Option<Version> {
13 let parts: Vec<&str> = version_str.split('-').collect();
14 let version_part: &str = parts.first()?;
15 let prerelease: Option<String> = parts.get(1).map(|s: &&str| s.to_string());
16 let nums: Vec<&str> = version_part.split('.').collect();
17 if nums.len() != 3 {
18 return None;
19 }
20 let major: u64 = nums.first()?.parse().ok()?;
21 let minor: u64 = nums.get(1)?.parse().ok()?;
22 let patch: u64 = nums.get(2)?.parse().ok()?;
23 Some(Version {
24 major,
25 minor,
26 patch,
27 prerelease,
28 })
29}
30
31fn parse_prerelease(prerelease: &str) -> Option<(&str, u64)> {
41 let parts: Vec<&str> = prerelease.split('.').collect();
42 let pre_type: &str = parts.first()?;
43 let number: u64 = parts
44 .get(1)
45 .and_then(|s: &&str| s.parse().ok())
46 .unwrap_or(0);
47 Some((pre_type, number))
48}
49
50fn get_next_prerelease(current: Option<&String>, target_type: &str) -> String {
61 match current {
62 Some(pre) => {
63 if let Some((pre_type, number)) = parse_prerelease(pre)
64 && pre_type == target_type
65 && number > 0
66 {
67 return format!("{}.{}", target_type, number + 1);
68 }
69 format!("{target_type}.1")
70 }
71 None => target_type.to_string(),
72 }
73}
74
75fn version_to_string(version: &Version) -> String {
85 let base: String = format!("{}.{}.{}", version.major, version.minor, version.patch);
86 match &version.prerelease {
87 Some(pre) => format!("{base}-{pre}"),
88 None => base,
89 }
90}
91
92fn bump_version(version: &Version, bump_type: &BumpVersionType) -> Version {
103 match bump_type {
104 BumpVersionType::Patch => Version {
105 major: version.major,
106 minor: version.minor,
107 patch: version.patch + 1,
108 prerelease: None,
109 },
110 BumpVersionType::Minor => Version {
111 major: version.major,
112 minor: version.minor + 1,
113 patch: 0,
114 prerelease: None,
115 },
116 BumpVersionType::Major => Version {
117 major: version.major + 1,
118 minor: 0,
119 patch: 0,
120 prerelease: None,
121 },
122 BumpVersionType::Release => Version {
123 major: version.major,
124 minor: version.minor,
125 patch: version.patch,
126 prerelease: None,
127 },
128 BumpVersionType::Alpha => {
129 let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "alpha");
130 Version {
131 major: version.major,
132 minor: version.minor,
133 patch: version.patch,
134 prerelease: Some(prerelease),
135 }
136 }
137 BumpVersionType::Beta => {
138 let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "beta");
139 Version {
140 major: version.major,
141 minor: version.minor,
142 patch: version.patch,
143 prerelease: Some(prerelease),
144 }
145 }
146 BumpVersionType::Rc => {
147 let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "rc");
148 Version {
149 major: version.major,
150 minor: version.minor,
151 patch: version.patch,
152 prerelease: Some(prerelease),
153 }
154 }
155 }
156}
157
158pub async fn execute_bump(
173 manifest_path: &str,
174 bump_type: &BumpVersionType,
175) -> Result<String, Box<dyn std::error::Error>> {
176 let path: &Path = Path::new(manifest_path);
177 let content: String = read_to_string(path).await?;
178 let mut doc: DocumentMut = content
179 .parse()
180 .map_err(|e: TomlError| format!("failed to parse {}: {}", manifest_path, e))?;
181 let version_slot: &mut Item = if doc
182 .get("workspace")
183 .and_then(|workspace: &Item| workspace.get("package"))
184 .is_some()
185 {
186 doc.get_mut("workspace")
187 .and_then(|workspace: &mut Item| workspace.get_mut("package"))
188 .and_then(|package: &mut Item| package.get_mut("version"))
189 .ok_or_else(|| -> Box<dyn std::error::Error> {
190 "workspace.package.version not found".into()
191 })?
192 } else if doc.get("package").is_some() {
193 doc.get_mut("package")
194 .and_then(|package: &mut Item| package.get_mut("version"))
195 .ok_or_else(|| -> Box<dyn std::error::Error> { "package.version not found".into() })?
196 } else {
197 return Err("neither [package] nor [workspace.package] found in Cargo.toml".into());
198 };
199 let version_str: String = version_slot
200 .as_str()
201 .ok_or_else(|| -> Box<dyn std::error::Error> { "version field is not a string".into() })?
202 .to_string();
203 let version: Version =
204 parse_version(&version_str).ok_or_else(|| -> Box<dyn std::error::Error> {
205 format!("failed to parse version: {}", version_str).into()
206 })?;
207 let bumped: Version = bump_version(&version, bump_type);
208 let version_string: String = version_to_string(&bumped);
209 set_item_string_preserving_decor(version_slot, &version_string);
210 write(path, doc.to_string()).await?;
211 Ok(version_string)
212}