Skip to content

Commit 5b62380

Browse files
committed
feat: v0.1.3
1 parent a5b48de commit 5b62380

11 files changed

Lines changed: 339 additions & 5 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hyperlane-cli"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
readme = "README.md"
55
edition = "2024"
66
authors = ["root@ltpp.vip"]

src/bump/enum.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// Types of version bumps
2+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3+
pub(crate) enum BumpVersionType {
4+
/// Bump patch version (0.1.2 -> 0.1.3)
5+
Patch,
6+
/// Bump minor version (0.1.2 -> 0.2.0)
7+
Minor,
8+
/// Bump major version (0.1.2 -> 1.0.0)
9+
Major,
10+
/// Remove pre-release identifier to make it a release version
11+
Release,
12+
/// Add or bump alpha pre-release version (0.1.2 -> 0.1.2-alpha, 0.1.2-alpha -> 0.1.2-alpha.1)
13+
Alpha,
14+
/// Add or bump beta pre-release version (0.1.2 -> 0.1.2-beta, 0.1.2-alpha.2 -> 0.1.2-beta.1)
15+
Beta,
16+
/// Add or bump rc pre-release version (0.1.2 -> 0.1.2-rc, 0.1.2-beta.1 -> 0.1.2-rc.1)
17+
Rc,
18+
}

