From 6003851279c46f6a9a821be3e18c2741befe4b98 Mon Sep 17 00:00:00 2001 From: BrettMayson Date: Fri, 25 Oct 2024 06:51:51 -0600 Subject: [PATCH] localization: only-lang flag (#815) * localization: only-lang flag * help shows the correct command --- Cargo.lock | 1 + bin/src/commands/localization/mod.rs | 13 ++- bin/src/commands/localization/sort.rs | 9 +- bin/src/modules/stringtables/mod.rs | 91 ++++++++++++++++-- libs/stringtable/Cargo.toml | 1 + .../src/analyze/lints/01_sorted.rs | 93 ++++++------------- libs/stringtable/src/analyze/mod.rs | 32 ++----- libs/stringtable/src/key.rs | 2 +- libs/stringtable/src/lib.rs | 2 +- libs/stringtable/src/package.rs | 2 +- 10 files changed, 142 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cd2bb13..3564ad16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1619,6 +1619,7 @@ dependencies = [ "paste", "quick-xml", "serde", + "toml 0.8.19", "tracing", ] diff --git a/bin/src/commands/localization/mod.rs b/bin/src/commands/localization/mod.rs index d2c70628..09280cb3 100644 --- a/bin/src/commands/localization/mod.rs +++ b/bin/src/commands/localization/mod.rs @@ -1,4 +1,4 @@ -use clap::{ArgMatches, Command}; +use clap::{ArgAction, ArgMatches, Command}; use crate::{report::Report, Error}; @@ -11,7 +11,14 @@ pub fn cli() -> Command { .visible_alias("ln") .about("Manage localization stringtables") .subcommand(Command::new("coverage").about("Check the coverage of localization")) - .subcommand(Command::new("sort").about("Sort the stringtables")) + .subcommand( + Command::new("sort").about("Sort the stringtables").arg( + clap::Arg::new("only-lang") + .long("only-lang") + .help("Sort only the languages") + .action(ArgAction::SetTrue), + ), + ) } /// Execute the localization command @@ -24,7 +31,7 @@ pub fn cli() -> Command { pub fn execute(matches: &ArgMatches) -> Result { match matches.subcommand() { Some(("coverage", _)) => coverage::coverage(), - Some(("sort", _)) => sort::sort(), + Some(("sort", matches)) => sort::sort(matches), _ => { cli().print_help().expect("Failed to print help"); Ok(Report::new()) diff --git a/bin/src/commands/localization/sort.rs b/bin/src/commands/localization/sort.rs index ff312a26..627030b3 100644 --- a/bin/src/commands/localization/sort.rs +++ b/bin/src/commands/localization/sort.rs @@ -1,12 +1,15 @@ use std::io::BufReader; +use clap::ArgMatches; use hemtt_stringtable::Project; use crate::{context::Context, report::Report, Error}; -pub fn sort() -> Result { +pub fn sort(matches: &ArgMatches) -> Result { let ctx = Context::new(None, crate::context::PreservePrevious::Remove, true)?; + let only_lang = matches.get_flag("only-lang"); + for addon in ctx.addons() { let stringtable_path = ctx .workspace_path() @@ -15,7 +18,9 @@ pub fn sort() -> Result { if stringtable_path.exists()? { match Project::from_reader(BufReader::new(stringtable_path.open_file()?)) { Ok(mut project) => { - project.sort(); + if !only_lang { + project.sort(); + } let out_path = ctx .project_folder() .join(addon.folder_pathbuf()) diff --git a/bin/src/modules/stringtables/mod.rs b/bin/src/modules/stringtables/mod.rs index 0cd524aa..e2d1252c 100644 --- a/bin/src/modules/stringtables/mod.rs +++ b/bin/src/modules/stringtables/mod.rs @@ -1,4 +1,10 @@ -use hemtt_stringtable::analyze::{lint_addons, lint_check}; +use std::{io::BufReader, sync::Arc}; + +use hemtt_stringtable::{ + analyze::{lint_addon, lint_addons, lint_check}, + Project, +}; +use hemtt_workspace::reporting::{Code, Diagnostic}; use crate::{context::Context, report::Report, Error}; @@ -26,11 +32,84 @@ impl Module for Stringtables { fn pre_build(&self, ctx: &Context) -> Result { let mut report = Report::new(); - report.extend(lint_addons( - ctx.workspace_path().to_owned(), - &ctx.addons().to_vec(), - Some(ctx.config()), - )); + + let stringtables = ctx + .addons() + .iter() + .filter_map(|addon| { + let path = ctx + .workspace_path() + .join(addon.folder()) + .expect("vfs issue") + .join("stringtable.xml") + .expect("vfs issue"); + if path.exists().expect("vfs issue") { + let existing = path.read_to_string().expect("vfs issue"); + match Project::from_reader(BufReader::new(existing.as_bytes())) { + Ok(project) => Some((project, addon.clone(), existing)), + Err(e) => { + debug!("Failed to parse stringtable for {}: {}", addon.folder(), e); + report.push(Arc::new(CodeStringtableInvalid::new( + addon.folder(), + e.to_string(), + ))); + None + } + } + } else { + None + } + }) + .collect::>(); + + report.extend(lint_addons(&stringtables, Some(ctx.config()))); + + for stringtable in stringtables { + report.extend(lint_addon(&stringtable, Some(ctx.config()))); + } + Ok(report) } } + +#[allow(clippy::module_name_repetitions)] +pub struct CodeStringtableInvalid { + addon: String, + reason: String, + diagnostic: Option, +} + +impl Code for CodeStringtableInvalid { + fn ident(&self) -> &'static str { + "INVALID-STRINGTABLE" + } + + fn message(&self) -> String { + format!("Stringtable in `{}` is invalid", self.addon) + } + + fn note(&self) -> Option { + Some(self.reason.clone()) + } + + fn diagnostic(&self) -> Option { + self.diagnostic.clone() + } +} + +impl CodeStringtableInvalid { + #[must_use] + pub fn new(addon: String, reason: String) -> Self { + Self { + addon, + reason, + diagnostic: None, + } + .generate_processed() + } + + fn generate_processed(mut self) -> Self { + self.diagnostic = Some(Diagnostic::from_code(&self)); + self + } +} diff --git a/libs/stringtable/Cargo.toml b/libs/stringtable/Cargo.toml index 91f81295..eb8c6e11 100644 --- a/libs/stringtable/Cargo.toml +++ b/libs/stringtable/Cargo.toml @@ -17,6 +17,7 @@ linkme = { workspace = true } paste = { workspace = true } quick-xml = { version = "0.36.2", features = ["serialize"] } serde = { workspace = true, features = ["derive"] } +toml = { workspace = true } tracing = { workspace = true } [dev-dependencies] diff --git a/libs/stringtable/src/analyze/lints/01_sorted.rs b/libs/stringtable/src/analyze/lints/01_sorted.rs index 7b0fbf41..09815f99 100644 --- a/libs/stringtable/src/analyze/lints/01_sorted.rs +++ b/libs/stringtable/src/analyze/lints/01_sorted.rs @@ -1,8 +1,7 @@ -use std::{io::BufReader, sync::Arc}; +use std::sync::Arc; use hemtt_common::config::LintConfig; use hemtt_workspace::{addons::Addon, lint::{AnyLintRunner, Lint, LintRunner}, reporting::{Code, Codes, Diagnostic, Severity}}; -use tracing::debug; use crate::{analyze::SqfLintData, Project}; @@ -34,53 +33,47 @@ impl Lint for LintL01Sorted { } } +pub type StringtableData = (Project, Addon, String); + pub struct Runner; impl LintRunner for Runner { - type Target = Vec; + type Target = Vec; fn run( &self, _project: Option<&hemtt_common::config::ProjectConfig>, config: &hemtt_common::config::LintConfig, _processed: Option<&hemtt_workspace::reporting::Processed>, - target: &Vec, - data: &SqfLintData, + target: &Vec, + _data: &SqfLintData, ) -> Codes { let mut unsorted = Vec::new(); let mut codes: Codes = Vec::new(); - for addon in target { - let stringtable_path = data.workspace() - .join(addon.folder()).expect("vfs issue") - .join("stringtable.xml").expect("vfs issue"); - if stringtable_path.exists().expect("vfs issue") { - let existing = stringtable_path.read_to_string().expect("vfs issue"); - match Project::from_reader(BufReader::new(existing.as_bytes())) { - Ok(mut project) => { - project.sort(); - let mut writer = String::new(); - if let Err(e) = project.to_writer(&mut writer) { - panic!("Failed to write stringtable for {}: {}", addon.folder(), e); - } - if writer != existing { - unsorted.push(addon.folder().to_string()); - } - } - Err(e) => { - debug!("Failed to parse stringtable for {}: {}", addon.folder(), e); - codes.push(Arc::new(CodeStringtableInvalid::new(addon.folder()))); - } - } + let only_lang = matches!(config.option("only-lang"), Some(toml::Value::Boolean(true))); + for (project, addon, existing) in target { + let mut project = project.clone(); + if !only_lang { + project.sort(); + } + let mut writer = String::new(); + if let Err(e) = project.to_writer(&mut writer) { + panic!("Failed to write stringtable for {}: {}", addon.folder(), e); + } + if &writer != existing { + unsorted.push(addon.folder().to_string()); } } if unsorted.len() <= 3 { for addon in unsorted { codes.push(Arc::new(CodeStringtableNotSorted::new( Unsorted::Addon(addon), + only_lang, config.severity(), ))); } } else { codes.push(Arc::new(CodeStringtableNotSorted::new( Unsorted::Addons(unsorted), + only_lang, config.severity(), ))); } @@ -96,6 +89,7 @@ pub enum Unsorted { #[allow(clippy::module_name_repetitions)] pub struct CodeStringtableNotSorted { unsorted: Unsorted, + only_lang: bool, severity: Severity, diagnostic: Option, } @@ -119,7 +113,11 @@ impl Code for CodeStringtableNotSorted { } fn help(&self) -> Option { - Some("Run `hemtt ln sort` to sort the stringtable".to_string()) + if self.only_lang { + Some("Run `hemtt ln sort --only-lang` to sort the stringtable".to_string()) + } else { + Some("Run `hemtt ln sort` to sort the stringtable".to_string()) + } } fn diagnostic(&self) -> Option { @@ -129,9 +127,10 @@ impl Code for CodeStringtableNotSorted { impl CodeStringtableNotSorted { #[must_use] - pub fn new(unsorted: Unsorted, severity: Severity) -> Self { + pub fn new(unsorted: Unsorted, only_lang: bool, severity: Severity) -> Self { Self { unsorted, + only_lang, severity, diagnostic: None, } @@ -143,39 +142,3 @@ impl CodeStringtableNotSorted { self } } - -#[allow(clippy::module_name_repetitions)] -pub struct CodeStringtableInvalid { - addon: String, - diagnostic: Option, -} - -impl Code for CodeStringtableInvalid { - fn ident(&self) -> &'static str { - "L-L02" - } - - fn message(&self) -> String { - format!("Stringtable in `{}` is invalid", self.addon) - } - - fn diagnostic(&self) -> Option { - self.diagnostic.clone() - } -} - -impl CodeStringtableInvalid { - #[must_use] - pub fn new(addon: String) -> Self { - Self { - addon, - diagnostic: None, - } - .generate_processed() - } - - fn generate_processed(mut self) -> Self { - self.diagnostic = Some(Diagnostic::from_code(&self)); - self - } -} diff --git a/libs/stringtable/src/analyze/mod.rs b/libs/stringtable/src/analyze/mod.rs index e628df28..268b48ba 100644 --- a/libs/stringtable/src/analyze/mod.rs +++ b/libs/stringtable/src/analyze/mod.rs @@ -1,7 +1,6 @@ use hemtt_common::config::ProjectConfig; -use hemtt_workspace::{ - addons::Addon, lint::LintManager, lint_manager, reporting::Codes, WorkspacePath, -}; +use hemtt_workspace::{lint::LintManager, lint_manager, reporting::Codes}; +use lints::_01_sorted::StringtableData; pub mod lints { automod::dir!(pub "src/analyze/lints"); @@ -9,22 +8,9 @@ pub mod lints { lint_manager!(stringtable, vec![]); -pub struct SqfLintData { - workspace: WorkspacePath, -} - -impl SqfLintData { - #[must_use] - pub const fn workspace(&self) -> &WorkspacePath { - &self.workspace - } -} +pub struct SqfLintData {} -pub fn lint_addon( - workspace: WorkspacePath, - addon: &Addon, - project: Option<&ProjectConfig>, -) -> Codes { +pub fn lint_addon(addon: &StringtableData, project: Option<&ProjectConfig>) -> Codes { let mut manager = LintManager::new(project.map_or_else(Default::default, |project| { project.lints().stringtables().clone() })); @@ -36,15 +22,11 @@ pub fn lint_addon( ) { return e; } - manager.run(&SqfLintData { workspace }, project, None, addon) + manager.run(&SqfLintData {}, project, None, addon) } #[allow(clippy::ptr_arg)] // Needed for &Vec for &dyn Any -pub fn lint_addons( - workspace: WorkspacePath, - addons: &Vec, - project: Option<&ProjectConfig>, -) -> Codes { +pub fn lint_addons(addons: &Vec, project: Option<&ProjectConfig>) -> Codes { let mut manager = LintManager::new(project.map_or_else(Default::default, |project| { project.lints().stringtables().clone() })); @@ -56,5 +38,5 @@ pub fn lint_addons( ) { return e; } - manager.run(&SqfLintData { workspace }, project, None, addons) + manager.run(&SqfLintData {}, project, None, addons) } diff --git a/libs/stringtable/src/key.rs b/libs/stringtable/src/key.rs index 1e3e78db..e3a5805c 100644 --- a/libs/stringtable/src/key.rs +++ b/libs/stringtable/src/key.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct Key { #[serde(rename = "@ID")] diff --git a/libs/stringtable/src/lib.rs b/libs/stringtable/src/lib.rs index c404ea2a..e10f0d1a 100644 --- a/libs/stringtable/src/lib.rs +++ b/libs/stringtable/src/lib.rs @@ -10,7 +10,7 @@ pub use key::Key; pub use package::Package; pub use totals::Totals; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Project { #[serde(rename = "@name")] name: String, diff --git a/libs/stringtable/src/package.rs b/libs/stringtable/src/package.rs index 6c7880a9..735eafcd 100644 --- a/libs/stringtable/src/package.rs +++ b/libs/stringtable/src/package.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::{Key, Totals}; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Package { #[serde(rename = "@name")] name: String,