diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e55fd8d..d96e8ba 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,7 @@ ### Pull request checklist +_Any relevant description here_ + - [ ] This pull request relates to an existing [issue ticket](https://github.com/mrtryhard/qt-ts-tools/issues). If not, create one. - [ ] [`CHANGELOG.md`](https://github.com/mrtryhard/qt-ts-tools/blob/main/CHANGELOG.md) is updated if relevant. - [ ] You are aware that your contributions is under [APACHE 2.0 License](https://github.com/mrtryhard/qt-ts-tools/blob/main/CONTRIBUTING.md) unless you specify otherwise. - ---- - -_Any other relevant description here_ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8593bb0..66e598d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ serde = { version = "1.0.215", features = ["derive"] } sys-locale = "0.3.2" [profile.release] -strip = true -lto = true codegen-units = 1 +lto = true panic = "abort" +strip = true diff --git a/src/commands/extract.rs b/src/commands/extract.rs index 6397ffd..c304507 100644 --- a/src/commands/extract.rs +++ b/src/commands/extract.rs @@ -4,6 +4,13 @@ use log::debug; use crate::ts::{TSNode, TranslationNode, TranslationType}; use crate::{tr, ts}; +#[derive(clap::ValueEnum, PartialEq, Debug, Clone)] +pub enum TranslationTypeArg { + Obsolete, + Unfinished, + Vanished, +} + /// Extracts a translation type messages and contexts from the input translation file. #[derive(Args)] #[command(disable_help_flag = true)] @@ -21,13 +28,6 @@ pub struct ExtractArgs { pub help: Option, } -#[derive(clap::ValueEnum, PartialEq, Debug, Clone)] -pub enum TranslationTypeArg { - Obsolete, - Unfinished, - Vanished, -} - /// Filters the translation file to keep only the messages containing unfinished translations. pub fn extract_main(args: &ExtractArgs) -> Result<(), String> { match quick_xml::Reader::from_file(&args.input_path) { diff --git a/src/commands/merge.rs b/src/commands/merge.rs index 0fddace..23c0ead 100644 --- a/src/commands/merge.rs +++ b/src/commands/merge.rs @@ -27,6 +27,33 @@ pub struct MergeArgs { pub help: Option, } +// This works by depending on cmp looking up only source and location on messages nodes +// and on context by comparing the names only +pub fn merge_main(args: &MergeArgs) -> Result<(), String> { + let left = load_file(&args.input_left); + let right = load_file(&args.input_right); + + if let Err(e) = left { + return Err(tr!( + "error-open-or-parse", + ("file", args.input_left.as_str()), + ("error", e.to_string()) + )); + } + + if let Err(e) = right { + return Err(tr!( + "error-open-or-parse", + ("file", args.input_right.as_str()), + ("error", e.to_string()) + )); + } + + let result = merge_ts_nodes(left.unwrap(), right.unwrap(), args.keep_translation); + + ts::write_to_output(&args.output_path, &result) +} + /// MessageNode that can be `eq(...)`. #[derive(Eq, PartialOrd, Clone)] struct EquatableMessageNode { @@ -56,33 +83,6 @@ impl Hash for EquatableMessageNode { } } -// This works by depending on cmp looking up only source and location on messages nodes -// and on context by comparing the names only -pub fn merge_main(args: &MergeArgs) -> Result<(), String> { - let left = load_file(&args.input_left); - let right = load_file(&args.input_right); - - if let Err(e) = left { - return Err(tr!( - "error-open-or-parse", - ("file", args.input_left.as_str()), - ("error", e.to_string()) - )); - } - - if let Err(e) = right { - return Err(tr!( - "error-open-or-parse", - ("file", args.input_right.as_str()), - ("error", e.to_string()) - )); - } - - let result = merge_ts_nodes(left.unwrap(), right.unwrap(), args.keep_translation); - - ts::write_to_output(&args.output_path, &result) -} - fn merge_ts_nodes(mut left: TSNode, mut right: TSNode, keep_translation: bool) -> TSNode { if keep_translation { debug!("--keep_translation flag is active, the following nodes will NOT be updated from the right-side file: translation, comment, oldcomment, oldsource, encoding"); diff --git a/src/commands/shell_completion.rs b/src/commands/shell_completion.rs index 248db49..5fd5a05 100644 --- a/src/commands/shell_completion.rs +++ b/src/commands/shell_completion.rs @@ -7,17 +7,6 @@ use clap_complete_nushell::Nushell; use crate::cli::Cli; use crate::locale::tr; -#[derive(Args)] -#[command(disable_help_flag = true)] -pub struct ShellCompletionArgs { - #[arg(value_enum, help = tr!("cli-shell-completion-shell"))] - shell: clap_complete_command::Shell, - #[arg(short, long, help = tr!("cli-shell-completion-install"))] - output_path: Option, - #[arg(short, long, action = ArgAction::Help, help = tr!("cli-help"), help_heading = tr!("cli-headers-options"))] - help: Option, -} - #[derive(Debug, Clone, ValueEnum)] #[value(rename_all = "lower")] pub enum GenShell { @@ -29,6 +18,17 @@ pub enum GenShell { Zsh, } +#[derive(Args)] +#[command(disable_help_flag = true)] +pub struct ShellCompletionArgs { + #[arg(value_enum, help = tr!("cli-shell-completion-shell"))] + shell: clap_complete_command::Shell, + #[arg(short, long, help = tr!("cli-shell-completion-install"))] + output_path: Option, + #[arg(short, long, action = ArgAction::Help, help = tr!("cli-help"), help_heading = tr!("cli-headers-options"))] + help: Option, +} + impl Generator for GenShell { fn file_name(&self, name: &str) -> String { match self { diff --git a/src/commands/stat.rs b/src/commands/stat.rs index 73d9ecd..166759e 100644 --- a/src/commands/stat.rs +++ b/src/commands/stat.rs @@ -25,39 +25,6 @@ pub struct StatArgs { pub help: Option, } -#[derive(Clone, Default, Eq, Ord, PartialEq, PartialOrd)] -struct FileStats { - pub filepath: String, - pub unfinished_translations: usize, - pub vanished_translations: usize, - pub obsolete_translations: usize, - pub finished_translation: usize, - /// For files, total_translations corresponds to number of time that file was - /// mentioned as a location. - pub total_translations: usize, -} - -#[derive(Default)] -struct TotalStats { - // Translation block - pub total_missing_translations: usize, - pub total_vanished_translations: usize, - pub total_obsolete_translations: usize, - /// Corresponds to the number of unique translation - /// This is the sum of obsolete, vanished, missing and complete translations. - pub total_unique_translations: usize, - /// Corresponds to the number of references in files. - /// For example, if a translation is the same for 3 files, it will return 3, not 1. - /// Even if 2 locations is in the same file, it will count as 2. - pub total_translations_references: usize, - pub total_contexts: usize, - pub total_messages: usize, - pub total_context_less_messages: usize, - - /// Statistics by file - pub files: Vec, -} - /// Aggregates the stats for provided file and arguments. pub fn stat_main(args: &StatArgs) -> Result<(), String> { match quick_xml::Reader::from_file(&args.input_path) { @@ -91,38 +58,37 @@ pub fn stat_main(args: &StatArgs) -> Result<(), String> { } } -/// Writes the output TS file to the specified output (file or stdout). -/// This writer will auto indent/pretty print. It will always expand empty nodes, e.g. -/// `` instead of ``. -pub fn write_to_output(output_path: &String, output: String) -> Result<(), String> { - debug!("Writing {} characters to '{output_path}'", output.len()); +#[derive(Clone, Default, Eq, Ord, PartialEq, PartialOrd)] +struct FileStats { + pub filepath: String, + pub unfinished_translations: usize, + pub vanished_translations: usize, + pub obsolete_translations: usize, + pub finished_translation: usize, + /// For files, total_translations corresponds to number of time that file was + /// mentioned as a location. + pub total_translations: usize, +} - match std::fs::File::options() - .create(true) - .truncate(true) - .write(true) - .open(output_path) - { - Ok(mut file) => match file.write(output.as_bytes()) { - Ok(bytes) => { - debug!("Successfully wrote {}", bytes); - Ok(()) - } - Err(err) => { - debug!("Failed to write to output_path: {err:?}"); - Err(tr!( - "error-write-output", - ("output_path", output_path), - ("error", err.to_string()) - )) - } - }, - Err(e) => Err(tr!( - "error-write-output-open", - ("output_path", output_path), - ("error", e.to_string()) - )), - } +#[derive(Default)] +struct TotalStats { + // Translation block + pub total_missing_translations: usize, + pub total_vanished_translations: usize, + pub total_obsolete_translations: usize, + /// Corresponds to the number of unique translation + /// This is the sum of obsolete, vanished, missing and complete translations. + pub total_unique_translations: usize, + /// Corresponds to the number of references in files. + /// For example, if a translation is the same for 3 files, it will return 3, not 1. + /// Even if 2 locations is in the same file, it will count as 2. + pub total_translations_references: usize, + pub total_contexts: usize, + pub total_messages: usize, + pub total_context_less_messages: usize, + + /// Statistics by file + pub files: Vec, } #[derive(Clone, Eq, Hash, PartialEq)] @@ -131,6 +97,78 @@ enum FileKey<'a> { Valid(&'a String), } +fn generate_message_for_stats(stats: TotalStats, verbose: bool) -> String { + let mut buf = String::new(); + + if verbose && !stats.files.is_empty() { + buf.push_str("------------------------------------------------------------------------------------------------------\n"); + buf.push_str(&format!("{}\r\n", tr!("cli-stat-detailed-report"))); + buf.push_str("------------------------------------------------------------------------------------------------------\n"); + + for file in &stats.files { + // ["Unfinished", "Finished", "Obsolete", "Vanished"] are literals in the xml file, let's not translate. + buf.push_str(&format!( + "{} \"{}\"\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n", + tr!("cli-stat-filepath-header"), + file.filepath, + tr!("cli-stat-translations-refs"), + file.total_translations, + "Unfinished", + file.unfinished_translations, + "Finished", + file.finished_translation, + "Obsolete", + file.obsolete_translations, + "Vanished", + file.vanished_translations + )); + } + } + + buf.push_str("------------------------------------------------------------------------------------------------------\n"); + buf.push_str(&format!("{}\n", tr!("cli-stat-file-summary"))); + buf.push_str("------------------------------------------------------------------------------------------------------\n"); + buf.push_str(&format!( + "{: <24} : {}\n", + tr!("cli-stat-files"), + stats.files.len() + )); + buf.push_str(&format!("{: <24} : {}\n", "Contexts", stats.total_contexts)); + buf.push_str(&format!("{: <24} : {}\n", "Messages", stats.total_messages)); + buf.push_str(&format!( + "{: <24} : {}\n", + tr!("cli-stat-messages-without-context"), + stats.total_context_less_messages + )); + buf.push_str(&format!( + "{: <24} : {}\n", + tr!("cli-stat-unique-translations"), + stats.total_unique_translations + )); + buf.push_str(&format!( + "{: <24} : {}\n", + tr!("cli-stat-translations-refs"), + stats.total_translations_references + )); + buf.push_str(&format!( + "{: <24} : {}\n", + tr!("cli-stat-type-translations", ("type", "Missing")), + stats.total_missing_translations + )); + buf.push_str(&format!( + "{: <24} : {}\n", + tr!("cli-stat-type-translations", ("type", "Obsolete")), + stats.total_obsolete_translations + )); + buf.push_str(&format!( + "{: <24} : {}\n", + tr!("cli-stat-type-translations", ("type", "Vanished")), + stats.total_vanished_translations + )); + + buf +} + fn stats_ts_node(ts_node: &TSNode) -> TotalStats { let mut stats = TotalStats { total_contexts: ts_node.contexts.len(), @@ -209,76 +247,38 @@ fn stats_for_messages<'a>( } } -fn generate_message_for_stats(stats: TotalStats, verbose: bool) -> String { - let mut buf = String::new(); - - if verbose && !stats.files.is_empty() { - buf.push_str("------------------------------------------------------------------------------------------------------\n"); - buf.push_str(&format!("{}\r\n", tr!("cli-stat-detailed-report"))); - buf.push_str("------------------------------------------------------------------------------------------------------\n"); +/// Writes the output TS file to the specified output (file or stdout). +/// This writer will auto indent/pretty print. It will always expand empty nodes, e.g. +/// `` instead of ``. +fn write_to_output(output_path: &String, output: String) -> Result<(), String> { + debug!("Writing {} characters to '{output_path}'", output.len()); - for file in &stats.files { - // ["Unfinished", "Finished", "Obsolete", "Vanished"] are literals in the xml file, let's not translate. - buf.push_str(&format!( - "{} \"{}\"\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n\t{: <25}: {}\r\n", - tr!("cli-stat-filepath-header"), - file.filepath, - tr!("cli-stat-translations-refs"), - file.total_translations, - "Unfinished", - file.unfinished_translations, - "Finished", - file.finished_translation, - "Obsolete", - file.obsolete_translations, - "Vanished", - file.vanished_translations - )); - } + match std::fs::File::options() + .create(true) + .truncate(true) + .write(true) + .open(output_path) + { + Ok(mut file) => match file.write(output.as_bytes()) { + Ok(bytes) => { + debug!("Successfully wrote {}", bytes); + Ok(()) + } + Err(err) => { + debug!("Failed to write to output_path: {err:?}"); + Err(tr!( + "error-write-output", + ("output_path", output_path), + ("error", err.to_string()) + )) + } + }, + Err(e) => Err(tr!( + "error-write-output-open", + ("output_path", output_path), + ("error", e.to_string()) + )), } - - buf.push_str("------------------------------------------------------------------------------------------------------\n"); - buf.push_str(&format!("{}\n", tr!("cli-stat-file-summary"))); - buf.push_str("------------------------------------------------------------------------------------------------------\n"); - buf.push_str(&format!( - "{: <24} : {}\n", - tr!("cli-stat-files"), - stats.files.len() - )); - buf.push_str(&format!("{: <24} : {}\n", "Contexts", stats.total_contexts)); - buf.push_str(&format!("{: <24} : {}\n", "Messages", stats.total_messages)); - buf.push_str(&format!( - "{: <24} : {}\n", - tr!("cli-stat-messages-without-context"), - stats.total_context_less_messages - )); - buf.push_str(&format!( - "{: <24} : {}\n", - tr!("cli-stat-unique-translations"), - stats.total_unique_translations - )); - buf.push_str(&format!( - "{: <24} : {}\n", - tr!("cli-stat-translations-refs"), - stats.total_translations_references - )); - buf.push_str(&format!( - "{: <24} : {}\n", - tr!("cli-stat-type-translations", ("type", "Missing")), - stats.total_missing_translations - )); - buf.push_str(&format!( - "{: <24} : {}\n", - tr!("cli-stat-type-translations", ("type", "Obsolete")), - stats.total_obsolete_translations - )); - buf.push_str(&format!( - "{: <24} : {}\n", - tr!("cli-stat-type-translations", ("type", "Vanished")), - stats.total_vanished_translations - )); - - buf } #[cfg(test)] diff --git a/src/locale.rs b/src/locale.rs index a2aae88..ecfc06f 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -83,12 +83,15 @@ pub fn initialize_locale() -> Box { localizer } -#[test] -fn test_tr_macro() { - let s = "MyFile".to_owned(); - assert_eq!( - tr!("error-open-or-parse", ("file", s), ("error", "Test")), - "Could not open or parse input file \"MyFile\". Reason: Test." - ); - assert_eq!(tr!("cli-merge-input-left"), "File to receive the merge."); +#[cfg(test)] +mod tests { + #[test] + fn test_tr_macro() { + let s = "MyFile".to_owned(); + assert_eq!( + tr!("error-open-or-parse", ("file", s), ("error", "Test")), + "Could not open or parse input file \"MyFile\". Reason: Test." + ); + assert_eq!(tr!("cli-merge-input-left"), "File to receive the merge."); + } }