Skip to content

Commit e5182c5

Browse files
committed
fix(cli): keep json output machine-readable with plural checks
1 parent a6e9899 commit e5182c5

3 files changed

Lines changed: 105 additions & 11 deletions

File tree

langcodec-cli/src/main.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,13 @@ fn main() {
578578

579579
if check_plurals {
580580
match codec.validate_plurals() {
581-
Ok(()) => println!("\n✅ Plural validation passed"),
581+
Ok(()) => {
582+
if json {
583+
eprintln!("✅ Plural validation passed");
584+
} else {
585+
println!("\n✅ Plural validation passed");
586+
}
587+
}
582588
Err(e) => {
583589
eprintln!("\n❌ Plural validation failed: {}", e);
584590
std::process::exit(2);

langcodec-cli/src/view.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,9 @@ fn render_json_output(
170170

171171
/// Print a view of the resources in a codec.
172172
pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOptions) {
173-
let keys_only_text = opts.keys_only && !opts.json;
174-
if !keys_only_text && !opts.json {
173+
let text_mode = !opts.json;
174+
let keys_only_text = opts.keys_only && text_mode;
175+
if text_mode && !keys_only_text {
175176
println!("Processing resources...");
176177
}
177178
let status_filter = match parse_status_filter(&opts.status) {
@@ -216,10 +217,6 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
216217
std::process::exit(1);
217218
}
218219

219-
if !keys_only_text && !opts.json {
220-
println!("✅ Found {} resource(s)", resources.len());
221-
}
222-
223220
let filtered_resources = resources
224221
.iter()
225222
.map(|resource| {
@@ -236,8 +233,21 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
236233
})
237234
.collect::<Vec<_>>();
238235

236+
let visible_resources = if status_filter.is_some() {
237+
filtered_resources
238+
.into_iter()
239+
.filter(|(_, entries)| !entries.is_empty())
240+
.collect::<Vec<_>>()
241+
} else {
242+
filtered_resources
243+
};
244+
245+
if text_mode && !keys_only_text {
246+
println!("✅ Found {} resource(s)", visible_resources.len());
247+
}
248+
239249
if opts.json {
240-
let rendered = match render_json_output(&filtered_resources, opts.keys_only) {
250+
let rendered = match render_json_output(&visible_resources, opts.keys_only) {
241251
Ok(text) => text,
242252
Err(err) => {
243253
eprintln!("❌ {}", err);
@@ -250,7 +260,7 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
250260

251261
if keys_only_text {
252262
let include_lang_prefix = lang_filter.is_none();
253-
for (resource, entries) in &filtered_resources {
263+
for (resource, entries) in &visible_resources {
254264
for entry in entries {
255265
if include_lang_prefix {
256266
println!("{}\t{}", resource.metadata.language, entry.id);
@@ -262,7 +272,7 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
262272
return;
263273
}
264274

265-
for (i, (resource, entries)) in filtered_resources.iter().enumerate() {
275+
for (i, (resource, entries)) in visible_resources.iter().enumerate() {
266276
println!("\n=== Resource {} ===", i + 1);
267277
println!("Language: {}", resource.metadata.language);
268278
println!("Domain: {}", resource.metadata.domain);
@@ -309,7 +319,7 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
309319
if lang_filter.is_none() {
310320
let mut unique_keys = HashSet::new();
311321
let mut per_language_counts: BTreeMap<String, usize> = BTreeMap::new();
312-
for (resource, entries) in &filtered_resources {
322+
for (resource, entries) in &visible_resources {
313323
per_language_counts
314324
.entry(resource.metadata.language.clone())
315325
.and_modify(|count| *count += entries.len())

langcodec-cli/tests/view_status_cli_tests.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,3 +630,81 @@ fn test_view_status_json_keys_only_lang_uses_consistent_object_schema() {
630630
assert!(keys.iter().all(|item| item["lang"] == "en"));
631631
assert!(keys.iter().all(|item| item["key"] == "needs_review_key"));
632632
}
633+
634+
#[test]
635+
fn test_view_status_json_with_check_plurals_keeps_stdout_json() {
636+
let temp_dir = TempDir::new().unwrap();
637+
let input_file = temp_dir.path().join("Localizable.xcstrings");
638+
write_xcstrings_multilang_fixture(&input_file);
639+
640+
let output = langcodec_cmd()
641+
.args([
642+
"view",
643+
"-i",
644+
input_file.to_str().unwrap(),
645+
"--status",
646+
"needs_review",
647+
"--json",
648+
"--check-plurals",
649+
])
650+
.output()
651+
.unwrap();
652+
653+
assert!(
654+
output.status.success(),
655+
"CLI failed: {}",
656+
String::from_utf8_lossy(&output.stderr)
657+
);
658+
659+
let stdout = String::from_utf8_lossy(&output.stdout);
660+
let _payload: serde_json::Value = serde_json::from_str(&stdout)
661+
.unwrap_or_else(|e| panic!("Expected JSON output. parse error: {e}. stdout: {stdout}"));
662+
663+
let stderr = String::from_utf8_lossy(&output.stderr);
664+
assert!(
665+
stderr.contains("Plural validation passed"),
666+
"Expected plural validation success in stderr. stderr: {}",
667+
stderr
668+
);
669+
}
670+
671+
#[test]
672+
fn test_view_status_text_excludes_zero_match_languages_from_output_and_summary() {
673+
let temp_dir = TempDir::new().unwrap();
674+
let input_file = temp_dir.path().join("Localizable.xcstrings");
675+
write_xcstrings_partial_match_fixture(&input_file);
676+
677+
let output = langcodec_cmd()
678+
.args([
679+
"view",
680+
"-i",
681+
input_file.to_str().unwrap(),
682+
"--status",
683+
"needs_review",
684+
])
685+
.output()
686+
.unwrap();
687+
688+
assert!(
689+
output.status.success(),
690+
"CLI failed: {}",
691+
String::from_utf8_lossy(&output.stderr)
692+
);
693+
694+
let stdout = String::from_utf8_lossy(&output.stdout);
695+
assert!(
696+
stdout.contains("Language: en"),
697+
"Expected matched language in output. stdout: {}",
698+
stdout
699+
);
700+
assert!(
701+
!stdout.contains("Language: fr"),
702+
"Expected zero-match language to be excluded. stdout: {}",
703+
stdout
704+
);
705+
assert!(
706+
stdout.contains("Total languages: 1"),
707+
"Expected summary to count only matching languages. stdout: {}",
708+
stdout
709+
);
710+
}

0 commit comments

Comments
 (0)