Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Tools/langtool/src/section.rs
5669 views
1
// Super simplified ini file processor.
2
// Doesn't even bother with understanding comments.
3
// Just understands section headings and
4
// keys and values, split by ' = '.
5
6
use regex::Regex;
7
8
#[derive(Debug, Clone)]
9
pub struct Section {
10
pub name: String,
11
pub title_line: String,
12
pub lines: Vec<String>,
13
}
14
15
pub fn split_line(line: &str) -> Option<(&str, &str)> {
16
let line = line.trim();
17
if let Some(pos) = line.find(" =") {
18
let value = &line[pos + 2..];
19
if value.is_empty() {
20
return None;
21
}
22
return Some((line[0..pos].trim(), value.trim()));
23
}
24
None
25
}
26
27
pub fn line_value(line: &str) -> Option<&str> {
28
split_line(line).map(|tuple| tuple.1)
29
}
30
31
impl Section {
32
pub fn apply_regex(&mut self, key: &str, pattern: &str, replacement: &str) {
33
let re = Regex::new(pattern).unwrap();
34
for line in self.lines.iter_mut() {
35
let prefix = if let Some(pos) = line.find(" =") {
36
&line[0..pos]
37
} else {
38
continue;
39
};
40
if prefix.eq(key) {
41
if let Some((_, value)) = split_line(line) {
42
let new_value = re.replace_all(value, replacement);
43
*line = format!("{} = {}", key, new_value);
44
}
45
}
46
}
47
}
48
49
pub fn remove_line(&mut self, key: &str) -> Option<String> {
50
let mut remove_index = None;
51
for (index, line) in self.lines.iter().enumerate() {
52
let prefix = if let Some(pos) = line.find(" =") {
53
&line[0..pos]
54
} else {
55
continue;
56
};
57
58
if prefix.eq(key) {
59
remove_index = Some(index);
60
break;
61
}
62
}
63
64
if let Some(remove_index) = remove_index {
65
Some(self.lines.remove(remove_index))
66
} else {
67
None
68
}
69
}
70
71
pub fn remove_linebreaks(&mut self, key: &str) {
72
for line in self.lines.iter_mut() {
73
let prefix = if let Some(pos) = line.find(" =") {
74
&line[0..pos]
75
} else {
76
continue;
77
};
78
if !prefix.trim().eq(key) {
79
continue;
80
}
81
// Escaped linebreaks.
82
*line = line.replace("\\n", " ");
83
}
84
}
85
86
pub fn get_line(&self, key: &str) -> Option<String> {
87
for line in self.lines.iter() {
88
let prefix = if let Some(pos) = line.find(" =") {
89
&line[0..pos]
90
} else {
91
continue;
92
};
93
94
if prefix.eq(key) {
95
return Some(line.clone());
96
}
97
}
98
None
99
}
100
101
pub fn insert_line_if_missing(&mut self, line: &str) -> bool {
102
let prefix = if let Some(pos) = line.find(" =") {
103
&line[0..pos + 2]
104
} else {
105
return false;
106
};
107
108
// Ignore comments when copying lines.
109
if prefix.starts_with('#') {
110
return false;
111
}
112
// Need to decide a policy for these.
113
if prefix.starts_with("translators") {
114
return false;
115
}
116
let prefix = prefix.to_owned();
117
118
for iter_line in &self.lines {
119
if iter_line.starts_with(&prefix) {
120
// Already have it
121
return false;
122
}
123
}
124
125
// Now try to insert it at an alphabetic-ish location.
126
let prefix = prefix.to_ascii_lowercase();
127
128
// Then, find a suitable insertion spot
129
for (i, iter_line) in self.lines.iter().enumerate() {
130
if iter_line.to_ascii_lowercase() > prefix {
131
println!("{}: Inserting line {line}", self.name);
132
self.lines.insert(i, line.to_owned());
133
return true;
134
}
135
}
136
137
for i in (0..self.lines.len()).rev() {
138
if self.lines[i].is_empty() {
139
continue;
140
}
141
println!("{}: Inserting line {line}", self.name);
142
self.lines.insert(i + 1, line.to_owned());
143
return true;
144
}
145
146
println!("{}: failed to insert {line}", self.name);
147
true
148
}
149
150
pub fn rename_key(&mut self, old: &str, new: &str) {
151
let prefix = old.to_owned() + " =";
152
let mut found_index = None;
153
for (index, line) in self.lines.iter().enumerate() {
154
if line.starts_with(&prefix) {
155
found_index = Some(index);
156
}
157
}
158
if let Some(index) = found_index {
159
let line = self.lines.remove(index);
160
let mut right_part = line.strip_prefix(&prefix).unwrap().to_string();
161
if right_part.trim() == old.trim() {
162
// Was still untranslated - replace the translation too.
163
right_part = format!(" {new}");
164
}
165
let line = new.to_owned() + " =" + &right_part;
166
self.insert_line_if_missing(&line);
167
} else {
168
let name = &self.name;
169
println!("rename_key: didn't find a line starting with {prefix} in section {name}");
170
}
171
}
172
173
pub fn dupe_key(&mut self, old: &str, new: &str) {
174
let prefix = old.to_owned() + " =";
175
let mut found_index = None;
176
for (index, line) in self.lines.iter().enumerate() {
177
if line.starts_with(&prefix) {
178
found_index = Some(index);
179
}
180
}
181
if let Some(index) = found_index {
182
let line = self.lines.get(index).unwrap();
183
let mut right_part = line.strip_prefix(&prefix).unwrap().to_string();
184
if right_part.trim() == old.trim() {
185
// Was still untranslated - replace the translation too.
186
right_part = format!(" {new}");
187
}
188
let line = new.to_owned() + " =" + &right_part;
189
self.insert_line_if_missing(&line);
190
} else {
191
let name = &self.name;
192
println!("dupe_key: didn't find a line starting with {prefix} in section {name}");
193
}
194
}
195
196
pub fn sort(&mut self) {
197
self.lines.sort();
198
}
199
200
pub fn comment_out_lines_if_not_in(&mut self, other: &Section) {
201
// Brute force (O(n^2)). Bad but not a problem.
202
203
for line in &mut self.lines {
204
let prefix = if let Some(pos) = line.find(" =") {
205
&line[0..pos + 2]
206
} else {
207
// Keep non-key lines.
208
continue;
209
};
210
if prefix.starts_with("Font") || prefix.starts_with('#') {
211
continue;
212
}
213
if !other.lines.iter().any(|line| line.starts_with(prefix)) && !prefix.contains("URL") {
214
println!("Commenting out from {}: {line}", other.name);
215
// Comment out the line.
216
*line = "#".to_owned() + line;
217
}
218
}
219
}
220
221
pub fn remove_lines_if_not_in(&mut self, other: &Section) {
222
// Brute force (O(n^2)). Bad but not a problem.
223
224
self.lines.retain(|line| {
225
let prefix = if let Some(pos) = line.find(" =") {
226
&line[0..pos + 2]
227
} else {
228
// Keep non-key lines.
229
return true;
230
};
231
if prefix.starts_with("Font") || prefix.starts_with('#') {
232
return true;
233
}
234
235
// keeps the line if this expression returns true.
236
other.lines.iter().any(|line| line.starts_with(prefix))
237
});
238
}
239
240
pub fn get_lines_not_in(&self, other: &Section) -> Vec<String> {
241
let mut missing_lines = Vec::new();
242
// Brute force (O(n^2)). Bad but not a problem.
243
for line in &self.lines {
244
let prefix = if let Some(pos) = line.find(" =") {
245
&line[0..pos + 2]
246
} else {
247
// Keep non-key lines.
248
continue;
249
};
250
if prefix.starts_with("Font") || prefix.starts_with('#') {
251
continue;
252
}
253
254
// keeps the line if this expression returns true.
255
if !other.lines.iter().any(|line| line.starts_with(prefix)) {
256
missing_lines.push(line.clone());
257
}
258
}
259
missing_lines
260
}
261
262
pub fn get_keys_if_not_in(&mut self, other: &Section) -> Vec<String> {
263
let mut missing_lines = Vec::new();
264
// Brute force (O(n^2)). Bad but not a problem.
265
for line in &self.lines {
266
let prefix = if let Some(pos) = line.find(" =") {
267
&line[0..pos + 2]
268
} else {
269
// Keep non-key lines.
270
continue;
271
};
272
if prefix.starts_with("Font") || prefix.starts_with('#') {
273
continue;
274
}
275
276
// keeps the line if this expression returns true.
277
if !other.lines.iter().any(|line| line.starts_with(prefix)) {
278
missing_lines.push(prefix[0..prefix.len() - 2].trim().to_string());
279
}
280
}
281
missing_lines
282
}
283
284
// Returns true if the key was found and updated.
285
pub fn set_value(&mut self, key: &str, value: &str, comment: Option<&str>) -> bool {
286
let mut found_index = None;
287
for (index, line) in self.lines.iter().enumerate() {
288
let prefix = if let Some(pos) = line.find(" =") {
289
&line[0..pos]
290
} else {
291
continue;
292
};
293
294
if prefix.eq(key) {
295
found_index = Some(index);
296
break;
297
}
298
}
299
300
if let Some(found_index) = found_index {
301
self.lines[found_index] = match comment {
302
Some(c) => format!("{} = {} # {}", key, value, c),
303
None => format!("{} = {}", key, value),
304
};
305
true
306
} else {
307
false
308
}
309
}
310
311
pub fn get_value(&self, key: &str) -> Option<String> {
312
for line in &self.lines {
313
if let Some((ref_key, value)) = split_line(line) {
314
if key.eq(ref_key) {
315
// Found it!
316
// The value might have a comment starting with #, strip that before returning.
317
let value = value.split('#').next().unwrap().trim();
318
return Some(value.to_string());
319
}
320
}
321
}
322
None
323
}
324
}
325
326