1use crate::*;
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
158fn find_version_position(line: &str) -> Option<(usize, usize)> {
168 let trimmed: &str = line.trim();
169 if !trimmed.starts_with("version") || !trimmed.contains('=') {
170 return None;
171 }
172 let eq_pos: usize = line.find('=')?;
173 let after_eq: &str = &line[eq_pos + 1..];
174 let quote_start: usize = after_eq.find('"')?;
175 let after_first_quote: &str = &after_eq[quote_start + 1..];
176 let quote_end: usize = after_first_quote.find('"')?;
177 let version_start: usize = eq_pos + 1 + quote_start + 1;
178 let version_end: usize = version_start + quote_end;
179 Some((version_start, version_end))
180}
181
182pub async fn execute_bump(
193 manifest_path: &str,
194 bump_type: &BumpVersionType,
195) -> Result<String, Box<dyn std::error::Error>> {
196 let path: &Path = Path::new(manifest_path);
197 let content: String = read_to_string(path).await?;
198 let mut new_version: Option<String> = None;
199 let mut found_version: bool = false;
200 let mut updated_content: String = content.clone();
201 for line in content.lines() {
202 if found_version {
203 break;
204 }
205 if let Some((version_start, version_end)) = find_version_position(line) {
206 let version_str: &str = &line[version_start..version_end];
207 if let Some(version) = parse_version(version_str) {
208 let bumped: Version = bump_version(&version, bump_type);
209 let version_string: String = version_to_string(&bumped);
210 new_version = Some(version_string.clone());
211 let new_line: String = format!(
212 "{}{version_string}{}",
213 &line[..version_start],
214 &line[version_end..]
215 );
216 updated_content = updated_content.replacen(line, &new_line, 1);
217 found_version = true;
218 }
219 }
220 }
221 if !found_version {
222 return Err("version field not found in Cargo.toml".into());
223 }
224 write(path, updated_content).await?;
225 match new_version {
226 Some(v) => Ok(v),
227 None => Err("failed to bump version".into()),
228 }
229}