From 12174d62b32d01628264d25a78849017ec48d812 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 9 Mar 2026 01:18:32 -0700 Subject: [PATCH] feat: accept Go-style single-dash CLI options Go's flag package uses single dash (-port) while clap uses double dash (--port). Add normalize_args_vec() to convert single-dash long options to double-dash before clap parsing, so both formats work. Update test framework to use single-dash flags matching Go convention. --- seaweed-volume/src/config.rs | 88 +++++++++++++++++++- test/volume_server/framework/cluster_rust.go | 28 +++---- 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/seaweed-volume/src/config.rs b/seaweed-volume/src/config.rs index b462a7205..b845c4078 100644 --- a/seaweed-volume/src/config.rs +++ b/seaweed-volume/src/config.rs @@ -252,9 +252,47 @@ pub enum MinFreeSpace { Bytes(u64), } +/// Convert single-dash long options to double-dash for clap compatibility. +/// Go's `flag` package uses `-port`, clap expects `--port`. +/// This allows both `-port 8080` and `--port 8080` to work. +fn normalize_args_vec(args: Vec) -> Vec { + let mut args = args; + // Skip args[0] (binary name). + let mut i = 1; + while i < args.len() { + let arg = &args[i]; + // Stop processing after "--" + if arg == "--" { + break; + } + // Already double-dash or not a flag: leave as-is + if arg.starts_with("--") || !arg.starts_with('-') { + i += 1; + continue; + } + // Single char flags like -h, -V: leave as-is + let without_dash = &arg[1..]; + // Check if it's a single-dash long option: more than 1 char and not a negative number + if without_dash.len() > 1 && !without_dash.starts_with(|c: char| c.is_ascii_digit()) { + // Handle -key=value format + if let Some(eq_pos) = without_dash.find('=') { + let key = &without_dash[..eq_pos]; + if key.len() > 1 { + args[i] = format!("--{}", without_dash); + } + } else { + args[i] = format!("-{}", arg); + } + } + i += 1; + } + args +} + /// Parse CLI arguments and resolve all defaults — mirroring Go's `runVolume()` + `startVolumeServer()`. pub fn parse_cli() -> VolumeServerConfig { - let cli = Cli::parse(); + let args: Vec = std::env::args().collect(); + let cli = Cli::parse_from(normalize_args_vec(args)); resolve_config(cli) } @@ -823,6 +861,54 @@ mod tests { assert_eq!(tags[1], Vec::::new()); } + #[test] + fn test_normalize_args_single_dash_to_double() { + let args = vec![ + "bin".into(), + "-port".into(), "8080".into(), + "-ip.bind".into(), "127.0.0.1".into(), + "-dir".into(), "/data".into(), + ]; + let norm = normalize_args_vec(args); + assert_eq!(norm, vec![ + "bin", "--port", "8080", "--ip.bind", "127.0.0.1", "--dir", "/data", + ]); + } + + #[test] + fn test_normalize_args_double_dash_unchanged() { + let args = vec![ + "bin".into(), + "--port".into(), "8080".into(), + "--master".into(), "localhost:9333".into(), + ]; + let norm = normalize_args_vec(args); + assert_eq!(norm, vec![ + "bin", "--port", "8080", "--master", "localhost:9333", + ]); + } + + #[test] + fn test_normalize_args_single_char_flags_unchanged() { + let args = vec!["bin".into(), "-h".into(), "-V".into()]; + let norm = normalize_args_vec(args); + assert_eq!(norm, vec!["bin", "-h", "-V"]); + } + + #[test] + fn test_normalize_args_equals_format() { + let args = vec!["bin".into(), "-port=8080".into(), "-ip.bind=0.0.0.0".into()]; + let norm = normalize_args_vec(args); + assert_eq!(norm, vec!["bin", "--port=8080", "--ip.bind=0.0.0.0"]); + } + + #[test] + fn test_normalize_args_stop_at_double_dash() { + let args = vec!["bin".into(), "-port".into(), "8080".into(), "--".into(), "-notaflag".into()]; + let norm = normalize_args_vec(args); + assert_eq!(norm, vec!["bin", "--port", "8080", "--", "-notaflag"]); + } + #[test] fn test_parse_security_config_access_ui() { let tmp = tempfile::NamedTempFile::new().unwrap(); diff --git a/test/volume_server/framework/cluster_rust.go b/test/volume_server/framework/cluster_rust.go index ae4466ccd..a887173dd 100644 --- a/test/volume_server/framework/cluster_rust.go +++ b/test/volume_server/framework/cluster_rust.go @@ -185,24 +185,24 @@ func (rc *RustCluster) startRustVolume(dataDir string) error { } args := []string{ - "--port", strconv.Itoa(rc.volumePort), - "--port.grpc", strconv.Itoa(rc.volumeGrpcPort), - "--port.public", strconv.Itoa(rc.volumePubPort), - "--ip", "127.0.0.1", - "--ip.bind", "127.0.0.1", - "--dir", dataDir, - "--max", "16", - "--master", "127.0.0.1:" + strconv.Itoa(rc.masterPort), - "--securityFile", filepath.Join(rc.configDir, "security.toml"), - "--concurrentUploadLimitMB", strconv.Itoa(rc.profile.ConcurrentUploadLimitMB), - "--concurrentDownloadLimitMB", strconv.Itoa(rc.profile.ConcurrentDownloadLimitMB), - "--preStopSeconds", "0", + "-port", strconv.Itoa(rc.volumePort), + "-port.grpc", strconv.Itoa(rc.volumeGrpcPort), + "-port.public", strconv.Itoa(rc.volumePubPort), + "-ip", "127.0.0.1", + "-ip.bind", "127.0.0.1", + "-dir", dataDir, + "-max", "16", + "-master", "127.0.0.1:" + strconv.Itoa(rc.masterPort), + "-securityFile", filepath.Join(rc.configDir, "security.toml"), + "-concurrentUploadLimitMB", strconv.Itoa(rc.profile.ConcurrentUploadLimitMB), + "-concurrentDownloadLimitMB", strconv.Itoa(rc.profile.ConcurrentDownloadLimitMB), + "-preStopSeconds", "0", } if rc.profile.InflightUploadTimeout > 0 { - args = append(args, "--inflightUploadDataTimeout", rc.profile.InflightUploadTimeout.String()) + args = append(args, "-inflightUploadDataTimeout", rc.profile.InflightUploadTimeout.String()) } if rc.profile.InflightDownloadTimeout > 0 { - args = append(args, "--inflightDownloadDataTimeout", rc.profile.InflightDownloadTimeout.String()) + args = append(args, "-inflightDownloadDataTimeout", rc.profile.InflightDownloadTimeout.String()) } rc.volumeCmd = exec.Command(rc.rustVolumeBinary, args...)