Skip to content

Commit d5d0086

Browse files
committed
refactor(converter): optimize resource handling and entry conversion
- Replaced `first()` with `into_iter().next()` for more efficient resource access in `convert_resources_to_format`. - Updated plural and string entry handling to use `into_iter()` for improved performance and clarity. - Enhanced the conversion logic in `From<Resource>` implementations to avoid unnecessary cloning, streamlining the entry creation process. - Adjusted tests to reflect changes in resource handling and ensure correctness in entry conversions.
1 parent fba3618 commit d5d0086

5 files changed

Lines changed: 101 additions & 74 deletions

File tree

langcodec-cli/src/convert.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -509,11 +509,12 @@ pub fn read_resources_from_any_input(
509509
});
510510

511511
if let Some(lang) = lang_from_filename {
512-
// Try with explicit format and language
512+
let err_prefix = format!("Failed to read input with language '{}': ", lang);
513+
513514
let format_type = if input.ends_with(".strings") {
514-
langcodec::formats::FormatType::Strings(Some(lang.clone()))
515+
langcodec::formats::FormatType::Strings(Some(lang))
515516
} else if input.ends_with(".xml") {
516-
langcodec::formats::FormatType::AndroidStrings(Some(lang.clone()))
517+
langcodec::formats::FormatType::AndroidStrings(Some(lang))
517518
} else if input.ends_with(".xcstrings") {
518519
langcodec::formats::FormatType::Xcstrings
519520
} else if input.ends_with(".csv") {
@@ -525,10 +526,9 @@ pub fn read_resources_from_any_input(
525526
};
526527

527528
let mut codec = Codec::new();
528-
codec.read_file_by_type(input, format_type).map_err(|e2| {
529-
format!("Failed to read input with language '{}': {}", lang, e2)
530-
})?;
531-
return Ok(codec.resources);
529+
codec
530+
.read_file_by_type(input, format_type)
531+
.map_err(|e2| format!("{err_prefix}{e2}"))?;
532532
} else {
533533
eprintln!("Standard format detection failed: {}", e);
534534
}

langcodec/src/converter.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ pub fn convert_resources_to_format(
5454
) -> Result<(), Error> {
5555
match output_format {
5656
FormatType::AndroidStrings(_) => {
57-
if let Some(resource) = resources.first() {
58-
AndroidStringsFormat::from(resource.clone())
57+
if let Some(resource) = resources.into_iter().next() {
58+
AndroidStringsFormat::from(resource)
5959
.write_to(Path::new(output_path))
6060
.map_err(|e| {
6161
Error::conversion_error(
@@ -70,8 +70,8 @@ pub fn convert_resources_to_format(
7070
}
7171
}
7272
FormatType::Strings(_) => {
73-
if let Some(resource) = resources.first() {
74-
StringsFormat::try_from(resource.clone())
73+
if let Some(resource) = resources.into_iter().next() {
74+
StringsFormat::try_from(resource)
7575
.and_then(|f| f.write_to(Path::new(output_path)))
7676
.map_err(|e| {
7777
Error::conversion_error(
@@ -891,19 +891,19 @@ mod tests {
891891
// Read back as Android
892892
let android = crate::formats::AndroidStringsFormat::read_from(&output).unwrap();
893893
assert_eq!(android.plurals.len(), 1);
894-
let p = &android.plurals[0];
894+
let p = android.plurals.into_iter().next().unwrap();
895895
assert_eq!(p.name, "apples");
896896
// Should include at least 'one' and 'other'
897897
let mut qs: Vec<_> = p
898898
.items
899-
.iter()
899+
.into_iter()
900900
.map(|i| match i.quantity {
901-
PluralCategory::One => ("one", i.value.clone()),
902-
PluralCategory::Other => ("other", i.value.clone()),
903-
PluralCategory::Zero => ("zero", i.value.clone()),
904-
PluralCategory::Two => ("two", i.value.clone()),
905-
PluralCategory::Few => ("few", i.value.clone()),
906-
PluralCategory::Many => ("many", i.value.clone()),
901+
PluralCategory::One => ("one", i.value),
902+
PluralCategory::Other => ("other", i.value),
903+
PluralCategory::Zero => ("zero", i.value),
904+
PluralCategory::Two => ("two", i.value),
905+
PluralCategory::Few => ("few", i.value),
906+
PluralCategory::Many => ("many", i.value),
907907
})
908908
.collect();
909909
qs.sort_by(|a, b| a.0.cmp(b.0));

langcodec/src/formats/android_strings.rs

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -131,22 +131,22 @@ impl From<Resource> for Format {
131131
fn from(value: Resource) -> Self {
132132
let mut strings = Vec::new();
133133
let mut plurals = Vec::new();
134-
for entry in &value.entries {
135-
match &entry.value {
136-
Translation::Singular(_) => strings.push(StringResource::from_entry(entry)),
134+
for entry in value.entries {
135+
match entry.value {
136+
Translation::Singular(_) => strings.push(StringResource::from_entry(&entry)),
137137
Translation::Plural(p) => {
138138
let mut items: Vec<PluralItem> = p
139139
.forms
140-
.iter()
140+
.into_iter()
141141
.map(|(cat, v)| PluralItem {
142-
quantity: cat.clone(),
143-
value: v.clone(),
142+
quantity: cat,
143+
value: v,
144144
})
145145
.collect();
146146
// Ensure stable order later
147147
items.sort_by(|a, b| a.quantity.cmp(&b.quantity));
148148
plurals.push(PluralsResource {
149-
name: entry.id.clone(),
149+
name: entry.id,
150150
items,
151151
translatable: match entry.status {
152152
EntryStatus::Translated => Some(true),
@@ -159,7 +159,7 @@ impl From<Resource> for Format {
159159
}
160160

161161
Self {
162-
language: value.metadata.language.clone(),
162+
language: value.metadata.language,
163163
strings,
164164
plurals,
165165
}
@@ -168,13 +168,18 @@ impl From<Resource> for Format {
168168

169169
impl From<Format> for Resource {
170170
fn from(value: Format) -> Self {
171-
let mut entries: Vec<Entry> = value.strings.iter().map(StringResource::to_entry).collect();
171+
let mut entries: Vec<Entry> = value
172+
.strings
173+
.into_iter()
174+
.map(StringResource::into_entry)
175+
.collect();
172176

173177
// Convert plurals to entries
174-
for pr in &value.plurals {
178+
for pr in value.plurals {
175179
let mut forms = std::collections::BTreeMap::new();
176-
for item in &pr.items {
177-
forms.insert(item.quantity.clone(), item.value.clone());
180+
for item in pr.items {
181+
let PluralItem { quantity, value } = item;
182+
forms.insert(quantity, value);
178183
}
179184
let all_empty = forms.values().all(|v| v.is_empty());
180185
let status = match pr.translatable {
@@ -190,10 +195,7 @@ impl From<Format> for Resource {
190195
};
191196
entries.push(Entry {
192197
id: pr.name.clone(),
193-
value: Translation::Plural(Plural {
194-
id: pr.name.clone(),
195-
forms,
196-
}),
198+
value: Translation::Plural(Plural { id: pr.name, forms }),
197199
comment: None,
198200
status,
199201
custom: HashMap::new(),
@@ -202,7 +204,7 @@ impl From<Format> for Resource {
202204

203205
Resource {
204206
metadata: Metadata {
205-
language: value.language.clone(),
207+
language: value.language,
206208
domain: String::new(), // strings.xml does not have a domain
207209
custom: HashMap::new(),
208210
},
@@ -219,15 +221,23 @@ pub struct StringResource {
219221
}
220222

221223
impl StringResource {
222-
fn to_entry(&self) -> Entry {
224+
fn into_entry(self) -> Entry {
225+
let StringResource {
226+
name,
227+
value,
228+
translatable,
229+
} = self;
230+
231+
let is_value_empty = value.is_empty();
232+
223233
Entry {
224-
id: self.name.clone(),
225-
value: Translation::Singular(self.value.clone()),
234+
id: name,
235+
value: Translation::Singular(value),
226236
comment: None,
227-
status: match self.translatable {
237+
status: match translatable {
228238
Some(true) => EntryStatus::Translated,
229239
Some(false) => EntryStatus::DoNotTranslate,
230-
None if self.value.is_empty() => EntryStatus::New,
240+
None if is_value_empty => EntryStatus::New,
231241
None => EntryStatus::Translated,
232242
},
233243
custom: HashMap::new(),
@@ -386,6 +396,7 @@ fn parse_plurals_resource<R: BufRead>(
386396

387397
#[cfg(test)]
388398
mod tests {
399+
389400
use super::*;
390401
use crate::traits::Parser;
391402
use crate::types::EntryStatus;
@@ -487,8 +498,9 @@ mod tests {
487498
</resources>
488499
"#;
489500
let format = Format::from_str(xml).unwrap();
490-
assert_eq!(format.strings.len(), 1);
491-
let entry = format.strings[0].to_entry();
501+
let length = format.strings.len();
502+
assert_eq!(length, 1);
503+
let entry = format.strings.into_iter().next().unwrap().into_entry();
492504
assert_eq!(entry.status, EntryStatus::New);
493505
}
494506

langcodec/src/formats/strings.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ impl From<Format> for Resource {
110110
domain: String::from(""),
111111
custom: HashMap::new(),
112112
},
113-
entries: value.pairs.iter().map(Pair::to_entry).collect(),
113+
entries: value.pairs.into_iter().map(Pair::into_entry).collect(),
114114
}
115115
}
116116
}
@@ -119,14 +119,12 @@ impl TryFrom<Resource> for Format {
119119
type Error = Error;
120120

121121
fn try_from(value: Resource) -> Result<Self, Self::Error> {
122-
let language = value.metadata.language.clone();
123-
124-
let pairs = value
125-
.entries
126-
.iter()
127-
.map(|entry| Pair::try_from(entry.clone()))
122+
let Resource { metadata, entries } = value;
123+
let language = metadata.language;
124+
let pairs = entries
125+
.into_iter()
126+
.map(Pair::try_from)
128127
.collect::<Result<Vec<_>, _>>()?;
129-
130128
Ok(Format { language, pairs })
131129
}
132130
}
@@ -150,12 +148,20 @@ pub struct Pair {
150148
}
151149

152150
impl Pair {
153-
fn to_entry(&self) -> Entry {
151+
fn into_entry(self) -> Entry {
152+
let Pair {
153+
key,
154+
value,
155+
comment,
156+
} = self;
157+
158+
let is_pair_value_empty = value.is_empty();
159+
154160
Entry {
155-
id: self.key.clone(),
156-
value: Translation::Singular(self.value.clone()),
157-
comment: self.comment.clone(),
158-
status: if self.value.is_empty() {
161+
id: key,
162+
value: Translation::Singular(value),
163+
comment,
164+
status: if is_pair_value_empty {
159165
EntryStatus::New
160166
} else {
161167
EntryStatus::Translated
@@ -687,7 +693,7 @@ mod tests {
687693
assert_eq!(pair.key, "empty");
688694
assert_eq!(pair.value, "");
689695
// Should be marked as New status in Entry
690-
let entry = pair.to_entry();
696+
let entry = pair.clone().into_entry();
691697
assert_eq!(entry.status, EntryStatus::New);
692698
}
693699

langcodec/src/formats/xcstrings.rs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,38 +41,47 @@ impl TryFrom<Vec<Resource>> for Format {
4141
let mut source_language = String::new();
4242
let mut version = String::new();
4343

44-
for resource in resources {
45-
if let Some(meta_source_language) = resource.metadata.custom.get("source_language") {
46-
// source_language should be modified if and only if it is empty, otherwise,
47-
// it should throw an error if it differs.
48-
if source_language.is_empty() {
49-
source_language = meta_source_language.clone();
50-
} else if source_language != *meta_source_language {
44+
for mut resource in resources {
45+
// source_language
46+
if source_language.is_empty() {
47+
if let Some(v) = resource.metadata.custom.remove("source_language") {
48+
source_language = v; // moved, no clone
49+
} else {
50+
return Err(Error::InvalidResource(
51+
"No source language found in metadata".into(),
52+
));
53+
}
54+
} else if let Some(v) = resource.metadata.custom.get("source_language") {
55+
if source_language != *v {
5156
return Err(Error::DataMismatch(format!(
5257
"Source language mismatch: expected {}, found {}",
53-
source_language, meta_source_language
58+
source_language, v
5459
)));
5560
}
5661
} else {
5762
return Err(Error::InvalidResource(
58-
"No source language found in metadata".to_string(),
63+
"No source language found in metadata".into(),
5964
));
6065
}
6166

62-
if let Some(meta_version) = resource.metadata.custom.get("version") {
63-
// version should be modified if and only if it is empty, otherwise,
64-
// it should throw an error if it differs.
65-
if version.is_empty() {
66-
version = meta_version.clone();
67-
} else if version != *meta_version {
67+
if version.is_empty() {
68+
if let Some(v) = resource.metadata.custom.remove("version") {
69+
version = v; // move, no clone
70+
} else {
71+
return Err(Error::InvalidResource(
72+
"No version found in metadata".into(),
73+
));
74+
}
75+
} else if let Some(v) = resource.metadata.custom.get("version") {
76+
if version != *v {
6877
return Err(Error::DataMismatch(format!(
6978
"Version mismatch: expected {}, found {}",
70-
version, meta_version
79+
version, v
7180
)));
7281
}
7382
} else {
7483
return Err(Error::InvalidResource(
75-
"No version found in metadata".to_string(),
84+
"No version found in metadata".into(),
7685
));
7786
}
7887

0 commit comments

Comments
 (0)