From dd3dab7d52c45da4c9d76b7aada57240bb4358db Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Dec 2025 08:54:48 +0100 Subject: [PATCH 1/2] fix(args): Add backward compatibility for single-dash long options (#1576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old versions of ccextractor accepted single-dash long options like -quiet, -stdout, -autoprogram. The new Rust-based argument parser (clap) only accepts double-dash options (--quiet, --stdout, etc.). When users ran scripts with -quiet, clap parsed it as individual short options -q -u -i -e -t and failed with exit code 7. Users with stderr redirected never saw the error, causing silent failures with zero-length output files. This adds a normalize_legacy_option() function that pre-processes arguments before passing them to clap: - Single-dash long options (e.g., -quiet) convert to --quiet - Double-dash options remain unchanged - Short options like -o remain unchanged - Numeric options like -1, -12 remain unchanged Includes 6 unit tests for the new function. Fixes #1576 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/rust/src/lib.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 0be93184..f4ce4d99 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -358,6 +358,35 @@ extern "C" fn ccxr_close_handle(handle: RawHandle) { } } +/// Normalize legacy single-dash long options to double-dash format. +/// +/// Old versions of ccextractor accepted `-quiet`, `-stdout`, etc. but clap +/// requires `--quiet`, `--stdout`. This function converts single-dash long +/// options to double-dash for backward compatibility. +/// +/// # Rules +/// - Single-dash options with multiple characters (e.g., `-quiet`) are converted to `--quiet` +/// - Double-dash options (e.g., `--quiet`) are left unchanged +/// - Single-letter short options (e.g., `-o`) are left unchanged +/// - Non-option arguments (e.g., `file.ts`) are left unchanged +/// - Numeric options (e.g., `-1`, `-12`) are left unchanged (these are valid short options) +fn normalize_legacy_option(arg: String) -> String { + // Check if it's a single-dash option with multiple characters (e.g., -quiet) + // but not a short option with a value (e.g., -o filename) + // Single-letter options like -o, -s should be left unchanged + // Numeric options like -1, -12 should also be left unchanged + if arg.starts_with('-') + && !arg.starts_with("--") + && arg.len() > 2 + && arg.chars().nth(1).is_some_and(|c| c.is_ascii_alphabetic()) + { + // Convert -option to --option + format!("-{}", arg) + } else { + arg + } +} + /// # Safety /// Safe if argv is a valid pointer /// @@ -380,6 +409,11 @@ pub unsafe extern "C" fn ccxr_parse_parameters(argc: c_int, argv: *mut *mut c_ch return ExitCause::NoInputFiles.exit_code(); } + // Backward compatibility: Convert single-dash long options to double-dash + // Old versions of ccextractor accepted -quiet, -stdout, etc. but clap requires --quiet, --stdout + // This allows scripts using the old syntax to continue working + let args: Vec = args.into_iter().map(normalize_legacy_option).collect(); + let args: Args = match Args::try_parse_from(args) { Ok(args) => args, Err(e) => { @@ -482,4 +516,108 @@ mod test { assert_eq!(decoder_ctx.processed_enough, 0); assert_eq!(unsafe { cb_708 }, 11); } + + #[test] + fn test_normalize_legacy_option_single_dash_long() { + // Single-dash long options should be converted to double-dash + assert_eq!( + normalize_legacy_option("-quiet".to_string()), + "--quiet".to_string() + ); + assert_eq!( + normalize_legacy_option("-stdout".to_string()), + "--stdout".to_string() + ); + assert_eq!( + normalize_legacy_option("-autoprogram".to_string()), + "--autoprogram".to_string() + ); + assert_eq!( + normalize_legacy_option("-goptime".to_string()), + "--goptime".to_string() + ); + } + + #[test] + fn test_normalize_legacy_option_double_dash() { + // Double-dash options should remain unchanged + assert_eq!( + normalize_legacy_option("--quiet".to_string()), + "--quiet".to_string() + ); + assert_eq!( + normalize_legacy_option("--stdout".to_string()), + "--stdout".to_string() + ); + assert_eq!( + normalize_legacy_option("--autoprogram".to_string()), + "--autoprogram".to_string() + ); + } + + #[test] + fn test_normalize_legacy_option_short_options() { + // Single-letter short options should remain unchanged + assert_eq!( + normalize_legacy_option("-o".to_string()), + "-o".to_string() + ); + assert_eq!( + normalize_legacy_option("-s".to_string()), + "-s".to_string() + ); + } + + #[test] + fn test_normalize_legacy_option_numeric_options() { + // Numeric options should remain unchanged (these are valid ccextractor options) + assert_eq!( + normalize_legacy_option("-1".to_string()), + "-1".to_string() + ); + assert_eq!( + normalize_legacy_option("-2".to_string()), + "-2".to_string() + ); + assert_eq!( + normalize_legacy_option("-12".to_string()), + "-12".to_string() + ); + } + + #[test] + fn test_normalize_legacy_option_non_options() { + // Non-option arguments should remain unchanged + assert_eq!( + normalize_legacy_option("file.ts".to_string()), + "file.ts".to_string() + ); + assert_eq!( + normalize_legacy_option("/path/to/file.ts".to_string()), + "/path/to/file.ts".to_string() + ); + assert_eq!( + normalize_legacy_option("ccextractor".to_string()), + "ccextractor".to_string() + ); + } + + #[test] + fn test_normalize_legacy_option_edge_cases() { + // Empty string + assert_eq!( + normalize_legacy_option("".to_string()), + "".to_string() + ); + // Just a dash + assert_eq!( + normalize_legacy_option("-".to_string()), + "-".to_string() + ); + // Double dash alone (end of options marker) + assert_eq!( + normalize_legacy_option("--".to_string()), + "--".to_string() + ); + } } From 77624ec678325d751fc43c41410af8b4b8d23ab6 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 20 Dec 2025 10:27:22 +0100 Subject: [PATCH 2/2] style: Run cargo fmt on Rust code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/rust/src/lib.rs | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index f4ce4d99..2f22a6ec 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -558,27 +558,15 @@ mod test { #[test] fn test_normalize_legacy_option_short_options() { // Single-letter short options should remain unchanged - assert_eq!( - normalize_legacy_option("-o".to_string()), - "-o".to_string() - ); - assert_eq!( - normalize_legacy_option("-s".to_string()), - "-s".to_string() - ); + assert_eq!(normalize_legacy_option("-o".to_string()), "-o".to_string()); + assert_eq!(normalize_legacy_option("-s".to_string()), "-s".to_string()); } #[test] fn test_normalize_legacy_option_numeric_options() { // Numeric options should remain unchanged (these are valid ccextractor options) - assert_eq!( - normalize_legacy_option("-1".to_string()), - "-1".to_string() - ); - assert_eq!( - normalize_legacy_option("-2".to_string()), - "-2".to_string() - ); + assert_eq!(normalize_legacy_option("-1".to_string()), "-1".to_string()); + assert_eq!(normalize_legacy_option("-2".to_string()), "-2".to_string()); assert_eq!( normalize_legacy_option("-12".to_string()), "-12".to_string() @@ -605,19 +593,10 @@ mod test { #[test] fn test_normalize_legacy_option_edge_cases() { // Empty string - assert_eq!( - normalize_legacy_option("".to_string()), - "".to_string() - ); + assert_eq!(normalize_legacy_option("".to_string()), "".to_string()); // Just a dash - assert_eq!( - normalize_legacy_option("-".to_string()), - "-".to_string() - ); + assert_eq!(normalize_legacy_option("-".to_string()), "-".to_string()); // Double dash alone (end of options marker) - assert_eq!( - normalize_legacy_option("--".to_string()), - "--".to_string() - ); + assert_eq!(normalize_legacy_option("--".to_string()), "--".to_string()); } }