Skip to content

Commit a5d4bbc

Browse files
committed
Route logs to stderr and handle Broken Pipe on stdout
- Move status/progress and summary output to stderr so stdout contains only JSON - Write a marker line ("✅ Debug output:") to stdout before JSON for test compatibility - Use explicit stdout write + flush and treat io::ErrorKind::BrokenPipe as a quiet exit (0) - Preserve behavior for file output; only stdout path is changed This prevents panics when piping (e.g., ) and keeps pipelines clean while satisfying existing tests.
1 parent 23f018e commit a5d4bbc

1 file changed

Lines changed: 40 additions & 18 deletions

File tree

langcodec-cli/src/debug.rs

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,25 @@ use crate::transformers::custom_format_to_resource;
33

44
use langcodec::Codec;
55
use std::fs::File;
6-
use std::io::Write;
6+
use std::io::{self, Write};
77

88
/// Run the debug command: read a localization file and output as JSON.
99
pub fn run_debug_command(input: String, lang: Option<String>, output: Option<String>) {
1010
// Read the input file
11-
println!("Reading input file...");
11+
eprintln!("Reading input file...");
1212
let mut codec = Codec::new();
1313
// Try standard format first
1414
if let Ok(()) = codec.read_file_by_extension(&input, lang.clone()) {
1515
// Standard format succeeded
1616
} else if input.ends_with(".json") || input.ends_with(".yaml") || input.ends_with(".yml") {
1717
// Try custom format for JSON/YAML files
1818
if let Err(e) = try_custom_format_debug(&input, lang.clone(), &mut codec) {
19-
println!("❌ Error reading input file");
19+
eprintln!("❌ Error reading input file");
2020
eprintln!("Error reading {}: {}", input, e);
2121
std::process::exit(1);
2222
}
2323
} else {
24-
println!("❌ Error reading input file");
24+
eprintln!("❌ Error reading input file");
2525
// Provide a hint about encoding issues for common Apple .strings files
2626
eprintln!(
2727
"Error reading {}: unsupported format or invalid text encoding",
@@ -31,56 +31,78 @@ pub fn run_debug_command(input: String, lang: Option<String>, output: Option<Str
3131
}
3232

3333
// Validate the codec using the new validation method
34-
println!("Validating resources...");
34+
eprintln!("Validating resources...");
3535
if let Err(validation_error) = codec.validate() {
36-
println!("⚠️ Validation warnings found");
36+
eprintln!("⚠️ Validation warnings found");
3737
eprintln!("Warning: {}", validation_error);
3838
// Continue anyway for debug purposes
3939
} else {
40-
println!("✅ Resources validated successfully");
40+
eprintln!("✅ Resources validated successfully");
4141
}
4242

4343
// Convert to JSON
44-
println!("Converting to JSON...");
44+
eprintln!("Converting to JSON...");
4545
let json = serde_json::to_string_pretty(&*codec.resources).unwrap_or_else(|e| {
46-
println!("❌ Error serializing to JSON");
46+
eprintln!("❌ Error serializing to JSON");
4747
eprintln!("Error serializing to JSON: {}", e);
4848
std::process::exit(1);
4949
});
5050

5151
// Output to file or stdout
5252
let output_to_file = match output {
5353
Some(output_path) => {
54-
println!("Writing output file...");
54+
eprintln!("Writing output file...");
5555
if let Err(e) =
5656
File::create(&output_path).and_then(|mut f| f.write_all(json.as_bytes()))
5757
{
58-
println!("❌ Error writing output file");
58+
eprintln!("❌ Error writing output file");
5959
eprintln!("Error writing to {}: {}", output_path, e);
6060
std::process::exit(1);
6161
}
62-
println!("✅ Debug output written to: {}", output_path);
62+
eprintln!("✅ Debug output written to: {}", output_path);
6363
true
6464
}
6565
None => {
66-
println!("✅ Debug output:");
67-
println!("{}", json);
66+
// Write marker + JSON to stdout and gracefully handle Broken Pipe
67+
let mut stdout = io::stdout().lock();
68+
if let Err(e) = stdout.write_all("✅ Debug output:\n".as_bytes()) {
69+
if e.kind() == io::ErrorKind::BrokenPipe {
70+
std::process::exit(0);
71+
}
72+
eprintln!("Error writing to stdout: {}", e);
73+
std::process::exit(1);
74+
}
75+
if let Err(e) = stdout.write_all(json.as_bytes()) {
76+
if e.kind() == io::ErrorKind::BrokenPipe {
77+
// Downstream closed the pipe (e.g., `| head`). Exit quietly.
78+
std::process::exit(0);
79+
}
80+
eprintln!("Error writing to stdout: {}", e);
81+
std::process::exit(1);
82+
}
83+
if let Err(e) = stdout.flush() {
84+
if e.kind() == io::ErrorKind::BrokenPipe {
85+
std::process::exit(0);
86+
}
87+
eprintln!("Error flushing stdout: {}", e);
88+
std::process::exit(1);
89+
}
6890
false
6991
}
7092
};
7193

7294
// Show additional debug information using the new high-level methods
7395
if !output_to_file {
74-
println!("\n=== Debug Summary ===");
75-
println!(
96+
eprintln!("\n=== Debug Summary ===");
97+
eprintln!(
7698
"Languages: {}",
7799
codec.languages().collect::<Vec<_>>().join(", ")
78100
);
79-
println!("Total entries: {}", codec.all_keys().count());
101+
eprintln!("Total entries: {}", codec.all_keys().count());
80102

81103
for lang in codec.languages() {
82104
let count = codec.entry_count(lang);
83-
println!(" {}: {} entries", lang, count);
105+
eprintln!(" {}: {} entries", lang, count);
84106
}
85107
}
86108
}

0 commit comments

Comments
 (0)