Skip to content

Commit 23f018e

Browse files
committed
Parallelize input file reading in merge command
Refactors the merge command to read input files concurrently using rayon, improving performance for large numbers of files. Adds helper functions to handle both standard and custom formats, and updates error handling for better reporting. Bumps langcodec and CLI versions to 0.4.0.
1 parent 6f446f2 commit 23f018e

2 files changed

Lines changed: 64 additions & 45 deletions

File tree

langcodec-cli/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "langcodec-cli"
3-
version = "0.3.5"
3+
version = "0.4.0"
44
edition = "2024"
55
description = "A universal CLI tool for converting and inspecting localization files (Apple, Android, CSV, etc.)"
66
license = "MIT"
@@ -12,7 +12,7 @@ categories = ["command-line-utilities", "internationalization", "localization"]
1212
documentation = "https://docs.rs/langcodec-cli"
1313

1414
[dependencies]
15-
langcodec = "0.3.4"
15+
langcodec = "0.4.0"
1616
clap = { version = "4", features = ["derive"] }
1717
clap_complete = "4"
1818
unicode-width = "0.2.1"

langcodec-cli/src/merge.rs

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::formats::parse_custom_format;
22
use crate::transformers::custom_format_to_resource;
33

44
use langcodec::{Codec, converter};
5+
use rayon::prelude::*;
56

67
/// Strategy for handling conflicts when merging localization files.
78
#[derive(Debug, Clone, PartialEq, clap::ValueEnum)]
@@ -26,29 +27,28 @@ pub fn run_merge_command(
2627
std::process::exit(1);
2728
}
2829

29-
// Read all input files into a single codec
30-
let mut codec = Codec::new();
31-
for (i, input) in inputs.iter().enumerate() {
32-
println!("Reading file {}/{}: {}", i + 1, inputs.len(), input);
33-
34-
// Try standard format first
35-
if let Ok(()) = codec.read_file_by_extension(input, lang.clone()) {
36-
continue;
37-
}
38-
39-
// If standard format fails, try custom format for JSON/YAML files
40-
if (input.ends_with(".json") || input.ends_with(".yaml") || input.ends_with(".yml"))
41-
&& let Ok(()) = try_custom_format_merge(input, lang.clone(), &mut codec)
42-
{
43-
continue;
30+
// Read all input files concurrently into Codecs, then combine and merge
31+
println!("Reading {} input files...", inputs.len());
32+
let read_results: Vec<Result<Codec, String>> = inputs
33+
.par_iter()
34+
.map(|input| read_input_to_codec(input, lang.clone()))
35+
.collect();
36+
37+
let mut input_codecs: Vec<Codec> = Vec::with_capacity(read_results.len());
38+
for (idx, res) in read_results.into_iter().enumerate() {
39+
match res {
40+
Ok(c) => input_codecs.push(c),
41+
Err(e) => {
42+
println!("❌ Error reading input file {}/{}", idx + 1, inputs.len());
43+
eprintln!("{}", e);
44+
std::process::exit(1);
45+
}
4446
}
45-
46-
// If both fail, show error
47-
println!("❌ Error reading input file");
48-
eprintln!("Error reading {}: unsupported format", input);
49-
std::process::exit(1);
5047
}
5148

49+
// Combine all input codecs first, then merge by language
50+
let mut codec = Codec::from_codecs(input_codecs);
51+
5252
// Skip validation for merge operations since we expect multiple resources with potentially duplicate languages
5353

5454
// Merge resources using the new lib crate method
@@ -133,30 +133,49 @@ pub fn run_merge_command(
133133
);
134134
}
135135

136-
/// Try to read a custom format file and add it to the codec
137-
fn try_custom_format_merge(
136+
/// Read a single input file into a vector of Resources, supporting both standard and custom formats
137+
fn read_input_to_resources(
138138
input: &str,
139-
_lang: Option<String>,
140-
codec: &mut Codec,
141-
) -> Result<(), String> {
142-
// Validate custom format file
143-
crate::validation::validate_custom_format_file(input)?;
144-
145-
// Auto-detect format based on file content
146-
let file_content = std::fs::read_to_string(input)
147-
.map_err(|e| format!("Error reading file {}: {}", input, e))?;
148-
149-
// Validate file content
150-
crate::formats::validate_custom_format_content(input, &file_content)?;
151-
152-
// Convert custom format to Resource
153-
let resources =
154-
custom_format_to_resource(input.to_string(), parse_custom_format("json-language-map")?)?;
155-
156-
// Add resources to codec
157-
for resource in resources {
158-
codec.add_resource(resource);
139+
lang: Option<String>,
140+
) -> Result<Vec<langcodec::Resource>, String> {
141+
// Try standard format via lib crate (uses extension + language inference)
142+
{
143+
let mut local_codec = Codec::new();
144+
if let Ok(()) = local_codec.read_file_by_extension(input, lang.clone()) {
145+
return Ok(local_codec.resources);
146+
}
159147
}
160148

161-
Ok(())
149+
// Try custom JSON/YAML formats (for merge, we follow the existing JSON-language-map behavior)
150+
if input.ends_with(".json") || input.ends_with(".yaml") || input.ends_with(".yml") {
151+
// Validate custom format file
152+
crate::validation::validate_custom_format_file(input)
153+
.map_err(|e| format!("Failed to validate {}: {}", input, e))?;
154+
155+
// Auto-detect format based on file content
156+
let file_content = std::fs::read_to_string(input)
157+
.map_err(|e| format!("Error reading file {}: {}", input, e))?;
158+
159+
// Validate file content (ignore returned format; keep parity with existing merge behavior)
160+
crate::formats::validate_custom_format_content(input, &file_content)
161+
.map_err(|e| format!("Invalid custom format {}: {}", input, e))?;
162+
163+
// Convert custom format to Resource using JSON language map to match current merge behavior
164+
let resources = custom_format_to_resource(
165+
input.to_string(),
166+
parse_custom_format("json-language-map")
167+
.map_err(|e| format!("Failed to parse custom format: {}", e))?,
168+
)
169+
.map_err(|e| format!("Failed to convert custom format {}: {}", input, e))?;
170+
171+
return Ok(resources);
172+
}
173+
174+
Err(format!("Error reading {}: unsupported format", input))
175+
}
176+
177+
/// Read a single input into a Codec (wrapper over read_input_to_resources)
178+
fn read_input_to_codec(input: &str, lang: Option<String>) -> Result<Codec, String> {
179+
let resources = read_input_to_resources(input, lang)?;
180+
Ok(Codec { resources })
162181
}

0 commit comments

Comments
 (0)