src/bump/fn.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
use crate::*;
2+
3+
/// Parse a version string into Version struct
4+
///
5+
/// # Arguments
6+
///
7+
/// - `&str`: The version string to parse (e.g., "0.1.2" or "0.1.2-alpha")
8+
///
9+
/// # Returns
10+
///
11+
/// - `Option<Version>`: Parsed version if successful, None otherwise
12+
fn 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+
31+
/// Parse pre-release identifier to extract type and number
32+
///
33+
/// # Arguments
34+
///
35+
/// - `&str`: The pre-release string (e.g., "alpha", "alpha.1", "beta.2")
36+
///
37+
/// # Returns
38+
///
39+
/// - `Option<(&str, u64)>`: Tuple of (pre_release_type, number) if parsed successfully
40+
fn 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+
50+
/// Get the next pre-release version string
51+
///
52+
/// # Arguments
53+
///
54+
/// - `Option<&String>`: Current pre-release identifier
55+
/// - `&str`: Target pre-release type ("alpha", "beta", "rc")
56+
///
57+
/// # Returns
58+
///
59+
/// - `String`: The new pre-release identifier
60+
fn 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+
if pre_type == target_type && number > 0 {
65+
return format!("{}.{}", target_type, number + 1);
66+
}
67+
}
68+
format!("{target_type}.1")
69+
}
70+
None => target_type.to_string(),
71+
}
72+
}
73+
74+
/// Convert Version back to string representation
75+
///
76+
/// # Arguments
77+
///
78+
/// - `&Version`: The Version struct to convert
79+
///
80+
/// # Returns
81+
///
82+
/// - `String`: Version string (e.g., "0.1.2" or "0.1.2-alpha")
83+
fn version_to_string(version: &Version) -> String {
84+
let base: String = format!("{}.{}.{}", version.major, version.minor, version.patch);
85+
match &version.prerelease {
86+
Some(pre) => format!("{base}-{pre}"),
87+
None => base,
88+
}
89+
}
90+
91+
/// Apply version bump according to the specified type
92+
///
93+
/// # Arguments
94+
///
95+
/// - `&Version`: The current version
96+
/// - `BumpVersionType`: The type of version bump to apply
97+
///
98+
/// # Returns
99+
///
100+
/// - `Version`: The new version after bumping
101+
fn bump_version(version: &Version, bump_type: BumpVersionType) -> Version {
102+
match bump_type {
103+
BumpVersionType::Patch => Version {
104+
major: version.major,
105+
minor: version.minor,
106+
patch: version.patch + 1,
107+
prerelease: None,
108+
},
109+
BumpVersionType::Minor => Version {
110+
major: version.major,
111+
minor: version.minor + 1,
112+
patch: 0,
113+
prerelease: None,
114+
},
115+
BumpVersionType::Major => Version {
116+
major: version.major + 1,
117+
minor: 0,
118+
patch: 0,
119+
prerelease: None,
120+
},
121+
BumpVersionType::Release => Version {
122+
major: version.major,
123+
minor: version.minor,
124+
patch: version.patch,
125+
prerelease: None,
126+
},
127+
BumpVersionType::Alpha => {
128+
let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "alpha");
129+
Version {
130+
major: version.major,
131+
minor: version.minor,
132+
patch: version.patch,
133+
prerelease: Some(prerelease),
134+
}
135+
}
136+
BumpVersionType::Beta => {
137+
let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "beta");
138+
Version {
139+
major: version.major,
140+
minor: version.minor,
141+
patch: version.patch,
142+
prerelease: Some(prerelease),
143+
}
144+
}
145+
BumpVersionType::Rc => {
146+
let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "rc");
147+
Version {
148+
major: version.major,
149+
minor: version.minor,
150+
patch: version.patch,
151+
prerelease: Some(prerelease),
152+
}
153+
}
154+
}
155+
}
156+
157+
/// Find version value position in a line
158+
///
159+
/// # Arguments
160+
///
161+
/// - `&str`: The line to search
162+
///
163+
/// # Returns
164+
///
165+
/// - `Option<(usize, usize)>`: Start and end positions of version string within quotes
166+
fn find_version_position(line: &str) -> Option<(usize, usize)> {
167+
let trimmed: &str = line.trim();
168+
if !trimmed.starts_with("version") || !trimmed.contains('=') {
169+
return None;
170+
}
171+
let eq_pos: usize = line.find('=')?;
172+
let after_eq: &str = &line[eq_pos + 1..];
173+
let quote_start: usize = after_eq.find('"')?;
174+
let after_first_quote: &str = &after_eq[quote_start + 1..];
175+
let quote_end: usize = after_first_quote.find('"')?;
176+
let version_start: usize = eq_pos + 1 + quote_start + 1;
177+
let version_end: usize = version_start + quote_end;
178+
Some((version_start, version_end))
179+
}
180+
181+
/// Read and update version in Cargo.toml
182+
///
183+
/// # Arguments
184+
///
185+
/// - `&str`: Path to Cargo.toml file
186+
/// - `BumpVersionType`: Type of version bump to apply
187+
///
188+
/// # Returns
189+
///
190+
/// - `Result<String, Box<dyn std::error::Error>>`: The new version string or an error
191+
pub(crate) fn execute_bump(
192+
manifest_path: &str,
193+
bump_type: BumpVersionType,
194+
) -> Result<String, Box<dyn std::error::Error>> {
195+
let path: &Path = Path::new(manifest_path);
196+
let content: String = read_to_string(path)?;
197+
let mut new_version: Option<String> = None;
198+
let mut found_version: bool = false;
199+
let mut updated_content: String = content.clone();
200+
for line in content.lines() {
201+
if found_version {
202+
break;
203+
}
204+
if let Some((version_start, version_end)) = find_version_position(line) {
205+
let version_str: &str = &line[version_start..version_end];
206+
if let Some(version) = parse_version(version_str) {
207+
let bumped: Version = bump_version(&version, bump_type);
208+
let version_string: String = version_to_string(&bumped);
209+
new_version = Some(version_string.clone());
210+
let new_line: String = format!(
211+
"{}{}{}",
212+
&line[..version_start],
213+
version_string,
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)?;
225+
match new_version {
226+
Some(v) => Ok(v),
227+
None => Err("failed to bump version".into()),
228+
}
229+
}

src/bump/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod r#enum;
2+
mod r#fn;
3+
mod r#struct;
4+
5+
pub(crate) use {r#enum::*, r#fn::*, r#struct::*};

src/bump/struct.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// Parsed version components following semantic versioning
2+
#[derive(Clone, Debug, Eq, PartialEq)]
3+
pub(crate) struct Version {
4+
/// Major version number
5+
pub major: u64,
6+
/// Minor version number
7+
pub minor: u64,
8+
/// Patch version number
9+
pub patch: u64,
10+
/// Optional pre-release identifier (e.g., "alpha", "beta", "rc.1")
11+
pub prerelease: Option<String>,
12+
}

src/command/enum.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ pub(crate) enum CommandType {
55
Fmt,
66
/// Watch files using cargo-watch
77
Watch,
8+
/// Bump version in Cargo.toml
9+
Bump,
810
/// Show help
911
Help,
1012
/// Show version

src/config/fn.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub(crate) fn parse_args() -> Args {
1010
let mut command: CommandType = CommandType::Help;
1111
let mut check: bool = false;
1212
let mut manifest_path: Option<String> = None;
13+
let mut bump_type: Option<BumpVersionType> = None;
1314
let mut i: usize = 1;
1415
while i < raw_args.len() {
1516
let arg: &str = raw_args[i].as_str();
@@ -30,6 +31,32 @@ pub(crate) fn parse_args() -> Args {
3031
command = CommandType::Watch;
3132
}
3233
}
34+
"bump" => {
35+
if command == CommandType::Help || command == CommandType::Version {
36+
command = CommandType::Bump;
37+
}
38+
}
39+
"--patch" => {
40+
bump_type = Some(BumpVersionType::Patch);
41+
}
42+
"--minor" => {
43+
bump_type = Some(BumpVersionType::Minor);
44+
}
45+
"--major" => {
46+
bump_type = Some(BumpVersionType::Major);
47+
}
48+
"--release" => {
49+
bump_type = Some(BumpVersionType::Release);
50+
}
51+
"--alpha" => {
52+
bump_type = Some(BumpVersionType::Alpha);
53+
}
54+
"--beta" => {
55+
bump_type = Some(BumpVersionType::Beta);
56+
}
57+
"--rc" => {
58+
bump_type = Some(BumpVersionType::Rc);
59+
}
3360
"--check" => {
3461
check = true;
3562
}
@@ -47,5 +74,6 @@ pub(crate) fn parse_args() -> Args {
4774
command,
4875
check,
4976
manifest_path,
77+
bump_type,
5078
}
5179
}

src/config/struct.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pub struct Args {
77
pub command: CommandType,
88
/// Check mode for fmt
99
pub check: bool,
10-
/// Manifest path for fmt
10+
/// Manifest path for fmt and bump
1111
pub manifest_path: Option<String>,
12+
/// Bump type for bump command
13+
pub bump_type: Option<BumpVersionType>,
1214
}

src/fmt/fn.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ async fn install_cargo_clippy() -> Result<(), std::io::Error> {
3636
/// Execute clippy fix command
3737
///
3838
/// # Arguments
39-
/// - `args`: The parsed arguments
39+
///
40+
/// - `&Args`: The parsed arguments
4041
///
4142
/// # Returns
43+
///
4244
/// - `Result<(), std::io::Error>`: Success or error
4345
async fn execute_clippy_fix(args: &Args) -> Result<(), std::io::Error> {
4446
if !is_cargo_clippy_installed().await {
@@ -64,7 +66,8 @@ async fn execute_clippy_fix(args: &Args) -> Result<(), std::io::Error> {
6466
/// Execute fmt command
6567
///
6668
/// # Arguments
67-
/// - `args`: The parsed arguments
69+
///
70+
/// - `&Args`: The parsed arguments
6871
///
6972
/// # Returns
7073
///

src/help/fn.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,28 @@ pub(crate) fn print_help() {
33
println!("hyperlane-cli [COMMAND] [OPTIONS]");
44
println!();
55
println!("Commands:");
6+
println!(" bump Bump version in Cargo.toml");
67
println!(" fmt Format Rust code using cargo fmt");
78
println!(" watch Watch files and run cargo run using cargo-watch");
89
println!(" -h, --help Print this help message");
910
println!(" -v, --version Print version information");
1011
println!();
12+
println!("Bump Options:");
13+
println!(" --patch Bump patch version (0.1.2 -> 0.1.3) [default]");
14+
println!(" --minor Bump minor version (0.1.2 -> 0.2.0)");
15+
println!(" --major Bump major version (0.1.2 -> 1.0.0)");
16+
println!(
17+
" --alpha Add or bump alpha version (0.1.2 -> 0.1.2-alpha, 0.1.2-alpha -> 0.1.2-alpha.1)"
18+
);
19+
println!(
20+
" --beta Add or bump beta version (0.1.2 -> 0.1.2-beta, 0.1.2-alpha.2 -> 0.1.2-beta.1)"
21+
);
22+
println!(
23+
" --rc Add or bump rc version (0.1.2 -> 0.1.2-rc, 0.1.2-beta.1 -> 0.1.2-rc.1)"
24+
);
25+
println!(" --release Remove pre-release identifier (0.1.2-alpha -> 0.1.2)");
26+
println!(" --manifest-path <PATH> Path to Cargo.toml [default: Cargo.toml]");
27+
println!();
1128
println!("Fmt Options:");
1229
println!(" --check Check formatting without making changes");
1330
println!(" --manifest-path <PATH> Path to Cargo.toml");

0 commit comments

Comments
 (0)