Skip to content

Commit 431c4c3

Browse files
committed
feat(cli): add tui dashboards
1 parent a42d956 commit 431c4c3

16 files changed

Lines changed: 1220 additions & 49 deletions

File tree

langcodec-cli/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ documentation = "https://docs.rs/langcodec-cli"
1515
langcodec = { path = "../langcodec", version = "0.9.1" }
1616
clap = { version = "4", features = ["derive"] }
1717
clap_complete = "4"
18-
unicode-width = "0.2.1"
18+
unicode-width = "0.2.0"
1919
crossterm = "0.29.0"
20+
ratatui = "0.29"
2021
atty = "0.2.14"
2122
async-trait = "0.1.89"
2223
mentra = "0.3.0"

langcodec-cli/src/convert.rs

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,7 @@ pub fn run_unified_convert_command(
240240
}
241241
println!(
242242
"{}",
243-
ui::status_line_stdout(
244-
ui::Tone::Success,
245-
"Successfully converted in strict mode",
246-
)
243+
ui::status_line_stdout(ui::Tone::Success, "Successfully converted in strict mode",)
247244
);
248245
return;
249246
}
@@ -270,10 +267,7 @@ pub fn run_unified_convert_command(
270267
}
271268
println!(
272269
"{}",
273-
ui::status_line_stdout(
274-
ui::Tone::Success,
275-
"Successfully converted in strict mode",
276-
)
270+
ui::status_line_stdout(ui::Tone::Success, "Successfully converted in strict mode",)
277271
);
278272
return;
279273
}
@@ -295,10 +289,7 @@ pub fn run_unified_convert_command(
295289
}
296290
println!(
297291
"{}",
298-
ui::status_line_stdout(
299-
ui::Tone::Success,
300-
"Successfully converted in strict mode",
301-
)
292+
ui::status_line_stdout(ui::Tone::Success, "Successfully converted in strict mode",)
302293
);
303294
return;
304295
}
@@ -332,10 +323,7 @@ pub fn run_unified_convert_command(
332323
if input.ends_with(".json") && options.input_format.is_none() {
333324
println!(
334325
"{}",
335-
ui::status_line_stdout(
336-
ui::Tone::Info,
337-
"Trying standard JSON format detection...",
338-
)
326+
ui::status_line_stdout(ui::Tone::Info, "Trying standard JSON format detection...",)
339327
);
340328
// Try to use the standard format detection which will show proper JSON parsing errors
341329
if let Err(e) = convert_auto(&input, &output) {
@@ -375,10 +363,7 @@ pub fn run_unified_convert_command(
375363
if let Err(e) = try_custom_format_conversion(&input, &output, &options.input_format) {
376364
println!(
377365
"{}",
378-
ui::status_line_stdout(
379-
ui::Tone::Error,
380-
"Custom format conversion failed",
381-
)
366+
ui::status_line_stdout(ui::Tone::Error, "Custom format conversion failed",)
382367
);
383368
eprintln!("Error: {}", e);
384369
std::process::exit(1);
@@ -405,10 +390,7 @@ pub fn run_unified_convert_command(
405390
if let Err(e) = try_explicit_format_conversion(&input, &output, &input_fmt, &output_fmt) {
406391
println!(
407392
"{}",
408-
ui::status_line_stdout(
409-
ui::Tone::Error,
410-
"Explicit format conversion failed",
411-
)
393+
ui::status_line_stdout(ui::Tone::Error, "Explicit format conversion failed",)
412394
);
413395
eprintln!("Error: {}", e);
414396
std::process::exit(1);

langcodec-cli/src/diff.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ fn print_or_write(output: Option<&String>, content: &str) -> Result<(), String>
4242

4343
fn render_human(report: &DiffReport) -> String {
4444
if ui::stdout_styled() {
45-
let mut lines = Vec::new();
46-
lines.push(ui::header("Diff"));
47-
lines.push(ui::key_value("Languages", report.summary.languages));
48-
lines.push(ui::key_value("Added", report.summary.added));
49-
lines.push(ui::key_value("Removed", report.summary.removed));
50-
lines.push(ui::key_value("Changed", report.summary.changed));
51-
lines.push(ui::key_value("Unchanged", report.summary.unchanged));
45+
let mut lines = vec![
46+
ui::header("Diff"),
47+
ui::key_value("Languages", report.summary.languages),
48+
ui::key_value("Added", report.summary.added),
49+
ui::key_value("Removed", report.summary.removed),
50+
ui::key_value("Changed", report.summary.changed),
51+
ui::key_value("Unchanged", report.summary.unchanged),
52+
];
5253

5354
for lang in &report.languages {
5455
lines.push(ui::section(&format!("Language {}", lang.language)));

langcodec-cli/src/edit.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,10 @@ fn apply_set_to_file(
268268
"{}",
269269
ui::status_line_stdout(
270270
ui::Tone::Info,
271-
&format!("Key '{}' not found in {} ({}); nothing to remove", key, l, input),
271+
&format!(
272+
"Key '{}' not found in {} ({}); nothing to remove",
273+
key, l, input
274+
),
272275
)
273276
);
274277
}
@@ -413,10 +416,7 @@ fn apply_set_to_file(
413416
} else {
414417
println!(
415418
"{}",
416-
ui::status_line_stdout(
417-
ui::Tone::Accent,
418-
&format!("Updated {} in place", input),
419-
)
419+
ui::status_line_stdout(ui::Tone::Accent, &format!("Updated {} in place", input),)
420420
);
421421
}
422422
}

langcodec-cli/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod formats;
77
pub mod merge;
88
pub mod transformers;
99
pub mod translate;
10+
pub mod tui;
1011
pub mod ui;
1112
pub mod validation;
1213

@@ -15,3 +16,4 @@ pub use formats::{CustomFormat, parse_custom_format};
1516
pub use langcodec::Codec;
1617
pub use transformers::custom_format_to_resource;
1718
pub use translate::{TranslateOptions, run_translate_command};
19+
pub use tui::UiMode;

langcodec-cli/src/main.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod stats;
1313
mod sync;
1414
mod transformers;
1515
mod translate;
16+
mod tui;
1617
mod ui;
1718
mod validation;
1819
mod view;
@@ -26,6 +27,7 @@ use crate::merge::{ConflictStrategy, run_merge_command};
2627
use crate::normalize::{NormalizeCliOptions, run_normalize_command};
2728
use crate::sync::{SyncOptions, run_sync_command};
2829
use crate::translate::{TranslateOptions, run_translate_command};
30+
use crate::tui::UiMode;
2931
use crate::validation::{ValidationContext, validate_context, validate_language_code};
3032
use crate::view::{ViewOptions, print_view, validate_status_filter};
3133
use clap::{CommandFactory, Parser, Subcommand};
@@ -306,6 +308,10 @@ enum Commands {
306308
/// Preview the translation run without writing files
307309
#[arg(long, default_value_t = false)]
308310
dry_run: bool,
311+
312+
/// Terminal UI mode: auto, plain, or tui
313+
#[arg(long = "ui", value_enum, default_value_t = UiMode::Auto)]
314+
ui_mode: UiMode,
309315
},
310316

311317
/// Generate translator-facing xcstrings comments from source usage with a Mentra agent.
@@ -349,6 +355,10 @@ enum Commands {
349355
/// Exit non-zero if comments would be added or refreshed
350356
#[arg(long, default_value_t = false)]
351357
check: bool,
358+
359+
/// Terminal UI mode: auto, plain, or tui
360+
#[arg(long = "ui", value_enum, default_value_t = UiMode::Auto)]
361+
ui_mode: UiMode,
352362
},
353363

354364
/// Debug: Read a localization file and output as JSON.
@@ -829,6 +839,7 @@ fn main() {
829839
concurrency,
830840
config,
831841
dry_run,
842+
ui_mode,
832843
} => {
833844
let mut context = ValidationContext::new();
834845
if let Some(source_path) = &source {
@@ -880,6 +891,7 @@ fn main() {
880891
config,
881892
dry_run,
882893
strict,
894+
ui_mode,
883895
}) {
884896
eprintln!(
885897
"{}",
@@ -899,6 +911,7 @@ fn main() {
899911
config,
900912
dry_run,
901913
check,
914+
ui_mode,
902915
} => {
903916
let mut context = ValidationContext::new();
904917
if let Some(input_path) = &input {
@@ -929,6 +942,7 @@ fn main() {
929942
config,
930943
dry_run,
931944
check,
945+
ui_mode,
932946
}) {
933947
eprintln!(
934948
"{}",

langcodec-cli/src/merge.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ pub fn run_merge_command(
2929
if inputs.is_empty() {
3030
eprintln!(
3131
"{}",
32-
ui::status_line_stderr(ui::Tone::Error, "Error: At least one input file is required.")
32+
ui::status_line_stderr(
33+
ui::Tone::Error,
34+
"Error: At least one input file is required."
35+
)
3336
);
3437
std::process::exit(1);
3538
}

langcodec-cli/src/stats.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ pub fn print_stats(
106106
(stats.translated as f64) * 100.0 / (stats.denominator as f64)
107107
};
108108

109-
println!("{}", ui::section(&format!("Language {}", res.metadata.language)));
109+
println!(
110+
"{}",
111+
ui::section(&format!("Language {}", res.metadata.language))
112+
);
110113
println!("{}", ui::divider(28));
111114
println!("{}", ui::key_value("Total", stats.total));
112115
println!(
@@ -161,7 +164,12 @@ pub fn print_stats(
161164
ui::key_value(
162165
"stale",
163166
ui::tone_text(
164-
&stats.by_status.get("stale").copied().unwrap_or(0).to_string(),
167+
&stats
168+
.by_status
169+
.get("stale")
170+
.copied()
171+
.unwrap_or(0)
172+
.to_string(),
165173
ui::Tone::Error,
166174
),
167175
)

langcodec-cli/src/sync.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ pub fn run_sync_command(opts: SyncOptions) -> Result<(), String> {
158158

159159
if ui::stdout_styled() {
160160
println!("{}", ui::header("Sync"));
161-
println!("{}", ui::key_value("Sync match language", &report.match_language));
161+
println!(
162+
"{}",
163+
ui::key_value("Sync match language", &report.match_language)
164+
);
162165
println!(
163166
"{}",
164167
ui::key_value("Total target entries considered", report.total_entries)
@@ -248,7 +251,10 @@ pub fn run_sync_command(opts: SyncOptions) -> Result<(), String> {
248251
"{}",
249252
ui::status_line_stdout(
250253
ui::Tone::Success,
251-
&format!("Sync complete: {}", opts.output.as_deref().unwrap_or(&opts.target)),
254+
&format!(
255+
"Sync complete: {}",
256+
opts.output.as_deref().unwrap_or(&opts.target)
257+
),
252258
)
253259
);
254260
Ok(())

0 commit comments

Comments
 (0)