Skip to content

Commit

Permalink
Merge pull request #44 from mrtryhard/develop/v0.3.0
Browse files Browse the repository at this point in the history
Develop/v0.3.0
  • Loading branch information
mrtryhard authored Apr 18, 2024
2 parents c7b519c + 7e67a82 commit aae9383
Show file tree
Hide file tree
Showing 13 changed files with 538 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
title: 'Bug: '
labels: 'bug'
assignees: ''

Expand Down
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Feature request
about: Create a feature or enhancement request
title: 'Feature request: '
labels: 'enhancement'
assignees: ''

---

**Enhancement's description:**
_Please enter a short description of what is the enhancement (provide as much detail as can be necessary)._

**Expected behavior / output:**
_Please provide some examples of input and expected output. You may include faulty input as an example of error handling as well. ._

## Sample translation file
_Only include this section if you can provide a translation file if it is relevant to the feature request_

## Sample expected / correct output file
_If possible, include an expected output file in order to make it easier for contributors to match. It is not required._
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased
[Third milestone](https://github.com/mrtryhard/qt-ts-tools/milestone/3). This introduces the `merge` command and improved documentation.

## Added

- Merge mechanism to merge two translation files [#24](https://github.com/mrtryhard/qt-ts-tools/issues/24)
- `extra-*` fields support in `TS` and `message` nodes [#4](https://github.com/mrtryhard/qt-ts-tools/issues/4)

## Changed

- Improved command line documentation [#25](https://github.com/mrtryhard/qt-ts-tools/issues/25), [#27](https://github.com/mrtryhard/qt-ts-tools/issues/27)
- Updated Clap dependencies [#26](https://github.com/mrtryhard/qt-ts-tools/issues/26)

## Fixed

## [0.2.0] - 2024-01-01
Expand All @@ -18,7 +25,7 @@ Completion of the [second milestone](https://github.com/mrtryhard/qt-ts-tools/mi

### Added

- Extraction mechanism to extract only relevant translation types. [#16](https://github.com/mrtryhard/qt-ts-tools/issues/16)
- Extraction mechanism to extract only relevant translation types [#16](https://github.com/mrtryhard/qt-ts-tools/issues/16)

### Changed

Expand All @@ -33,4 +40,4 @@ Introduction of `qt-ts-tools`. This completes the first [milestone](https://gith

### Added

- Sort mechanism to sort translation files by location and contexts. [#3](https://github.com/mrtryhard/qt-ts-tools/issues/3)
- Sort mechanism to sort translation files by location and contexts [#3](https://github.com/mrtryhard/qt-ts-tools/issues/3)
38 changes: 27 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ 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.2.0"
version = "0.3.0"
edition = "2021"
description = "Small command line utility to manipulate Qt's translation files with diverse operations."

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.4.18", features = ["derive"] }
clap = { version = "4.5.0", features = ["derive"] }
quick-xml = { version = "0.31.0", features = ["serialize"] }
serde = { version = "1.0.196", features = ["derive"] }
itertools = "0.12.1"

[profile.release]
strip = true
Expand Down
4 changes: 4 additions & 0 deletions src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ use crate::ts;
use crate::ts::{TSNode, TranslationType};
use clap::Args;

/// Extracts a translation type messages and contexts from the input translation file.
#[derive(Args)]
pub struct ExtractArgs {
/// File path to extract translations from.
pub input_path: String,
/// Translation type list to extract into a single, valid translation output.
#[arg(short('t'), long, value_enum, num_args = 1..)]
pub translation_type: Vec<TranslationTypeArg>,
/// If specified, will produce output in a file at designated location instead of stdout.
#[arg(short, long)]
pub output_path: Option<String>,
}
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod extract;
mod merge;
mod sort;
mod ts;

use crate::extract::{extract_main, ExtractArgs};
use crate::merge::{merge_main, MergeArgs};
use crate::sort::{sort_main, SortArgs};
use clap::{Parser, Subcommand};

Expand All @@ -17,12 +19,14 @@ struct Cli {
enum Commands {
Sort(SortArgs),
Extract(ExtractArgs),
Merge(MergeArgs),
}

fn get_cli_result(cli: Cli) -> Result<(), String> {
match &cli.command {
Commands::Sort(args) => sort_main(&args),
Commands::Extract(args) => extract_main(&args),
Commands::Merge(args) => merge_main(&args),
}
}

Expand Down
172 changes: 172 additions & 0 deletions src/merge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use std::hash::{Hash, Hasher};

use clap::Args;
use itertools::Itertools;

use crate::ts;
use crate::ts::{MessageNode, TSNode};

/// Merges two translation file contexts and messages into a single output.
#[derive(Args)]
pub struct MergeArgs {
/// File to receive the merge
pub input_left: String,
/// File to include changes from
pub input_right: String,
/// If specified, will produce output in a file at designated location instead of stdout.
#[arg(short, long)]
pub output_path: Option<String>,
}

/// MessageNode that can be `eq(...)`.
#[derive(Eq, PartialOrd, Clone)]
struct EquatableMessageNode {
pub node: MessageNode,
}

/// The rule for equality is if the message id or source match.
impl PartialEq for EquatableMessageNode {
fn eq(&self, other: &Self) -> bool {
if let Some(this_id) = &self.node.id {
if let Some(other_id) = &other.node.id {
return this_id == other_id;
}
}

self.node.source == other.node.source && self.node.locations == other.node.locations
}
}

impl Hash for EquatableMessageNode {
fn hash<H: Hasher>(&self, state: &mut H) {
self.node.source.hash(state);
self.node.locations.iter().for_each(|loc| {
loc.line.hash(state);
loc.filename.hash(state);
});
}
}

// This wortks 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(format!(
"Could not process left file '{}'. Error: {}",
&args.input_left,
e.to_string()
));
}

if let Err(e) = right {
return Err(format!(
"Could not process right file '{}'. Error: {}",
&args.input_right,
e.to_string()
));
}

let result = merge_ts_nodes(left.unwrap(), right.unwrap());

ts::write_to_output(&args.output_path, &result)
}

fn merge_ts_nodes(mut left: TSNode, mut right: TSNode) -> TSNode {
left.messages = merge_messages(&mut left.messages, &mut right.messages);
merge_contexts(&mut left, right);
left
}

fn merge_contexts(left: &mut TSNode, right: TSNode) {
right.contexts.into_iter().for_each(|mut right_context| {
let left_context_opt = left
.contexts
.iter_mut()
.find(|left_context| left_context.name == right_context.name);

if let Some(left_context) = left_context_opt {
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 {
left.contexts.push(right_context);
}
});
}

/// Merges two messages collections
fn merge_messages(
left_messages: &mut Vec<MessageNode>,
right_messages: &mut Vec<MessageNode>,
) -> Vec<MessageNode> {
let mut unique_messages_left: Vec<_> = left_messages
.drain(0..)
.map(|node| EquatableMessageNode { node })
.collect();

let mut unique_messages_right: Vec<_> = right_messages
.drain(0..)
.map(|node| EquatableMessageNode { node })
.collect();

// Update oldcomment, oldsource.
unique_messages_right.iter_mut().for_each(|right_message| {
let left_message = unique_messages_left
.iter()
.find(|&msg| msg == right_message);

if let Some(left_message) = left_message {
if right_message.node.source != left_message.node.source {
right_message.node.oldsource = left_message.node.source.clone();
}

if right_message.node.comment != left_message.node.comment {
right_message.node.oldcomment = left_message.node.comment.clone();
}
}
});

unique_messages_left
.drain(0..)
.filter(|a| !unique_messages_right.contains(&a))
.merge(unique_messages_right.iter().cloned())
.map(|node| node.node)
.collect()
}

fn load_file(path: &String) -> Result<TSNode, String> {
match quick_xml::Reader::from_file(&path) {
Ok(reader) => {
let nodes: Result<TSNode, _> = quick_xml::de::from_reader(reader.into_inner());
match nodes {
Ok(nodes) => Ok(nodes),
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
}

#[cfg(test)]
mod merge_test {
use super::*;

#[test]
fn test_merge_two_files() {
let left = load_file(&"./test_data/example_merge_left.xml".to_string())
.expect("Test data could not be loaded for left file.");
let right = load_file(&"./test_data/example_merge_right.xml".to_string())
.expect("Test data could not be loaded for right file.");
let expected_result = load_file(&"./test_data/example_merge_result.xml".to_string())
.expect("Test data could not be loaded for right file.");

let result = merge_ts_nodes(left, right);

assert_eq!(result, expected_result);
}
}
Loading

0 comments on commit aae9383

Please sign in to comment.