1use std::error::Error;
2
3pub fn get_version_from_cargo_toml(
4 cargo_toml: &str,
5) -> Result<String, Box<dyn Error>> {
6 get_prop_from_cargo_toml(cargo_toml, "version")
7}
8
9pub fn get_version_from_cargo_lock(
10 cargo_lock: &str,
11 cargo_toml: &str,
12) -> Result<String, Box<dyn Error>> {
13 let mut version: Option<String> = None;
14 let lines: Vec<&str> = cargo_lock.lines().collect();
15 let mut should_copy = false;
16 let project_name = get_name_from_cargo_toml(cargo_toml)?;
17
18 for line in lines {
19 if line.starts_with("name") {
20 should_copy = line.starts_with(&format!("name = \"{}\"", project_name));
21 }
22
23 if should_copy && line.starts_with("version") {
24 version = Some(get_value_from_toml_line(line, "version"));
25 }
26 }
27
28 match version {
29 Some(v) => Ok(v),
30 None => Err("Cargo.lock does not have a `version` entry")?,
31 }
32}
33
34pub fn set_version_in_cargo_toml(cargo_toml: &str, version: &str) -> String {
35 let lines: Vec<&str> = cargo_toml.lines().collect();
36 let mut new_cargo_toml: Vec<String> = Vec::new();
37 let mut should_write = false;
38
39 for line in lines {
40 let mut new_line = line.to_owned();
41 if line.starts_with('[') {
42 should_write = line.starts_with("[package]");
43 }
44
45 if should_write && line.starts_with("version") {
46 new_line = format!("version = \"{}\"", version);
47 }
48
49 new_cargo_toml.push(new_line);
50 }
51
52 new_cargo_toml.join("\n")
53}
54
55pub fn set_version_in_cargo_lock(
56 cargo_lock: &str,
57 project_name: &str,
58 version: &str,
59) -> String {
60 let lines: Vec<&str> = cargo_lock.lines().collect();
61 let mut new_cargo_lock: Vec<String> = Vec::new();
62 let mut should_write = false;
63
64 for line in lines {
65 let mut new_line = line.to_owned();
66 if line.starts_with("name") {
67 should_write = line.starts_with(&format!("name = \"{}\"", project_name));
68 }
69
70 if should_write && line.starts_with("version") {
71 new_line = format!("version = \"{}\"", version);
72 }
73
74 new_cargo_lock.push(new_line);
75 }
76
77 new_cargo_lock.join("\n")
78}
79
80pub fn get_name_from_cargo_toml(
81 cargo_toml: &str,
82) -> Result<String, Box<dyn Error>> {
83 get_prop_from_cargo_toml(cargo_toml, "name")
84}
85
86fn get_prop_from_cargo_toml(
87 cargo_toml: &str,
88 prop: &str,
89) -> Result<String, Box<dyn Error>> {
90 let mut property: Option<String> = None;
91 let lines: Vec<&str> = cargo_toml.lines().collect();
92 let mut should_copy = false;
93
94 for line in lines {
95 if line.starts_with('[') {
96 should_copy = line.starts_with("[package]")
97 }
98
99 if should_copy && line.starts_with(prop) {
100 property = Some(get_value_from_toml_line(line, prop));
101 }
102 }
103
104 match property {
105 Some(v) => Ok(v),
106 None => Err(format!(
107 "Cargo.toml does not have a `{prop}` entry inside [package]"
108 ))?,
109 }
110}
111
112fn get_value_from_toml_line(line: &str, prop: &str) -> String {
113 line
114 .replace(prop, "")
115 .replace('=', "")
116 .replace('"', "")
117 .trim()
118 .to_string()
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn get_cargo_toml(version: &str) -> String {
126 format!("\
127[dependencies]
128tokio = {{ version = \"1.1.1\" }}
129
130[package]
131name = \"cargo-v\"
132version = \"{}\"
133edition = \"2021\"
134description = \"Update the version of your package easily\"
135license = \"MIT\"
136
137# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
138[dependencies.dev]
139other = {{ version = \"1.1.8\" }}
140 ",
141 version)
142 }
143
144 fn get_cargo_lock(version: &str) -> String {
145 format!(
146 "
147# This file is automatically @generated by Cargo.
148# It is not intended for manual editing.
149version = 3
150
151[[package]]
152name = \"tokio\"
153version = \"0.1.8\"
154
155[[package]]
156name = \"cargo-v\"
157version = \"{}\"
158
159[[package]]
160name = \"regex\"
161version = \"0.1.8\"
162 ",
163 version
164 )
165 }
166
167 #[test]
168 fn should_get_version_from_cargo_toml() {
169 let cargo_toml = &get_cargo_toml("0.1.6");
170 let actual = get_version_from_cargo_toml(cargo_toml).unwrap();
171 let expected = "0.1.6";
172 assert_eq!(actual, expected);
173 }
174
175 #[test]
176 fn should_get_name_from_cargo_toml() {
177 let cargo_toml = &get_cargo_toml("0.1.6");
178 let actual = get_name_from_cargo_toml(cargo_toml).unwrap();
179 let expected = "cargo-v";
180 assert_eq!(actual, expected);
181 }
182
183 #[test]
184 fn should_get_property_edition_from_cargo_toml() {
185 let cargo_toml = &get_cargo_toml("0.1.6");
186 let actual = get_prop_from_cargo_toml(&cargo_toml, "edition").unwrap();
187 let expected = "2021";
188 assert_eq!(actual, expected);
189 }
190
191 #[test]
192 fn should_get_version_from_cargo_lock() {
193 let cargo_toml = &get_cargo_toml("0.1.6");
194 let cargo_lock = &get_cargo_lock("0.1.6");
195 let actual = get_version_from_cargo_lock(cargo_lock, cargo_toml).unwrap();
196 let expected = "0.1.6";
197 assert_eq!(actual, expected);
198 }
199
200 #[test]
201 fn should_get_value_from_line_in_cargo_toml() {
202 let line = "version = \"1.1.1\"";
203 let actual = get_value_from_toml_line(line, "version");
204 let expected = "1.1.1";
205 assert_eq!(actual, expected);
206 }
207
208 #[test]
209 fn should_write_new_version_in_cargo_toml() {
210 let cargo_toml = get_cargo_toml("0.1.6");
211 let actual = set_version_in_cargo_toml(&cargo_toml, "0.1.7");
212 let expected = get_cargo_toml("0.1.7");
213 assert_eq!(actual, expected);
214 }
215
216 #[test]
217 fn should_write_new_version_in_cargo_lock() {
218 let cargo_toml = &get_cargo_toml("0.1.6");
219 let cargo_lock = get_cargo_lock("0.1.6");
220 let project_name = &get_name_from_cargo_toml(cargo_toml).unwrap();
221 let actual = set_version_in_cargo_lock(&cargo_lock, project_name, "0.1.7");
222 let expected = get_cargo_lock("0.1.7");
223 assert_eq!(actual, expected);
224 }
225}