Skip to content

Commit 910bd6a

Browse files
committed
Add format inference and fallback logic to merge command
Enhanced the merge command to infer output format from the file extension and use the appropriate conversion method. Added fallback logic for single-resource outputs and improved error handling for unknown formats. Extended integration tests to cover format inference, fallback behavior, and error cases.
1 parent 599b194 commit 910bd6a

2 files changed

Lines changed: 233 additions & 8 deletions

File tree

langcodec-cli/src/merge.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::formats::parse_custom_format;
22
use crate::transformers::custom_format_to_resource;
33

4-
use langcodec::Codec;
4+
use langcodec::{Codec, converter};
55

66
/// Strategy for handling conflicts when merging localization files.
77
#[derive(Debug, Clone, PartialEq, clap::ValueEnum)]
@@ -62,15 +62,34 @@ pub fn run_merge_command(
6262
let merge_count = codec.merge_resources(&conflict_strategy);
6363
println!("Merged {} language groups", merge_count);
6464

65-
// Write merged resource to output file using the new lib crate method
6665
println!("Writing merged output...");
67-
codec.resources.iter().for_each(|resource| {
68-
if let Err(e) = Codec::write_resource_to_file(resource, &output) {
69-
println!("❌ Error writing output file");
70-
eprintln!("Error writing to {}: {}", output, e);
71-
std::process::exit(1);
66+
match converter::infer_format_from_path(output.clone()) {
67+
Some(format) => {
68+
println!("Converting resources to format: {:?}", format);
69+
if let Err(e) = converter::convert_resources_to_format(codec.resources, &output, format)
70+
{
71+
println!("❌ Error converting resources to format");
72+
eprintln!("Error converting to {}: {}", output, e);
73+
std::process::exit(1);
74+
}
75+
}
76+
None => {
77+
if codec.resources.len() == 1 {
78+
println!("Writing single resource to output file");
79+
if let Some(resource) = codec.resources.first() {
80+
if let Err(e) = Codec::write_resource_to_file(resource, &output) {
81+
println!("❌ Error writing output file");
82+
eprintln!("Error writing to {}: {}", output, e);
83+
std::process::exit(1);
84+
}
85+
}
86+
} else {
87+
println!("❌ Error writing output file");
88+
eprintln!("Error writing to {}: multiple resources", output);
89+
std::process::exit(1);
90+
}
7291
}
73-
});
92+
}
7493

7594
println!(
7695
"✅ Successfully merged {} files into {}",

langcodec-cli/tests/cli_integration_tests.rs

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,3 +723,209 @@ fn test_merge_command_multiple_languages_no_merges() {
723723
assert!(merged_content.contains("hello"));
724724
assert!(merged_content.contains("goodbye"));
725725
}
726+
727+
#[test]
728+
fn test_merge_command_format_inference_strings() {
729+
let temp_dir = TempDir::new().unwrap();
730+
let input_file1 = temp_dir.path().join("file1.strings");
731+
let input_file2 = temp_dir.path().join("file2.strings");
732+
let output_file = temp_dir.path().join("merged.strings");
733+
734+
// Create two .strings files with different keys
735+
let content1 = r#"/* Greeting */
736+
"hello" = "Hello";"#;
737+
738+
let content2 = r#"/* Farewell */
739+
"goodbye" = "Goodbye";"#;
740+
741+
fs::write(&input_file1, content1).unwrap();
742+
fs::write(&input_file2, content2).unwrap();
743+
744+
let output = Command::new("cargo")
745+
.args([
746+
"run",
747+
"--",
748+
"merge",
749+
"-i",
750+
input_file1.to_str().unwrap(),
751+
"-i",
752+
input_file2.to_str().unwrap(),
753+
"-o",
754+
output_file.to_str().unwrap(),
755+
"--strategy",
756+
"last",
757+
])
758+
.output()
759+
.unwrap();
760+
761+
assert!(output.status.success());
762+
assert!(output_file.exists());
763+
764+
// Verify the output contains the format inference message
765+
let stdout = String::from_utf8_lossy(&output.stdout);
766+
assert!(
767+
stdout.contains("Converting resources to format:"),
768+
"Expected format inference message, got: {}",
769+
stdout
770+
);
771+
772+
// Verify the output contains the success message
773+
assert!(
774+
stdout.contains("✅ Successfully merged 2 files into"),
775+
"Expected success message, got: {}",
776+
stdout
777+
);
778+
779+
// Verify the merged file contains both entries
780+
let merged_content = fs::read_to_string(&output_file).unwrap();
781+
assert!(merged_content.contains("hello"));
782+
assert!(merged_content.contains("goodbye"));
783+
}
784+
785+
#[test]
786+
fn test_merge_command_format_inference_xml() {
787+
let temp_dir = TempDir::new().unwrap();
788+
let input_file1 = temp_dir.path().join("file1.strings");
789+
let input_file2 = temp_dir.path().join("file2.strings");
790+
let output_file = temp_dir.path().join("merged.xml");
791+
792+
// Create two .strings files with different keys
793+
let content1 = r#"/* Greeting */
794+
"hello" = "Hello";"#;
795+
796+
let content2 = r#"/* Farewell */
797+
"goodbye" = "Goodbye";"#;
798+
799+
fs::write(&input_file1, content1).unwrap();
800+
fs::write(&input_file2, content2).unwrap();
801+
802+
let output = Command::new("cargo")
803+
.args([
804+
"run",
805+
"--",
806+
"merge",
807+
"-i",
808+
input_file1.to_str().unwrap(),
809+
"-i",
810+
input_file2.to_str().unwrap(),
811+
"-o",
812+
output_file.to_str().unwrap(),
813+
"--strategy",
814+
"last",
815+
])
816+
.output()
817+
.unwrap();
818+
819+
assert!(output.status.success());
820+
assert!(output_file.exists());
821+
822+
// Verify the output contains the format inference message
823+
let stdout = String::from_utf8_lossy(&output.stdout);
824+
assert!(
825+
stdout.contains("Converting resources to format:"),
826+
"Expected format inference message, got: {}",
827+
stdout
828+
);
829+
830+
// Verify the output contains the success message
831+
assert!(
832+
stdout.contains("✅ Successfully merged 2 files into"),
833+
"Expected success message, got: {}",
834+
stdout
835+
);
836+
837+
// Verify the merged file contains XML content
838+
let merged_content = fs::read_to_string(&output_file).unwrap();
839+
assert!(merged_content.contains("<?xml"));
840+
assert!(merged_content.contains("<resources>"));
841+
}
842+
843+
#[test]
844+
fn test_merge_command_single_resource_fallback() {
845+
let temp_dir = TempDir::new().unwrap();
846+
let input_file = temp_dir.path().join("file.strings");
847+
let output_file = temp_dir.path().join("output.strings");
848+
849+
// Create a single .strings file
850+
let content = r#"/* Greeting */
851+
"hello" = "Hello";"#;
852+
853+
fs::write(&input_file, content).unwrap();
854+
855+
let output = Command::new("cargo")
856+
.args([
857+
"run",
858+
"--",
859+
"merge",
860+
"-i",
861+
input_file.to_str().unwrap(),
862+
"-o",
863+
output_file.to_str().unwrap(),
864+
"--strategy",
865+
"last",
866+
])
867+
.output()
868+
.unwrap();
869+
870+
assert!(output.status.success());
871+
assert!(output_file.exists());
872+
873+
// Since .strings extension can be inferred, it should use the format conversion path
874+
// Verify the output contains the format inference message
875+
let stdout = String::from_utf8_lossy(&output.stdout);
876+
assert!(
877+
stdout.contains("Converting resources to format:"),
878+
"Expected format inference message, got: {}",
879+
stdout
880+
);
881+
882+
// Verify the output contains the success message
883+
assert!(
884+
stdout.contains("✅ Successfully merged 1 files into"),
885+
"Expected success message, got: {}",
886+
stdout
887+
);
888+
889+
// Verify the output file contains the expected content
890+
let output_content = fs::read_to_string(&output_file).unwrap();
891+
assert!(output_content.contains("hello"));
892+
}
893+
894+
#[test]
895+
fn test_merge_command_actual_fallback_behavior() {
896+
let temp_dir = TempDir::new().unwrap();
897+
let input_file = temp_dir.path().join("file.strings");
898+
let output_file = temp_dir.path().join("output.unknown");
899+
900+
// Create a single .strings file
901+
let content = r#"/* Greeting */
902+
"hello" = "Hello";"#;
903+
904+
fs::write(&input_file, content).unwrap();
905+
906+
let output = Command::new("cargo")
907+
.args([
908+
"run",
909+
"--",
910+
"merge",
911+
"-i",
912+
input_file.to_str().unwrap(),
913+
"-o",
914+
output_file.to_str().unwrap(),
915+
"--strategy",
916+
"last",
917+
])
918+
.output()
919+
.unwrap();
920+
921+
// This should fail because the format cannot be inferred from .unknown extension
922+
// and the fallback also requires a valid extension
923+
assert!(!output.status.success());
924+
925+
let stderr = String::from_utf8_lossy(&output.stderr);
926+
assert!(
927+
stderr.contains("Cannot infer format from output path"),
928+
"Expected format inference error, got: {}",
929+
stderr
930+
);
931+
}

0 commit comments

Comments
 (0)