From adc8b142c0dbd89a63fbded44fe3b7004e6364a7 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Mon, 27 May 2024 23:58:53 +0900 Subject: [PATCH 01/15] fixes #18: Add logging support. * Use `RUST_LOG=level` where `level` is either `trace`, `debug`, `info`, `warn`, `info` * Use `LOG_DIR=path to directory` to set the output directory. * The file will be output as `qt_ts_tools.log`. --- Cargo.lock | 256 ++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + src/extract.rs | 6 ++ src/locale.rs | 3 +- src/main.rs | 45 +++++++++ src/merge.rs | 32 ++++++- src/ts.rs | 9 ++ 7 files changed, 347 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7caa777..95caa84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,12 @@ dependencies = [ "serde", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.4" @@ -179,6 +185,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -204,6 +219,15 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "displaydoc" version = "0.2.4" @@ -321,8 +345,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -341,7 +365,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata", + "regex-automata 0.4.6", "same-file", "walkdir", "winapi-util", @@ -415,18 +439,61 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -457,6 +524,9 @@ dependencies = [ "quick-xml", "serde", "sys-locale", + "tracing", + "tracing-appender", + "tracing-subscriber", ] [[package]] @@ -478,6 +548,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + [[package]] name = "regex-automata" version = "0.4.6" @@ -486,9 +577,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.3", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.3" @@ -568,6 +665,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -629,6 +735,47 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.7.5" @@ -638,6 +785,79 @@ dependencies = [ "displaydoc", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "type-map" version = "0.5.0" @@ -702,6 +922,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "walkdir" version = "2.5.0" @@ -712,6 +938,22 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.8" @@ -721,6 +963,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index ebfa9a0..7970ab1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ lazy_static = "1.4.0" quick-xml = { version = "0.31.0", features = ["serialize"] } serde = { version = "1.0.203", features = ["derive"] } sys-locale = "0.3.1" +tracing = "0.1.40" +tracing-appender = "0.2.3" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [profile.release] strip = true diff --git a/src/extract.rs b/src/extract.rs index 5733a2e..846e8a9 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,4 +1,5 @@ use clap::{ArgAction, Args}; +use tracing::debug; use crate::locale::{tr, tr_args}; use crate::ts; @@ -77,6 +78,11 @@ fn retain_ts_node(ts_node: &mut TSNode, wanted_types: &[TranslationType]) { ts_node.contexts.retain_mut(|context| { context.messages.retain(|message| { message.translation.as_ref().is_some_and(|translation| { + debug!( + "Translation node candidate for being retained: {:?} | {:?}", + translation.translation_simple, translation.translation_type + ); + translation .translation_type .as_ref() diff --git a/src/locale.rs b/src/locale.rs index 2619567..794d125 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -14,7 +14,7 @@ static_loader! { } lazy_static! { - static ref CURRENT_LANG: LanguageIdentifier = { + pub(crate) static ref CURRENT_LANG: LanguageIdentifier = { LanguageIdentifier::from_str( sys_locale::get_locale() .unwrap_or("en".to_string()) @@ -39,7 +39,6 @@ lazy_static! { /// ```rust /// tr("some-text-id") /// ``` -#[allow(unused)] pub fn tr(text_id: &str) -> String { LOCALES.lookup(&CURRENT_LANG, text_id) } diff --git a/src/main.rs b/src/main.rs index d3b5572..a69046b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use tracing::{debug, error, info}; + use crate::cli::get_cli_result; mod cli; @@ -8,8 +10,51 @@ mod sort; mod ts; fn main() { + initialize_logging(); + + debug!( + "Using localization language: {}", + locale::CURRENT_LANG.language.to_string() + ); + if let Err(e) = get_cli_result() { + error!("Command returned error: {e}"); println!("{e}"); std::process::exit(1); } + + info!("Tool exits normally"); +} + +/// Initializes the logging in the application. +/// Logging will only be active when environment variable `RUST_LOG` is set. +/// ### Example +/// **Activating log** +/// ```bash +/// RUST_LOG="trace" ./qt-ts-tools +/// ``` +/// **Setting the output directory** +/// ```bash +/// RUST_LOG="trace" LOG_DIR=. ./qt-ts-tools +/// ``` +/// +/// ### Output +/// A file name `qt_ts_tools.log` should be output at `LOG_DIR` location. +fn initialize_logging() { + if let Ok(log_level) = std::env::var("RUST_LOG") { + let crate_name = env!("CARGO_PKG_NAME").replace('-', "_"); + + let log_file = std::env::var("LOG_DIR").unwrap_or(".".to_owned()); + let file_appender = tracing_appender::rolling::never(log_file, format!("{crate_name}.log")); + + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::new(format!( + "{}={log_level}", + crate_name + ))) + .with_writer(file_appender) + .with_ansi(false) + .pretty() + .init(); + } } diff --git a/src/merge.rs b/src/merge.rs index 1280538..ccd97f7 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -2,6 +2,7 @@ use std::hash::{Hash, Hasher}; use clap::{ArgAction, Args}; use itertools::Itertools; +use tracing::debug; use crate::locale::{tr, tr_args}; use crate::ts; @@ -53,7 +54,7 @@ impl Hash for EquatableMessageNode { } } -// This wortks by depending on cmp looking up only source and location on messages nodes +// 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); @@ -100,12 +101,26 @@ fn merge_contexts(left: &mut TSNode, right: TSNode) { .find(|left_context| left_context.name == right_context.name); if let Some(left_context) = left_context_opt { + debug!( + "Found context '{}' matching in left and right files.", + left_context.name + ); + debug!( + "Left context has {} messages, Right context has {} messages.", + left_context.messages.len(), + right_context.messages.len() + ); + left_context.comment = right_context.comment; left_context.encoding = right_context.encoding; left_context.messages = merge_messages(&mut left_context.messages, &mut right_context.messages); } else { + debug!( + "No matching context with name '{}' in left file.", + right_context.name + ); left.contexts.push(right_context); } }); @@ -133,7 +148,17 @@ fn merge_messages( .find(|&msg| msg == right_message); if let Some(left_message) = left_message { + debug!( + "Found matching message with source '{:?}' and id '{:?}' ", + left_message.node.source, left_message.node.id + ); + if right_message.node.source != left_message.node.source { + debug!( + "Updating source '{:?}' to '{:?}'", + left_message.node.source, right_message.node.source + ); + right_message .node .oldsource @@ -141,6 +166,11 @@ fn merge_messages( } if right_message.node.comment != left_message.node.comment { + debug!( + "Updating comment '{:?}' to '{:?}'", + left_message.node.comment, right_message.node.comment + ); + right_message .node .oldcomment diff --git a/src/ts.rs b/src/ts.rs index 6a24b2c..084d4f2 100644 --- a/src/ts.rs +++ b/src/ts.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use std::io::{BufWriter, Write}; use serde::{Deserialize, Serialize}; +use tracing::debug; use crate::locale::tr_args; @@ -277,6 +278,12 @@ impl Ord for ContextNode { /// This writer will auto indent/pretty print. It will always expand empty nodes, e.g. /// `` instead of ``. pub fn write_to_output(output_path: &Option, node: &TSNode) -> Result<(), String> { + debug!( + "Writing output to '{output_path:?}': Node contexts={}, standalone messages={}", + node.contexts.len(), + node.messages.len() + ); + let mut inner_writer: BufWriter> = match &output_path { None => BufWriter::new(Box::new(std::io::stdout().lock())), Some(output_path) => match std::fs::File::options() @@ -306,6 +313,8 @@ pub fn write_to_output(output_path: &Option, node: &TSNode) -> Result<() match node.serialize(ser) { Ok(_) => { + debug!("Bytes to write: {}", output_buffer.len()); + let res = inner_writer.write_all(output_buffer.as_bytes()); match res { Ok(_) => Ok(()), From 384eac2d2d6a211d92ce58d3bd047235be6d19da Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Tue, 28 May 2024 00:01:53 +0900 Subject: [PATCH 02/15] update: Add logging info to CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbd166f..e2c01ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.5.0] - unreleased + +### Added + +- The tool now support logging for debugging and observability purpose [#18](https://github.com/mrtryhard/qt-ts-tools/issues/18) + +### Changed + +### Fixed + ## [0.4.0] - 2024-05-24 [Fifth milestone](https://github.com/mrtryhard/qt-ts-tools/milestone/4). This release introduces some user experience improvement efforts such as localization (adopting current system language) and auto-completion support. From 9592fe0a3297a9c078d228a96ed8f807cac3acd5 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Mon, 3 Jun 2024 23:34:47 +0900 Subject: [PATCH 03/15] update #41: Update comments for TS file. --- src/merge.rs | 4 ++-- src/ts.rs | 66 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/merge.rs b/src/merge.rs index 1280538..3414b45 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -136,14 +136,14 @@ fn merge_messages( if right_message.node.source != left_message.node.source { right_message .node - .oldsource + .old_source .clone_from(&left_message.node.source); } if right_message.node.comment != left_message.node.comment { right_message .node - .oldcomment + .old_comment .clone_from(&left_message.node.comment); } } diff --git a/src/ts.rs b/src/ts.rs index 6a24b2c..d0b576e 100644 --- a/src/ts.rs +++ b/src/ts.rs @@ -10,15 +10,19 @@ use crate::locale::tr_args; // For now they can't handle Qt's semi-weird XSD. // https://doc.qt.io/qt-6/linguist-ts-file-format.html -/// If no type is set, a message is "finished". +/// TranslationType defines the status of a translation (aka the progress) #[derive(Debug, Default, Clone, Eq, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum TranslationType { + /// Translation is completed #[default] #[serde(skip)] Finished, + /// Translation is not finished Unfinished, + /// Translation requires an update Obsolete, + /// Translation is not used anymore Vanished, } @@ -29,29 +33,39 @@ pub enum YesNo { No, } +/// Root node of the translation file. #[derive(Debug, Deserialize, Serialize, PartialEq)] #[serde(rename = "TS")] pub struct TSNode { + /// Defines the version of the TS format, although unused by this tool. #[serde(rename = "@version", skip_serializing_if = "Option::is_none")] pub version: Option, + /// Source language on which this translation is based on. #[serde(rename = "@sourcelanguage", skip_serializing_if = "Option::is_none")] pub source_language: Option, + /// Language of this translation. #[serde(rename = "@language", skip_serializing_if = "Option::is_none")] pub language: Option, + /// Translations attached to a context #[serde(rename = "context", skip_serializing_if = "Vec::is_empty", default)] pub contexts: Vec, + /// Standalone translation messages. #[serde(rename = "message", skip_serializing_if = "Vec::is_empty", default)] pub messages: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub dependencies: Option, + /// Translation comment. #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub oldcomment: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub extracomment: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub translatorcomment: Option, + /// Previous translation comment. + #[serde(rename = "oldcomment", skip_serializing_if = "Option::is_none")] + pub old_comment: Option, + /// Other, extra comment + #[serde(rename = "extracomment", skip_serializing_if = "Option::is_none")] + pub extra_comment: Option, + /// Translator comment + #[serde(rename = "translatorcomment", skip_serializing_if = "Option::is_none")] + pub translator_comment: Option, /* Following section corresponds to `extra-something` in Qt's XSD. From documentation: > extra elements may appear in TS and message elements. Each element may appear @@ -82,13 +96,18 @@ pub struct TSNode { pub loc_blank: Option, } +/// Context and its associated translated message. #[derive(Debug, Eq, Deserialize, Serialize, PartialEq)] pub struct ContextNode { + /// Unique name of the context pub name: String, + /// List of translation messages #[serde(rename = "message")] pub messages: Vec, + /// Comment describing information about the context #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, + /// Encoding of the messages within that context. #[serde(rename = "@encoding", skip_serializing_if = "Option::is_none")] pub encoding: Option, } @@ -104,34 +123,40 @@ pub struct Dependency { pub catalog: String, } +/// Translation message node. #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct MessageNode { /// Original string to translate #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, - /// Result of a merge - #[serde(skip_serializing_if = "Option::is_none")] - pub oldsource: Option, + /// Old source before a merge. Merging will set that field. + #[serde(rename = "oldsource", skip_serializing_if = "Option::is_none")] + pub old_source: Option, + /// Translation in the target language. #[serde(skip_serializing_if = "Option::is_none")] pub translation: Option, + /// Lines and files in which the translation message is used. #[serde(skip_serializing_if = "Vec::is_empty", rename = "location", default)] pub locations: Vec, /// This is "disambiguation" in the (new) API, or "msgctxt" in gettext speak #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, /// Previous content of comment (result of merge) - #[serde(skip_serializing_if = "Option::is_none")] - pub oldcomment: Option, + #[serde(rename = "oldcomment", skip_serializing_if = "Option::is_none")] + pub old_comment: Option, /// The real comment (added by developer/designer) - #[serde(skip_serializing_if = "Option::is_none")] - pub extracomment: Option, + #[serde(rename = "extracomment", skip_serializing_if = "Option::is_none")] + pub extra_comment: Option, /// Comment added by translator - #[serde(skip_serializing_if = "Option::is_none")] - pub translatorcomment: Option, + #[serde(rename = "translatorcomment", skip_serializing_if = "Option::is_none")] + pub translator_comment: Option, + /// Support for the plural forms #[serde(rename = "@numerus", skip_serializing_if = "Option::is_none")] pub numerus: Option, + /// Message unique id (not guaranteed to be existant) #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, + /// Extra information #[serde(skip_serializing_if = "Option::is_none")] pub userdata: Option, /* @@ -164,30 +189,39 @@ pub struct MessageNode { pub loc_blank: Option, } +/// Translation node that indicates an actual translation for a message. #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct TranslationNode { // Did not find a way to make it an enum // Therefore: either you have a `translation_simple` or a `numerus_forms`, but not both. + /// Simple translation version, which do not take plural forms into account #[serde(rename = "$text", skip_serializing_if = "Option::is_none")] pub translation_simple: Option, + /// Plural forms for the translation #[serde(rename = "numerusform", skip_serializing_if = "Vec::is_empty", default)] pub numerus_forms: Vec, + /// Translation type (which represents the translation status) #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] pub translation_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub variants: Option, + /// Extra data #[serde(skip_serializing_if = "Option::is_none")] pub userdata: Option, // deprecated } +/// Location of a translation #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct LocationNode { + /// File from which the translation source originates from. #[serde(rename = "@filename", skip_serializing_if = "Option::is_none")] pub filename: Option, + /// Line where the source of the translation message is located in the file. #[serde(rename = "@line", skip_serializing_if = "Option::is_none")] pub line: Option, } +/// Represents a translation plural form. #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct NumerusFormNode { #[serde(default, rename = "$value", skip_serializing_if = "String::is_empty")] From 3bc58bb875b9c56f81e0f86ed10011853ad6da13 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Mon, 3 Jun 2024 23:34:47 +0900 Subject: [PATCH 04/15] update #41: Update comments for TS file. --- src/merge.rs | 4 ++-- src/ts.rs | 66 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/merge.rs b/src/merge.rs index ccd97f7..cfd3056 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -161,7 +161,7 @@ fn merge_messages( right_message .node - .oldsource + .old_source .clone_from(&left_message.node.source); } @@ -173,7 +173,7 @@ fn merge_messages( right_message .node - .oldcomment + .old_comment .clone_from(&left_message.node.comment); } } diff --git a/src/ts.rs b/src/ts.rs index 084d4f2..860359f 100644 --- a/src/ts.rs +++ b/src/ts.rs @@ -11,15 +11,19 @@ use crate::locale::tr_args; // For now they can't handle Qt's semi-weird XSD. // https://doc.qt.io/qt-6/linguist-ts-file-format.html -/// If no type is set, a message is "finished". +/// TranslationType defines the status of a translation (aka the progress) #[derive(Debug, Default, Clone, Eq, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum TranslationType { + /// Translation is completed #[default] #[serde(skip)] Finished, + /// Translation is not finished Unfinished, + /// Translation requires an update Obsolete, + /// Translation is not used anymore Vanished, } @@ -30,29 +34,39 @@ pub enum YesNo { No, } +/// Root node of the translation file. #[derive(Debug, Deserialize, Serialize, PartialEq)] #[serde(rename = "TS")] pub struct TSNode { + /// Defines the version of the TS format, although unused by this tool. #[serde(rename = "@version", skip_serializing_if = "Option::is_none")] pub version: Option, + /// Source language on which this translation is based on. #[serde(rename = "@sourcelanguage", skip_serializing_if = "Option::is_none")] pub source_language: Option, + /// Language of this translation. #[serde(rename = "@language", skip_serializing_if = "Option::is_none")] pub language: Option, + /// Translations attached to a context #[serde(rename = "context", skip_serializing_if = "Vec::is_empty", default)] pub contexts: Vec, + /// Standalone translation messages. #[serde(rename = "message", skip_serializing_if = "Vec::is_empty", default)] pub messages: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub dependencies: Option, + /// Translation comment. #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub oldcomment: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub extracomment: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub translatorcomment: Option, + /// Previous translation comment. + #[serde(rename = "oldcomment", skip_serializing_if = "Option::is_none")] + pub old_comment: Option, + /// Other, extra comment + #[serde(rename = "extracomment", skip_serializing_if = "Option::is_none")] + pub extra_comment: Option, + /// Translator comment + #[serde(rename = "translatorcomment", skip_serializing_if = "Option::is_none")] + pub translator_comment: Option, /* Following section corresponds to `extra-something` in Qt's XSD. From documentation: > extra elements may appear in TS and message elements. Each element may appear @@ -83,13 +97,18 @@ pub struct TSNode { pub loc_blank: Option, } +/// Context and its associated translated message. #[derive(Debug, Eq, Deserialize, Serialize, PartialEq)] pub struct ContextNode { + /// Unique name of the context pub name: String, + /// List of translation messages #[serde(rename = "message")] pub messages: Vec, + /// Comment describing information about the context #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, + /// Encoding of the messages within that context. #[serde(rename = "@encoding", skip_serializing_if = "Option::is_none")] pub encoding: Option, } @@ -105,34 +124,40 @@ pub struct Dependency { pub catalog: String, } +/// Translation message node. #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct MessageNode { /// Original string to translate #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, - /// Result of a merge - #[serde(skip_serializing_if = "Option::is_none")] - pub oldsource: Option, + /// Old source before a merge. Merging will set that field. + #[serde(rename = "oldsource", skip_serializing_if = "Option::is_none")] + pub old_source: Option, + /// Translation in the target language. #[serde(skip_serializing_if = "Option::is_none")] pub translation: Option, + /// Lines and files in which the translation message is used. #[serde(skip_serializing_if = "Vec::is_empty", rename = "location", default)] pub locations: Vec, /// This is "disambiguation" in the (new) API, or "msgctxt" in gettext speak #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, /// Previous content of comment (result of merge) - #[serde(skip_serializing_if = "Option::is_none")] - pub oldcomment: Option, + #[serde(rename = "oldcomment", skip_serializing_if = "Option::is_none")] + pub old_comment: Option, /// The real comment (added by developer/designer) - #[serde(skip_serializing_if = "Option::is_none")] - pub extracomment: Option, + #[serde(rename = "extracomment", skip_serializing_if = "Option::is_none")] + pub extra_comment: Option, /// Comment added by translator - #[serde(skip_serializing_if = "Option::is_none")] - pub translatorcomment: Option, + #[serde(rename = "translatorcomment", skip_serializing_if = "Option::is_none")] + pub translator_comment: Option, + /// Support for the plural forms #[serde(rename = "@numerus", skip_serializing_if = "Option::is_none")] pub numerus: Option, + /// Message unique id (not guaranteed to be existant) #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, + /// Extra information #[serde(skip_serializing_if = "Option::is_none")] pub userdata: Option, /* @@ -165,30 +190,39 @@ pub struct MessageNode { pub loc_blank: Option, } +/// Translation node that indicates an actual translation for a message. #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct TranslationNode { // Did not find a way to make it an enum // Therefore: either you have a `translation_simple` or a `numerus_forms`, but not both. + /// Simple translation version, which do not take plural forms into account #[serde(rename = "$text", skip_serializing_if = "Option::is_none")] pub translation_simple: Option, + /// Plural forms for the translation #[serde(rename = "numerusform", skip_serializing_if = "Vec::is_empty", default)] pub numerus_forms: Vec, + /// Translation type (which represents the translation status) #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] pub translation_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub variants: Option, + /// Extra data #[serde(skip_serializing_if = "Option::is_none")] pub userdata: Option, // deprecated } +/// Location of a translation #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct LocationNode { + /// File from which the translation source originates from. #[serde(rename = "@filename", skip_serializing_if = "Option::is_none")] pub filename: Option, + /// Line where the source of the translation message is located in the file. #[serde(rename = "@line", skip_serializing_if = "Option::is_none")] pub line: Option, } +/// Represents a translation plural form. #[derive(Debug, Eq, Clone, Deserialize, Serialize, PartialEq)] pub struct NumerusFormNode { #[serde(default, rename = "$value", skip_serializing_if = "String::is_empty")] From 10d65181d6c94951a53dac3dc4b12007388b9850 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Mon, 3 Jun 2024 23:46:36 +0900 Subject: [PATCH 05/15] update #41: Update CHANGELOG.md and bump version. --- CHANGELOG.md | 3 +++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c01ff..9b0856d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.5.0] - unreleased +[Sixth milestone](https://github.com/mrtryhard/qt-ts-tools/milestone/6). This release aims to polish what's been done so far. ### Added @@ -12,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Improved code documentation for reusable parsing TS structures [#41](https://github.com/mrtryhard/qt-ts-tools/issues/41) + ### Fixed ## [0.4.0] - 2024-05-24 diff --git a/Cargo.lock b/Cargo.lock index 95caa84..d2e89b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,7 +511,7 @@ dependencies = [ [[package]] name = "qt-ts-tools" -version = "0.4.0" +version = "0.5.0" dependencies = [ "clap", "clap_complete", diff --git a/Cargo.toml b/Cargo.toml index 7970ab1..83586cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/mrtryhard/qt-ts-tools" keywords = ["qt", "translation"] homepage = "https://github.com/mrtryhard/qt-ts-tools" license = "MIT OR Apache-2.0" -version = "0.4.0" +version = "0.5.0" edition = "2021" description = "Small command line utility to manipulate Qt's translation files with diverse operations." From 4746845e393385bdb248bff84281c39f59f0788b Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Mon, 10 Jun 2024 22:08:38 +0900 Subject: [PATCH 06/15] refactor #57: Move commands in folder. --- src/cli.rs | 6 ++--- src/{ => commands}/extract.rs | 0 src/{ => commands}/merge.rs | 0 src/commands/mod.rs | 3 +++ src/{ => commands}/sort.rs | 0 src/logging.rs | 32 +++++++++++++++++++++++++++ src/main.rs | 41 ++++------------------------------- 7 files changed, 42 insertions(+), 40 deletions(-) rename src/{ => commands}/extract.rs (100%) rename src/{ => commands}/merge.rs (100%) create mode 100644 src/commands/mod.rs rename src/{ => commands}/sort.rs (100%) create mode 100644 src/logging.rs diff --git a/src/cli.rs b/src/cli.rs index 4b3b64e..457132c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,10 +2,10 @@ use clap::{ArgAction, CommandFactory, Parser, Subcommand, ValueEnum}; use clap_complete::{Generator, Shell}; use clap_complete_nushell::Nushell; -use crate::extract::{extract_main, ExtractArgs}; +use crate::commands::extract::{extract_main, ExtractArgs}; +use crate::commands::merge::{merge_main, MergeArgs}; +use crate::commands::sort::{sort_main, SortArgs}; use crate::locale::tr; -use crate::merge::{merge_main, MergeArgs}; -use crate::sort::{sort_main, SortArgs}; #[derive(Parser)] #[command(author, diff --git a/src/extract.rs b/src/commands/extract.rs similarity index 100% rename from src/extract.rs rename to src/commands/extract.rs diff --git a/src/merge.rs b/src/commands/merge.rs similarity index 100% rename from src/merge.rs rename to src/commands/merge.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..11c85dd --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,3 @@ +pub mod extract; +pub mod merge; +pub mod sort; diff --git a/src/sort.rs b/src/commands/sort.rs similarity index 100% rename from src/sort.rs rename to src/commands/sort.rs diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..6b9f3f7 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,32 @@ +/// Initializes the logging in the application. +/// Logging will only be active when environment variable `RUST_LOG` is set. +/// ### Example +/// **Activating log** +/// ```bash +/// RUST_LOG="trace" ./qt-ts-tools +/// ``` +/// **Setting the output directory** +/// ```bash +/// RUST_LOG="trace" LOG_DIR=. ./qt-ts-tools +/// ``` +/// +/// ### Output +/// A file name `qt_ts_tools.log` should be output at `LOG_DIR` location. +pub fn initialize_logging() { + if let Ok(log_level) = std::env::var("RUST_LOG") { + let crate_name = env!("CARGO_PKG_NAME").replace('-', "_"); + + let log_file = std::env::var("LOG_DIR").unwrap_or(".".to_owned()); + let file_appender = tracing_appender::rolling::never(log_file, format!("{crate_name}.log")); + + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::new(format!( + "{}={log_level}", + crate_name + ))) + .with_writer(file_appender) + .with_ansi(false) + .pretty() + .init(); + } +} diff --git a/src/main.rs b/src/main.rs index a69046b..0bc96be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ use tracing::{debug, error, info}; use crate::cli::get_cli_result; +use crate::logging::initialize_logging; mod cli; -mod extract; +mod commands; mod locale; -mod merge; -mod sort; +mod logging; mod ts; fn main() { @@ -19,42 +19,9 @@ fn main() { if let Err(e) = get_cli_result() { error!("Command returned error: {e}"); - println!("{e}"); + eprintln!("{e}"); std::process::exit(1); } info!("Tool exits normally"); } - -/// Initializes the logging in the application. -/// Logging will only be active when environment variable `RUST_LOG` is set. -/// ### Example -/// **Activating log** -/// ```bash -/// RUST_LOG="trace" ./qt-ts-tools -/// ``` -/// **Setting the output directory** -/// ```bash -/// RUST_LOG="trace" LOG_DIR=. ./qt-ts-tools -/// ``` -/// -/// ### Output -/// A file name `qt_ts_tools.log` should be output at `LOG_DIR` location. -fn initialize_logging() { - if let Ok(log_level) = std::env::var("RUST_LOG") { - let crate_name = env!("CARGO_PKG_NAME").replace('-', "_"); - - let log_file = std::env::var("LOG_DIR").unwrap_or(".".to_owned()); - let file_appender = tracing_appender::rolling::never(log_file, format!("{crate_name}.log")); - - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::new(format!( - "{}={log_level}", - crate_name - ))) - .with_writer(file_appender) - .with_ansi(false) - .pretty() - .init(); - } -} From 61e69cecaf6e53398384d5661d0a236cd2ca898b Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Mon, 10 Jun 2024 22:22:09 +0900 Subject: [PATCH 07/15] refactor #57: Move shell completions in their own file. --- src/cli.rs | 60 +++--------------------------- src/commands/mod.rs | 1 + src/commands/shell_completion.rs | 63 ++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 55 deletions(-) create mode 100644 src/commands/shell_completion.rs diff --git a/src/cli.rs b/src/cli.rs index 457132c..da34bff 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,9 +1,8 @@ -use clap::{ArgAction, CommandFactory, Parser, Subcommand, ValueEnum}; -use clap_complete::{Generator, Shell}; -use clap_complete_nushell::Nushell; +use clap::{ArgAction, Parser, Subcommand}; use crate::commands::extract::{extract_main, ExtractArgs}; use crate::commands::merge::{merge_main, MergeArgs}; +use crate::commands::shell_completion::{shell_completion_main, ShellCompletionArgs}; use crate::commands::sort::{sort_main, SortArgs}; use crate::locale::tr; @@ -33,54 +32,8 @@ enum Commands { Extract(ExtractArgs), #[command(about = tr("cli-merge-desc"))] Merge(MergeArgs), - #[command(name = "shell-completion", about = tr("cli-shell-completion-desc"), disable_help_flag = true)] - ShellCompletion { - #[arg(value_enum)] - shell: clap_complete_command::Shell, - #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] - help: bool, - }, -} - -#[derive(Debug, Clone, ValueEnum)] -#[value(rename_all = "lower")] -pub enum GenShell { - Bash, - Elvish, - Fish, - Nushell, - PowerShell, - Zsh, -} - -impl Generator for GenShell { - fn file_name(&self, name: &str) -> String { - match self { - // clap_complete - Self::Bash => Shell::Bash.file_name(name), - Self::Elvish => Shell::Elvish.file_name(name), - Self::Fish => Shell::Fish.file_name(name), - Self::PowerShell => Shell::PowerShell.file_name(name), - Self::Zsh => Shell::Zsh.file_name(name), - - // clap_complete_nushell - Self::Nushell => Nushell.file_name(name), - } - } - - fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::prelude::Write) { - match self { - // clap_complete - Self::Bash => Shell::Bash.generate(cmd, buf), - Self::Elvish => Shell::Elvish.generate(cmd, buf), - Self::Fish => Shell::Fish.generate(cmd, buf), - Self::PowerShell => Shell::PowerShell.generate(cmd, buf), - Self::Zsh => Shell::Zsh.generate(cmd, buf), - - // clap_complete_nushell - Self::Nushell => Nushell.generate(cmd, buf), - } - } + #[command(name = "shell-completion", about = tr("cli-shell-completion-desc"))] + ShellCompletion(ShellCompletionArgs), } pub fn get_cli_result() -> Result<(), String> { @@ -90,9 +43,6 @@ pub fn get_cli_result() -> Result<(), String> { Commands::Sort(args) => sort_main(&args), Commands::Extract(args) => extract_main(&args), Commands::Merge(args) => merge_main(&args), - Commands::ShellCompletion { shell, help: _ } => { - shell.generate(&mut Cli::command(), &mut std::io::stdout()); - Ok(()) - } + Commands::ShellCompletion(args) => shell_completion_main(&args), } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 11c85dd..1852dde 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ pub mod extract; pub mod merge; +pub mod shell_completion; pub mod sort; diff --git a/src/commands/shell_completion.rs b/src/commands/shell_completion.rs new file mode 100644 index 0000000..25322a3 --- /dev/null +++ b/src/commands/shell_completion.rs @@ -0,0 +1,63 @@ +use clap::{ArgAction, Args, CommandFactory, ValueEnum}; +use clap_complete::{Generator, Shell}; +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)] + shell: clap_complete_command::Shell, + #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] + help: bool, +} + +#[derive(Debug, Clone, ValueEnum)] +#[value(rename_all = "lower")] +pub enum GenShell { + Bash, + Elvish, + Fish, + Nushell, + PowerShell, + Zsh, +} + +impl Generator for GenShell { + fn file_name(&self, name: &str) -> String { + match self { + // clap_complete + Self::Bash => Shell::Bash.file_name(name), + Self::Elvish => Shell::Elvish.file_name(name), + Self::Fish => Shell::Fish.file_name(name), + Self::PowerShell => Shell::PowerShell.file_name(name), + Self::Zsh => Shell::Zsh.file_name(name), + + // clap_complete_nushell + Self::Nushell => Nushell.file_name(name), + } + } + + fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::prelude::Write) { + match self { + // clap_complete + Self::Bash => Shell::Bash.generate(cmd, buf), + Self::Elvish => Shell::Elvish.generate(cmd, buf), + Self::Fish => Shell::Fish.generate(cmd, buf), + Self::PowerShell => Shell::PowerShell.generate(cmd, buf), + Self::Zsh => Shell::Zsh.generate(cmd, buf), + + // clap_complete_nushell + Self::Nushell => Nushell.generate(cmd, buf), + } + } +} + +pub fn shell_completion_main(args: &ShellCompletionArgs) -> Result<(), String> { + args.shell + .generate(&mut Cli::command(), &mut std::io::stdout()); + + Ok(()) +} From 80a305e4dadad6ba868e32c1e55f72eea11bc014 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Mon, 10 Jun 2024 22:28:03 +0900 Subject: [PATCH 08/15] fixes #82: Make `help` and `version` parameter optional. --- src/cli.rs | 4 ++-- src/commands/extract.rs | 2 +- src/commands/merge.rs | 2 +- src/commands/shell_completion.rs | 2 +- src/commands/sort.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index da34bff..3de06f7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,9 +17,9 @@ pub struct Cli { #[command(subcommand)] command: Commands, #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] - pub help: bool, + pub help: Option, #[arg(short, long, short_alias = 'v', action = ArgAction::Version, help = tr("cli-version"))] - version: bool, + version: Option, } #[derive(Subcommand)] diff --git a/src/commands/extract.rs b/src/commands/extract.rs index 846e8a9..c715b02 100644 --- a/src/commands/extract.rs +++ b/src/commands/extract.rs @@ -19,7 +19,7 @@ pub struct ExtractArgs { #[arg(short, long, help = tr("cli-extract-output"), help_heading = tr("cli-headers-options"))] pub output_path: Option, #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] - pub help: bool, + pub help: Option, } #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] diff --git a/src/commands/merge.rs b/src/commands/merge.rs index cfd3056..41d9eb7 100644 --- a/src/commands/merge.rs +++ b/src/commands/merge.rs @@ -22,7 +22,7 @@ pub struct MergeArgs { #[arg(short, long, help = tr("cli-merge-output"), help_heading = tr("cli-headers-options"))] pub output_path: Option, #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] - pub help: bool, + pub help: Option, } /// MessageNode that can be `eq(...)`. diff --git a/src/commands/shell_completion.rs b/src/commands/shell_completion.rs index 25322a3..ef96d1c 100644 --- a/src/commands/shell_completion.rs +++ b/src/commands/shell_completion.rs @@ -11,7 +11,7 @@ pub struct ShellCompletionArgs { #[arg(value_enum)] shell: clap_complete_command::Shell, #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] - help: bool, + help: Option, } #[derive(Debug, Clone, ValueEnum)] diff --git a/src/commands/sort.rs b/src/commands/sort.rs index 292f4b9..4cd3cca 100644 --- a/src/commands/sort.rs +++ b/src/commands/sort.rs @@ -14,7 +14,7 @@ pub struct SortArgs { #[arg(short, long, help = tr("cli-sort-output"), help_heading = tr("cli-headers-options"))] pub output_path: Option, #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] - pub help: bool, + pub help: Option, } /// Sorts an input TS file by context, then by messages. From 751b1420d6f2434dd1f0cb614eba8d205753cb86 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Tue, 11 Jun 2024 00:01:16 +0900 Subject: [PATCH 09/15] fixes #57: Add support for output-path for shell-completions. --- CHANGELOG.md | 4 +++ resources/locales/en/core.ftl | 6 ++++ resources/locales/fr/core.ftl | 6 ++++ src/commands/shell_completion.rs | 54 +++++++++++++++++++++++++++++--- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0856d..922e82c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - The tool now support logging for debugging and observability purpose [#18](https://github.com/mrtryhard/qt-ts-tools/issues/18) +- Added `output-path` to `shell-completion` command [#57](https://github.com/mrtryhard/qt-ts-tools/issues/57) ### Changed - Improved code documentation for reusable parsing TS structures [#41](https://github.com/mrtryhard/qt-ts-tools/issues/41) +- Extracted cli commands in their own subdirectory for code clarity [#57](https://github.com/mrtryhard/qt-ts-tools/issues/57) ### Fixed +- Fixed an issue where the `help` parameter was erroring every command [#82](https://github.com/mrtryhard/qt-ts-tools/issues/82) + ## [0.4.0] - 2024-05-24 [Fifth milestone](https://github.com/mrtryhard/qt-ts-tools/milestone/4). This release introduces some user experience improvement efforts such as localization (adopting current system language) and auto-completion support. diff --git a/resources/locales/en/core.ftl b/resources/locales/en/core.ftl index a3d7f1d..7f0e6ef 100644 --- a/resources/locales/en/core.ftl +++ b/resources/locales/en/core.ftl @@ -13,6 +13,12 @@ cli-merge-input-left = File to receive the merge. cli-merge-input-right = File to include changes from. cli-merge-output = If specified, will produce output in a file at designated location instead of stdout. cli-shell-completion-desc = Print a shell completion for supported shells. +cli-shell-completion-install = When set, tries to install to default path or provided path (may require elevation). +cli-shell-completion-shell = Shell to generate the auto-completion script for. +cli-shell-completion-error-get-shell = Could not obtain the completions for shell "{ $shell }". +cli-shell-completion-error-open = Could not open file for writing. System error: "{ $error }". +cli-shell-completion-error-write-privilege = Could not write to destination path. Are privileges required ? System error: "{ $error }". +cli-shell-completion-error-write-to-file = Could not write the completion completely to "{ $file }". Please validate output file. cli-sort-desc = Sorts the input translation file by context, then by messages. cli-sort-input = File path to sort translations from. cli-sort-output = If specified, will produce output in a file at designated location instead of stdout. diff --git a/resources/locales/fr/core.ftl b/resources/locales/fr/core.ftl index 719adc0..3e18acf 100644 --- a/resources/locales/fr/core.ftl +++ b/resources/locales/fr/core.ftl @@ -13,6 +13,12 @@ cli-merge-input-left = Fichier qui reçoit les changements. cli-merge-input-right = Fichier qui possède les changements. cli-merge-output = Si spécificé, chemin d'accès du fichier de sortie. cli-shell-completion-desc = Affiche le script d'auto-complétion pour un shell choisi. +cli-shell-completion-install = Lorsque spécifié, l'outil tentera d'install le script d'auto-completion dans le chemin par défaut pour le shell, ou dans le path spécifié s'il y a lieu. Peut nécessiter une élévation de privilège. +cli-shell-completion-shell = Shell pour lequel générer le script d'auto-complétion. +cli-shell-completion-error-get-shell = Impossible d'obtenir les auto-complétions pour le shell "{ $shell }". +cli-shell-completion-error-open = Impossible d'ouvrir le fichier en écriture. Erreur système: "{ $error }". +cli-shell-completion-error-write-privilege = Impossible d'écrire la destination. Des privilèges sont-ils requis ? Erreur système: "{ $error }". +cli-shell-completion-error-write-to-file = Ne peux écrire complètement le fichier "{ $file }". Veuillez valider le fichier de sortie. cli-sort-desc = Trie le fichier de traduction par contextes, puis messages. cli-sort-input = Chemin d'accès du fichier de traduction à trier. cli-sort-output = Si spécifié, chemin d'accès du fichier de sortie. diff --git a/src/commands/shell_completion.rs b/src/commands/shell_completion.rs index ef96d1c..1d89c8f 100644 --- a/src/commands/shell_completion.rs +++ b/src/commands/shell_completion.rs @@ -1,15 +1,19 @@ +use std::io::Write; + use clap::{ArgAction, Args, CommandFactory, ValueEnum}; use clap_complete::{Generator, Shell}; use clap_complete_nushell::Nushell; use crate::cli::Cli; -use crate::locale::tr; +use crate::locale::{tr, tr_args}; #[derive(Args)] #[command(disable_help_flag = true)] pub struct ShellCompletionArgs { - #[arg(value_enum)] + #[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, } @@ -56,8 +60,48 @@ impl Generator for GenShell { } pub fn shell_completion_main(args: &ShellCompletionArgs) -> Result<(), String> { - args.shell - .generate(&mut Cli::command(), &mut std::io::stdout()); + let mut buf = Vec::::new(); + + { + let mut writer = std::io::BufWriter::new(&mut buf); + args.shell.generate(&mut Cli::command(), &mut writer); + } + + match &args.output_path { + None => match &mut buf.is_empty() { + true => Err(tr_args( + "cli-shell-completion-error-get-shell", + [("shell", format!("{:?}", args.shell).into())].into(), + )), + false => Ok(()), + }, + Some(output_path) => write_to_file(&mut buf, output_path), + } +} - Ok(()) +fn write_to_file(buf: &mut Vec, output_path: &String) -> Result<(), String> { + let file = std::fs::File::create(output_path); + + match file { + Ok(mut file) => match file.write(&buf) { + Ok(sz) => { + if sz == buf.len() { + Ok(()) + } else { + Err(tr_args( + "cli-shell-completion-error-write-to-file", + [("file", output_path.into())].into(), + )) + } + } + Err(err) => Err(tr_args( + "cli-shell-completion-error-write-privilege", + [("error", err.to_string().into())].into(), + )), + }, + Err(err) => Err(tr_args( + "cli-shell-completion-error-open", + [("error", err.to_string().into())].into(), + )), + } } From 3cedc6ce2d04b3afc18777fcc6ef9753698edf32 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Tue, 11 Jun 2024 00:06:02 +0900 Subject: [PATCH 10/15] fixes #57: Fixes clippy errors. --- src/commands/shell_completion.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/shell_completion.rs b/src/commands/shell_completion.rs index 1d89c8f..3567401 100644 --- a/src/commands/shell_completion.rs +++ b/src/commands/shell_completion.rs @@ -79,11 +79,11 @@ pub fn shell_completion_main(args: &ShellCompletionArgs) -> Result<(), String> { } } -fn write_to_file(buf: &mut Vec, output_path: &String) -> Result<(), String> { +fn write_to_file(buf: &mut [u8], output_path: &String) -> Result<(), String> { let file = std::fs::File::create(output_path); match file { - Ok(mut file) => match file.write(&buf) { + Ok(mut file) => match file.write(buf) { Ok(sz) => { if sz == buf.len() { Ok(()) From a9cc229e9087cf3f6b8fe43004450393ecbd463f Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Tue, 11 Jun 2024 00:25:36 +0900 Subject: [PATCH 11/15] fixes #83: Update clap-rs third parties. --- CHANGELOG.md | 1 + Cargo.lock | 22 +++++++++++----------- Cargo.toml | 6 +++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 922e82c..6cd20bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Improved code documentation for reusable parsing TS structures [#41](https://github.com/mrtryhard/qt-ts-tools/issues/41) - Extracted cli commands in their own subdirectory for code clarity [#57](https://github.com/mrtryhard/qt-ts-tools/issues/57) +- Updated clap-rs third parties [#83](https://github.com/mrtryhard/qt-ts-tools/issues/83) ### Fixed diff --git a/Cargo.lock b/Cargo.lock index d2e89b8..c11f6ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.2" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" +checksum = "d2020fa13af48afc65a9a87335bda648309ab3d154cd03c7ff95b378c7ed39c4" dependencies = [ "clap", ] @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "clap_complete_nushell" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0e48e026ce7df2040239117d25e4e79714907420c70294a5ce4b6bbe6a7b6" +checksum = "1accf1b463dee0d3ab2be72591dccdab8bef314958340447c882c4c72acfe2a3" dependencies = [ "clap", "clap_complete", @@ -163,9 +163,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck", "proc-macro2", @@ -516,7 +516,7 @@ dependencies = [ "clap", "clap_complete", "clap_complete_command", - "clap_complete_nushell 4.5.1", + "clap_complete_nushell 4.5.2", "fluent", "fluent-templates", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 83586cb..19918ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,9 @@ description = "Small command line utility to manipulate Qt's translation files w # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "4.5.4", features = ["derive", "cargo", "string"] } -clap_complete = "4.5.2" -clap_complete_nushell = "4.5.1" +clap = { version = "4.5.7", features = ["derive", "cargo", "string"] } +clap_complete = "4.5.5" +clap_complete_nushell = "4.5.2" clap_complete_command = "0.5.1" fluent = "0.16.1" fluent-templates = "0.9.4" From 84e4a0939cb344129eac74ac52b9e7d9ec36adba Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Tue, 18 Jun 2024 11:36:52 +0900 Subject: [PATCH 12/15] fixes #85: Update quick-xml to 0.32.0 --- CHANGELOG.md | 3 ++- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd20bf..7040528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Improved code documentation for reusable parsing TS structures [#41](https://github.com/mrtryhard/qt-ts-tools/issues/41) - Extracted cli commands in their own subdirectory for code clarity [#57](https://github.com/mrtryhard/qt-ts-tools/issues/57) -- Updated clap-rs third parties [#83](https://github.com/mrtryhard/qt-ts-tools/issues/83) +- Updated clap-rs third party to 4.5.7 [#83](https://github.com/mrtryhard/qt-ts-tools/issues/83) +- Updated quick-xml third party to 0.32.0 [#85](https://github.com/mrtryhard/qt-ts-tools/issues/85) ### Fixed diff --git a/Cargo.lock b/Cargo.lock index c11f6ba..59814d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,9 +531,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" dependencies = [ "memchr", "serde", diff --git a/Cargo.toml b/Cargo.toml index 19918ed..2d73bac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ fluent = "0.16.1" fluent-templates = "0.9.4" itertools = "0.13.0" lazy_static = "1.4.0" -quick-xml = { version = "0.31.0", features = ["serialize"] } +quick-xml = { version = "0.32.0", features = ["serialize"] } serde = { version = "1.0.203", features = ["derive"] } sys-locale = "0.3.1" tracing = "0.1.40" From 836c056a446960371ccfbebd8513baf5ee6303f8 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Fri, 21 Jun 2024 18:27:00 +0900 Subject: [PATCH 13/15] wip #56: Add strip CLI * Also uniformize a little bit the translation files. More work to be done. --- resources/locales/en/core.ftl | 14 +++++++++----- resources/locales/fr/core.ftl | 12 ++++++++---- src/cli.rs | 11 ++++++++--- src/commands/extract.rs | 4 ++-- src/commands/merge.rs | 4 ++-- src/commands/mod.rs | 1 + src/commands/sort.rs | 4 ++-- src/ts.rs | 6 +++--- 8 files changed, 35 insertions(+), 21 deletions(-) diff --git a/resources/locales/en/core.ftl b/resources/locales/en/core.ftl index 7f0e6ef..8ea71ae 100644 --- a/resources/locales/en/core.ftl +++ b/resources/locales/en/core.ftl @@ -12,18 +12,22 @@ cli-merge-desc = Merges two translation file contexts and messages into a single cli-merge-input-left = File to receive the merge. cli-merge-input-right = File to include changes from. cli-merge-output = If specified, will produce output in a file at designated location instead of stdout. -cli-shell-completion-desc = Print a shell completion for supported shells. +cli-shell-completion-desc = Prints a shell completion for supported shells. cli-shell-completion-install = When set, tries to install to default path or provided path (may require elevation). cli-shell-completion-shell = Shell to generate the auto-completion script for. cli-shell-completion-error-get-shell = Could not obtain the completions for shell "{ $shell }". cli-shell-completion-error-open = Could not open file for writing. System error: "{ $error }". cli-shell-completion-error-write-privilege = Could not write to destination path. Are privileges required ? System error: "{ $error }". cli-shell-completion-error-write-to-file = Could not write the completion completely to "{ $file }". Please validate output file. +cli-strip-desc = Strips the input translation file from translation determined by filter. +cli-strip-input = File to strip translations from. +cli-strip-output = If specified, output file path. +cli-strip-translation-type = Translation types to strip from the file cli-sort-desc = Sorts the input translation file by context, then by messages. cli-sort-input = File path to sort translations from. cli-sort-output = If specified, will produce output in a file at designated location instead of stdout. cli-version = Prints the version of this tool. -open-or-parse-error = Could not open or parse input file "{ $file }". Reason: { $error }. -sort-parse-error = Could not parse input file "{ $input_path }". Reason: { $error }. -ts-error-write-output-open = Error occured while opening output file { $output_path }. Reason: { $error }. -ts-error-write-serialize = Problem occured while serializing output translation file. Reason: { $error }. +error-open-or-parse = Could not open or parse input file "{ $file }". Reason: { $error }. +error-ts-file-parse = Could not parse input file "{ $input_path }". Reason: { $error }. +error-ts-write-output-open = Error occured while opening output file { $output_path }. Reason: { $error }. +error-ts-write-serialize = Problem occured while serializing output translation file. Reason: { $error }. diff --git a/resources/locales/fr/core.ftl b/resources/locales/fr/core.ftl index 3e18acf..f7311e7 100644 --- a/resources/locales/fr/core.ftl +++ b/resources/locales/fr/core.ftl @@ -22,8 +22,12 @@ cli-shell-completion-error-write-to-file = Ne peux écrire complètement le fich cli-sort-desc = Trie le fichier de traduction par contextes, puis messages. cli-sort-input = Chemin d'accès du fichier de traduction à trier. cli-sort-output = Si spécifié, chemin d'accès du fichier de sortie. +cli-strip-desc = Retire les traductions du fichier de traduction spécifié selon le filtre donné. +cli-strip-input = Fichier à enlever les traductions +cli-strip-output = Si spécifié, chemin d'accès du fichier de sortie. +cli-strip-translation-type = Types de traductions à retirer du fichier d'entrée cli-version = Affiche la version de l'utilitaire. -open-or-parse-error = Échec de lecture ou décodage du fichier "{ $file }". Raison: { $error },. -sort-parse-error = Échec de lecture du fichier "{ $file }". Raison: { $error }. -ts-error-write-output-open = Erreur lors de l'ouverture en écriture du fichier "{ $file }". Raison: { $error }. -ts-error-write-serialize = Un problème est survenue lors de la sérialization du fichier. Raison: { $error }. +error-open-or-parse = Échec de lecture ou décodage du fichier "{ $file }". Raison: { $error },. +error-ts-file-parse = Échec de lecture du fichier "{ $file }". Raison: { $error }. +error-ts-write-output-open = Erreur lors de l'ouverture en écriture du fichier "{ $file }". Raison: { $error }. +error-ts-write-serialize = Un problème est survenue lors de la sérialization du fichier. Raison: { $error }. diff --git a/src/cli.rs b/src/cli.rs index 3de06f7..9c9c162 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,6 +4,7 @@ use crate::commands::extract::{extract_main, ExtractArgs}; use crate::commands::merge::{merge_main, MergeArgs}; use crate::commands::shell_completion::{shell_completion_main, ShellCompletionArgs}; use crate::commands::sort::{sort_main, SortArgs}; +use crate::commands::strip::{strip_main, StripArgs}; use crate::locale::tr; #[derive(Parser)] @@ -26,12 +27,15 @@ pub struct Cli { #[command(subcommand_help_heading = tr("cli-headers-commands"), next_help_heading = tr("cli-headers-options"))] enum Commands { - #[command(about = tr("cli-sort-desc"))] - Sort(SortArgs), #[command(about = tr("cli-extract-desc"))] Extract(ExtractArgs), #[command(about = tr("cli-merge-desc"))] Merge(MergeArgs), + #[command(about = tr("cli-sort-desc"))] + Sort(SortArgs), + #[command(about = tr("cli-strip-desc"))] + Strip(StripArgs), + // Want to have shell-completion as the very last option displayed #[command(name = "shell-completion", about = tr("cli-shell-completion-desc"))] ShellCompletion(ShellCompletionArgs), } @@ -40,9 +44,10 @@ pub fn get_cli_result() -> Result<(), String> { let cli = Cli::parse(); match cli.command { - Commands::Sort(args) => sort_main(&args), Commands::Extract(args) => extract_main(&args), Commands::Merge(args) => merge_main(&args), + Commands::Sort(args) => sort_main(&args), + Commands::Strip(args) => strip_main(&args), Commands::ShellCompletion(args) => shell_completion_main(&args), } } diff --git a/src/commands/extract.rs b/src/commands/extract.rs index c715b02..cd30613 100644 --- a/src/commands/extract.rs +++ b/src/commands/extract.rs @@ -45,7 +45,7 @@ pub fn extract_main(args: &ExtractArgs) -> Result<(), String> { ts::write_to_output(&args.output_path, &ts_node) } Err(e) => Err(tr_args( - "open-or-parse-error", + "error-open-or-parse", [ ("file", args.input_path.as_str().into()), ("error", e.to_string().into()), @@ -55,7 +55,7 @@ pub fn extract_main(args: &ExtractArgs) -> Result<(), String> { } } Err(e) => Err(tr_args( - "open-or-parse-error", + "error-open-or-parse", [ ("file", args.input_path.as_str().into()), ("error", e.to_string().into()), diff --git a/src/commands/merge.rs b/src/commands/merge.rs index 41d9eb7..3dfd746 100644 --- a/src/commands/merge.rs +++ b/src/commands/merge.rs @@ -62,7 +62,7 @@ pub fn merge_main(args: &MergeArgs) -> Result<(), String> { if let Err(e) = left { return Err(tr_args( - "open-or-parse-error", + "error-open-or-parse", [ ("file", args.input_left.as_str().into()), ("error", e.to_string().as_str().into()), @@ -73,7 +73,7 @@ pub fn merge_main(args: &MergeArgs) -> Result<(), String> { if let Err(e) = right { return Err(tr_args( - "open-or-parse-error", + "error-open-or-parse", [ ("file", args.input_right.as_str().into()), ("error", e.to_string().as_str().into()), diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 1852dde..e82df5b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,3 +2,4 @@ pub mod extract; pub mod merge; pub mod shell_completion; pub mod sort; +pub mod strip; diff --git a/src/commands/sort.rs b/src/commands/sort.rs index 4cd3cca..6fc2fc9 100644 --- a/src/commands/sort.rs +++ b/src/commands/sort.rs @@ -33,7 +33,7 @@ pub fn sort_main(args: &SortArgs) -> Result<(), String> { ts::write_to_output(&args.output_path, &ts_node) } Err(e) => Err(tr_args( - "sort-parse-error", + "error-ts-file-parse", [ ("file", args.input_path.as_str().into()), ("error", e.to_string().into()), @@ -43,7 +43,7 @@ pub fn sort_main(args: &SortArgs) -> Result<(), String> { } } Err(e) => Err(tr_args( - "open-or-parse-error", + "error-open-or-parse", [ ("file", args.input_path.as_str().into()), ("error", e.to_string().into()), diff --git a/src/ts.rs b/src/ts.rs index 860359f..7137940 100644 --- a/src/ts.rs +++ b/src/ts.rs @@ -329,7 +329,7 @@ pub fn write_to_output(output_path: &Option, node: &TSNode) -> Result<() Ok(file) => BufWriter::new(Box::new(file)), Err(e) => { return Err(tr_args( - "ts-error-write-output-open", + "error-ts-write-output-open", [ ("output_path", output_path.into()), ("error", e.to_string().into()), @@ -353,13 +353,13 @@ pub fn write_to_output(output_path: &Option, node: &TSNode) -> Result<() match res { Ok(_) => Ok(()), Err(e) => Err(tr_args( - "ts-error-write-serialize", + "error-ts-write-serialize", [("error", e.to_string().into())].into(), )), } } Err(e) => Err(tr_args( - "ts-error-write-serialize", + "error-ts-write-serialize", [("error", e.to_string().into())].into(), )), } From 46a3437e1c62dbd25832ef89e00fc63334253ee7 Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Sat, 22 Jun 2024 12:07:01 +0900 Subject: [PATCH 14/15] fixes #56: Finalize strip feature --- src/commands/strip.rs | 142 +++++++++++++++++++++++++++ test_data/example_strip.xml | 77 +++++++++++++++ test_data/example_strip_stripped.xml | 73 ++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 src/commands/strip.rs create mode 100644 test_data/example_strip.xml create mode 100644 test_data/example_strip_stripped.xml diff --git a/src/commands/strip.rs b/src/commands/strip.rs new file mode 100644 index 0000000..9641787 --- /dev/null +++ b/src/commands/strip.rs @@ -0,0 +1,142 @@ +use crate::locale::{tr, tr_args}; +use crate::ts; +use crate::ts::{TSNode, TranslationType}; +use clap::{ArgAction, Args}; +use tracing::debug; + +#[derive(clap::ValueEnum, PartialEq, Debug, Clone)] +pub enum TranslationTypeArg { + Obsolete, + Unfinished, + Vanished, +} + +impl From for TranslationType { + fn from(value: TranslationTypeArg) -> Self { + match value { + TranslationTypeArg::Obsolete => TranslationType::Obsolete, + TranslationTypeArg::Unfinished => TranslationType::Unfinished, + TranslationTypeArg::Vanished => TranslationType::Vanished, + } + } +} + +#[derive(Args)] +#[command(disable_help_flag = true)] +pub struct StripArgs { + /// File path to sort translations from. + #[arg(help = tr("cli-strip-input"), help_heading = tr("cli-headers-arguments"))] + pub input_path: String, + /// Translation type list to strip from input. + #[arg(short('t'), long, value_enum, num_args = 1.., help = tr("cli-strip-translation-type"), help_heading = tr("cli-headers-arguments"))] + pub translation_type: Vec, + /// If specified, will produce output in a file at designated location instead of stdout. + #[arg(short, long, help = tr("cli-strip-output"), help_heading = tr("cli-headers-options"))] + pub output_path: Option, + #[arg(short, long, action = ArgAction::Help, help = tr("cli-help"), help_heading = tr("cli-headers-options"))] + pub help: Option, +} + +pub fn strip_main(args: &StripArgs) -> Result<(), String> { + match quick_xml::Reader::from_file(&args.input_path) { + Ok(file) => { + let nodes: Result = quick_xml::de::from_reader(file.into_inner()); + match nodes { + Ok(mut ts_node) => { + let s: Vec = args + .translation_type + .iter() + .map(|arg| arg.clone().into()) + .collect(); + + strip_nodes(&mut ts_node, &s); + ts::write_to_output(&args.output_path, &ts_node) + } + Err(e) => Err(tr_args( + "strip-parse-error", + [ + ("file", args.input_path.as_str().into()), + ("error", e.to_string().into()), + ] + .into(), + )), + } + } + Err(e) => Err(tr_args( + "error-open-or-parse", + [ + ("file", args.input_path.as_str().into()), + ("error", e.to_string().into()), + ] + .into(), + )), + } +} + +fn strip_nodes(nodes: &mut TSNode, translation_type_filter: &[TranslationType]) { + let mut count = 0; + nodes.contexts.iter_mut().for_each(|context| { + context.messages.iter_mut().for_each(|message| { + if let Some(translation) = &mut message.translation.as_ref() { + if let Some(translation_type) = translation.translation_type.clone() { + if translation_type_filter.contains(&translation_type) { + debug!( + "Stripping translation {:?} from message `{}`", + &translation.translation_simple, + &message + .source + .as_ref() + .unwrap_or(&"Unknown source text".to_owned()) + ); + message.translation = None; + count += 1; + } + } + } + }); + }); + + nodes.messages.iter_mut().for_each(|message| { + if let Some(translation) = &mut message.translation.as_ref() { + if let Some(translation_type) = translation.translation_type.clone() { + if translation_type_filter.contains(&translation_type) { + debug!( + "Stripping translation {:?} from message `{}`", + &translation.translation_simple, + &message + .source + .as_ref() + .unwrap_or(&"Unknown source text".to_owned()) + ); + message.translation = None; + count += 1; + } + } + } + }); + + debug!("Stripped {count} translation tags"); +} + +#[cfg(test)] +mod strip_test { + use super::*; + + #[test] + fn test_strip() { + let reader_unstripped = quick_xml::Reader::from_file("./test_data/example_strip.xml") + .expect("Couldn't open example_strip test file"); + let reader_stripped = + quick_xml::Reader::from_file("./test_data/example_strip_stripped.xml") + .expect("Couldn't open example_strip_stripped test file"); + let mut data: TSNode = + quick_xml::de::from_reader(reader_unstripped.into_inner()).expect("Parsable"); + let data_stripped: TSNode = + quick_xml::de::from_reader(reader_stripped.into_inner()).expect("Parsable"); + + let types = vec![TranslationType::Obsolete]; + strip_nodes(&mut data, &types); + + assert_eq!(data, data_stripped); + } +} diff --git a/test_data/example_strip.xml b/test_data/example_strip.xml new file mode 100644 index 0000000..a257227 --- /dev/null +++ b/test_data/example_strip.xml @@ -0,0 +1,77 @@ + + + + tst_QKeySequence + + + Shift+K + Umschalt+K + + + + Ctrl+K + Strg+K + + + + + Alt+K + Alt+K + + + + Shift++ + Umschalt++ + + + + Ctrl++ + Strg++ + + + + Alt++ + Alt++ + + + + Meta++ + Meta++ + + + + Shift+,, Shift++ + Umschalt+,, Umschalt++ + + + + Shift+,, Ctrl++ + Umschalt+,, Strg++ + + + + Shift+,, Alt++ + Umschalt+,, Alt++ + + + + Shift+,, Meta++ + Umschalt+,, Meta++ + + + + + Shift+,, Meta++ + Umschalt+,, Meta++ + + + + Shift+,, Meta++ + Umschalt+,, Meta++ + + + + Shift+,, Meta++ + Umschalt+,, Meta++ + + \ No newline at end of file diff --git a/test_data/example_strip_stripped.xml b/test_data/example_strip_stripped.xml new file mode 100644 index 0000000..66821cf --- /dev/null +++ b/test_data/example_strip_stripped.xml @@ -0,0 +1,73 @@ + + + + tst_QKeySequence + + + Shift+K + + + + Ctrl+K + + + + + Alt+K + + + + Shift++ + Umschalt++ + + + + Ctrl++ + Strg++ + + + + Alt++ + Alt++ + + + + Meta++ + Meta++ + + + + Shift+,, Shift++ + Umschalt+,, Umschalt++ + + + + Shift+,, Ctrl++ + Umschalt+,, Strg++ + + + + Shift+,, Alt++ + Umschalt+,, Alt++ + + + + Shift+,, Meta++ + Umschalt+,, Meta++ + + + + + Shift+,, Meta++ + Umschalt+,, Meta++ + + + + Shift+,, Meta++ + + + + Shift+,, Meta++ + Umschalt+,, Meta++ + + \ No newline at end of file From 76d9aec68d905a4063a4f24ebb96a21eeec04f2d Mon Sep 17 00:00:00 2001 From: Alexandre Leblanc Date: Sat, 22 Jun 2024 12:29:05 +0900 Subject: [PATCH 15/15] update #56: Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7040528..f2091ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - The tool now support logging for debugging and observability purpose [#18](https://github.com/mrtryhard/qt-ts-tools/issues/18) - Added `output-path` to `shell-completion` command [#57](https://github.com/mrtryhard/qt-ts-tools/issues/57) +- Added `strip` command to strip specific types of translation out of a file [#56](https://github.com/mrtryhard/qt-ts-tools/issues/56) ### Changed