• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

corebreaker / poreader / 178

03 May 2024 05:07AM UTC coverage: 98.197% (-1.6%) from 99.764%
178

push

circleci

web-flow
Merge pull request #13 from corebreaker/circleci-project-setup

Fix coverage check in CI

708 of 721 relevant lines covered (98.2%)

50.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.55
/src/po/reader.rs
1
use super::{line::PoLine, line_iter::LineIter, parser::PoParser, MessageExtractor as Extractor};
2
use crate::{
3
    comment::Comment, error::Error, note::Note, plural::PluralForms, unit::Unit, CatalogueReader, Origin, State,
4
};
5

6
use locale_config::LanguageRange;
7
use std::{
8
    collections::HashMap,
9
    io::Read,
10
    iter::Peekable,
11
    mem::{replace, swap},
12
    rc::Rc,
13
};
14

15
/// Object for reading PO streams
16
///
17
/// An iterator is implemented for reading each unit of translation in the PO stream.
18
pub struct PoReader<'p, R: Read> {
19
    lines: Peekable<LineIter<'p, R>>,
20
    next_unit: Option<Result<Unit, Error>>,
21
    header_notes: Vec<Note>,
22
    header_comments: Vec<Comment>,
23
    header_properties: HashMap<String, String>,
24
    target_language: LanguageRange<'static>,
25
    plural_forms: Option<Rc<PluralForms>>,
26
}
27

28
impl<'p, R: Read> PoReader<'p, R> {
29
    pub(super) fn new(reader: R, parser: &'p PoParser) -> Result<PoReader<'p, R>, Error> {
5✔
30
        let mut res = PoReader {
5✔
31
            lines: LineIter::new(reader, parser).peekable(),
5✔
32
            next_unit: None,
5✔
33
            header_notes: vec![],
5✔
34
            header_comments: vec![],
5✔
35
            header_properties: HashMap::new(),
5✔
36
            target_language: LanguageRange::invariant(),
5✔
37
            plural_forms: None,
5✔
38
        };
×
39

40
        let (next_unit, has_header) = match res.next_unit(true) {
9✔
41
            Some(Err(err)) => {
1✔
42
                return Err(err);
1✔
43
            }
44
            Some(Ok(u)) => {
4✔
45
                let has_header = u.message().is_empty();
4✔
46

47
                (Some(Ok(u)), has_header)
4✔
48
            }
4✔
49
            u => (u, false),
×
50
        };
5✔
51

52
        res.next_unit = next_unit;
4✔
53
        if has_header {
6✔
54
            res.parse_po_header(parser)?;
8✔
55
            res.next_unit = res.next_unit(false);
2✔
56
        }
57

58
        Ok(res)
3✔
59
    }
5✔
60

61
    fn read_line(&mut self) -> Result<Option<(usize, bool)>, Error> {
20✔
62
        match self.lines.peek() {
20✔
63
            // end if no unit (possibly after comments)
64
            None => Ok(None),
4✔
65

66
            // error
67
            Some(Err(_)) => {
2✔
68
                if let Some(Err(err)) = replace(&mut self.next_unit, None) {
2✔
69
                    Err(err)
1✔
70
                } else if let Some(Err(err)) = self.lines.next() {
1✔
71
                    Err(err)
1✔
72
                } else {
73
                    unreachable!();
74
                }
1✔
75
            }
2✔
76

77
            // detect obsolete
78
            Some(Ok(PoLine::Message(line, p, ..))) if p.starts_with('~') => Ok(Some((*line, true))),
14✔
79

80
            // normal line
81
            Some(Ok(v)) => Ok(Some((v.line(), false))),
12✔
82
        }
83
    }
20✔
84

85
    fn parse_comments(&mut self, unit: &mut Unit) -> Result<(), Error> {
14✔
86
        while let Some(Ok(PoLine::Comment(..))) = self.lines.peek() {
60✔
87
            match self.lines.next() {
34✔
88
                Some(Ok(PoLine::Comment(_, ',', s))) => {
6✔
89
                    for flag in s.split(',').map(str::trim) {
23✔
90
                        unit.flags.insert(flag.to_string());
34✔
91

92
                        match flag {
93
                            "fuzzy" => unit.state = State::NeedsWork,
17✔
94
                            _ => (), // TODO: Implement other flags (do we need any?)
12✔
95
                        }
96
                    }
97
                }
6✔
98
                Some(Ok(PoLine::Comment(_, ':', s))) => {
10✔
99
                    unit.locations
20✔
100
                        .extend(s.split(char::is_whitespace).filter(|x| !x.is_empty()).map(From::from));
30✔
101
                }
10✔
102
                Some(Ok(PoLine::Comment(_, '.', value))) => {
1✔
103
                    unit.notes.push(Note::new(Origin::Developer, value));
1✔
104
                }
105
                Some(Ok(PoLine::Comment(_, ' ', value))) => {
7✔
106
                    unit.notes.push(Note::new(Origin::Translator, value));
7✔
107
                }
108
                Some(Ok(PoLine::Comment(_, kind, content))) => {
10✔
109
                    unit.comments.push(Comment::new(kind, content));
10✔
110
                }
111
                _ => unreachable!(), // we *know* it is a Some(Ok(Comment))
112
            }
113
        }
22✔
114

115
        if let Some(Err(_)) = self.lines.peek() {
26✔
116
            if let Some(Err(err)) = self.lines.next() {
5✔
117
                Err(err)
5✔
118
            } else {
119
                unreachable!();
120
            }
121
        } else {
5✔
122
            Ok(())
21✔
123
        }
124
    }
26✔
125

126
    fn parse_unit(&mut self, unit: Unit, first: bool) -> Result<Option<Unit>, Error> {
15✔
127
        let plural_forms = self.plural_forms.as_ref().map(Rc::clone);
15✔
128
        let params = Extractor::new(unit, &mut self.lines, plural_forms);
15✔
129

130
        params.parse_message_fields(first)
15✔
131
    }
15✔
132

133
    fn read_unit(&mut self, first: bool) -> Result<Option<Unit>, Error> {
18✔
134
        let mut unit = Unit::default();
18✔
135

136
        self.parse_comments(&mut unit)?;
36✔
137

138
        let line = match self.read_line()? {
15✔
139
            None => {
3✔
140
                return Ok(None);
3✔
141
            }
142
            Some((line, is_obsolete)) => {
12✔
143
                unit.obsolete = is_obsolete;
12✔
144
                line
145
            }
146
        };
12✔
147

148
        unit = match self.parse_unit(unit, first)? {
12✔
149
            Some(unit) => unit,
10✔
150
            None => {
151
                return Ok(None);
1✔
152
            }
153
        };
1✔
154

155
        if (!first) && unit.message.is_empty() {
20✔
156
            Err(Error::Unexpected(line, String::from("Source should not be empty")))
1✔
157
        } else {
158
            if unit.state == State::Empty && !unit.message.is_blank() {
12✔
159
                // translation is non-empty and state was not set yet, then it is final
160
                unit.state = State::Final;
6✔
161
            }
162

163
            Ok(Some(unit))
9✔
164
        }
165
    }
18✔
166

167
    fn next_unit(&mut self, first: bool) -> Option<Result<Unit, Error>> {
12✔
168
        match self.read_unit(first) {
12✔
169
            Ok(None) => None,
2✔
170
            Ok(Some(u)) => Some(Ok(u)),
7✔
171
            Err(e) => Some(Err(e)),
3✔
172
        }
173
    }
12✔
174

175
    fn parse_po_header(&mut self, parser: &PoParser) -> Result<(), Error> {
6✔
176
        if let Some(Ok(ref u)) = self.next_unit {
10✔
177
            let mut header_properties: HashMap<String, Vec<String>> = HashMap::new();
6✔
178

179
            for line in u.message.get_text().split('\n') {
20✔
180
                if let Some(n) = line.find(':') {
28✔
181
                    let key = line[..n].trim();
12✔
182
                    let val = line[(n + 1)..].trim();
12✔
183

184
                    header_properties
24✔
185
                        .entry(key.to_owned())
12✔
186
                        .or_default()
187
                        .push(val.to_owned());
12✔
188
                }
189
            }
190

191
            self.header_properties
12✔
192
                .extend(header_properties.into_iter().map(|(k, l)| (k, l.join(" "))));
17✔
193
            self.header_notes.extend_from_slice(&u.notes);
6✔
194
            self.header_comments.extend_from_slice(&u.comments);
6✔
195

196
            if let Some(lang) = self.header_properties.get("Language") {
10✔
197
                self.target_language = LanguageRange::new(lang)
4✔
198
                    .map(LanguageRange::into_static)
199
                    .or_else(|_| LanguageRange::from_unix(lang))
5✔
200
                    .unwrap_or_else(|_| LanguageRange::invariant());
1✔
201
            }
202

203
            if let Some(forms) = self.header_properties.get("Plural-Forms") {
6✔
204
                if !forms.is_empty() {
3✔
205
                    self.plural_forms.replace(Rc::new(PluralForms::parse(&forms, parser)?));
3✔
206
                }
207
            }
208
        }
6✔
209

210
        Ok(())
4✔
211
    }
6✔
212
}
213

214
impl<'p, R: Read> Iterator for PoReader<'p, R> {
215
    type Item = Result<Unit, Error>;
216

217
    fn next(&mut self) -> Option<Self::Item> {
5✔
218
        match self.next_unit {
5✔
219
            None => None,
2✔
220
            Some(Err(_)) => replace(&mut self.next_unit, None),
1✔
221
            _ => {
222
                let mut res = self.next_unit(false);
2✔
223

224
                swap(&mut res, &mut self.next_unit);
2✔
225
                res
2✔
226
            }
2✔
227
        }
228
    }
5✔
229
}
230

231
impl<'p, R: Read> CatalogueReader for PoReader<'p, R> {
232
    fn target_language(&self) -> &LanguageRange<'static> {
2✔
233
        &self.target_language
2✔
234
    }
4✔
235

236
    fn header_notes(&self) -> &Vec<Note> {
1✔
237
        &self.header_notes
238
    }
1✔
239

240
    fn header_comments(&self) -> &Vec<Comment> {
1✔
241
        &self.header_comments
1✔
242
    }
2✔
243

244
    fn header_properties(&self) -> &HashMap<String, String> {
1✔
245
        &self.header_properties
1✔
246
    }
2✔
247
}
248

249
// no-coverage:start
250
#[cfg(test)]
251
mod tests {
252
    use super::*;
253
    use crate::Message;
254
    use std::collections::HashSet;
255

256
    fn make_source() -> &'static str {
257
        "\
258
            #, flag1, flag2, fuzzy\n\
259
            #$ Any comment 1\n\
260
            #& Any comment 2\n\
261
            # Any note\n\
262
            #: File1:1 File1:2\n\
263
            #: File2:1 File2:2\n\
264
            msgctxt \"Any context\"\n\
265
            msgid \"\"\n\
266
            msgstr \"\"\n\
267
            \"Any-Header: Value\\n\"\n\
268
            \"Language: fr\\n\"\n\
269
            \n\
270
            msgid \"Hello, world !\"\n\
271
            msgstr \"Salut, tout le monde !\"\
272
        "
273
    }
274

275
    fn make_reader<R: Read>(reader: R, parser: &PoParser) -> PoReader<R> {
276
        let mut unit = Unit::default();
277

278
        unit.message = Message::Simple {
279
            id: String::new(),
280
            text: Some(String::from(
281
                "\
282
                    Header-1: Value1\n\
283
                    Language: en\n\
284
                    Plural-Forms: nplurals=2; plural=(n > 1);\n\
285
                    Header-2: ValueA\n\
286
                    Header-1: Value2\
287
                ",
288
            )),
289
        };
290

291
        PoReader {
292
            lines: LineIter::new(reader, parser).peekable(),
293
            next_unit: Some(Ok(unit)),
294
            header_notes: vec![
295
                Note::new(Origin::Translator, String::from("You")),
296
                Note::new(Origin::Developer, String::from("Me")),
297
            ],
298
            header_comments: vec![
299
                Comment::new('+', String::from("Comment 1")),
300
                Comment::new('=', String::from("Comment 2")),
301
            ],
302
            header_properties: HashMap::new(),
303
            target_language: LanguageRange::invariant(),
304
            plural_forms: None,
305
        }
306
    }
307

308
    #[test]
309
    fn test_func_parse_po_header_with_error() {
310
        let mut unit = Unit::default();
311

312
        unit.message = Message::Simple {
313
            id: String::new(),
314
            text: Some(String::from(r"Plural-Forms: plural=1+;")),
315
        };
316

317
        let source = "";
318
        let parser = PoParser::new();
319
        let mut reader = make_reader(source.as_bytes(), &parser);
320

321
        reader.next_unit.replace(Ok(unit));
322
        match reader.parse_po_header(&parser) {
323
            Err(err) => assert_eq!(
324
                format!("{:?}", err),
325
                r##"Error in plurals forms: Unrecognized EOF found at 2
326
Expected one of "(", "-", "n" or r#"[0-9]+"#"##,
327
            ),
328
            Ok(_) => panic!(
329
                "Unexpected result: forms={:?}, notes={:?}, headers={:?}, next={:?}",
330
                reader.plural_forms, reader.header_notes, reader.header_properties, reader.next_unit,
331
            ),
332
        }
333
    }
334

335
    #[test]
336
    fn test_func_parse_po_header_with_bad_language() {
337
        let fallback = LanguageRange::invariant();
338

339
        let source = "";
340
        let parser = PoParser::new();
341
        let mut reader = make_reader(source.as_bytes(), &parser);
342

343
        let mut unit = Unit::default();
344

345
        unit.message = Message::Simple {
346
            id: String::new(),
347
            text: Some(String::from(r"Language: fx42")),
348
        };
349

350
        reader.next_unit.replace(Ok(unit));
351

352
        match reader.parse_po_header(&parser) {
353
            Ok(()) => assert_eq!(reader.target_language(), &fallback),
354
            Err(err) => panic!(
355
                "Unexpected error: {:?}\nforms={:?}, notes={:?}, headers={:?}, next={:?}",
356
                err, reader.plural_forms, reader.header_notes, reader.header_properties, reader.next_unit,
357
            ),
358
        }
359
    }
360

361
    #[test]
362
    fn test_func_parse_po_header_normal() {
363
        let source = "";
364
        let parser = PoParser::new();
365
        let mut reader = make_reader(source.as_bytes(), &parser);
366

367
        match reader.parse_po_header(&parser) {
368
            Ok(()) => {
369
                assert_eq!(
370
                    reader.header_notes,
371
                    vec![
372
                        Note::new(Origin::Translator, String::from("You")),
373
                        Note::new(Origin::Developer, String::from("Me")),
374
                    ]
375
                );
376

377
                let definition = "nplurals=2; plural=(n > 1);";
378

379
                assert_eq!(
380
                    reader.header_properties,
381
                    vec![
382
                        ("Header-1", "Value1 Value2"),
383
                        ("Header-2", "ValueA"),
384
                        ("Language", "en"),
385
                        ("Plural-Forms", definition),
386
                    ]
387
                    .into_iter()
388
                    .map(|(k, v)| (String::from(k), String::from(v)))
389
                    .collect::<HashMap<_, _>>()
390
                );
391

392
                assert_eq!(
393
                    reader.header_comments,
394
                    vec![
395
                        Comment::new('+', String::from("Comment 1")),
396
                        Comment::new('=', String::from("Comment 2")),
397
                    ]
398
                );
399

400
                assert_eq!(reader.target_language.as_ref(), "en");
401

402
                if let Some(forms) = reader.plural_forms {
403
                    assert_eq!(forms.get_formula(), "(n > 1)");
404
                    assert_eq!(forms.get_definition(), definition);
405
                    assert_eq!(forms.get_count(), 2);
406
                } else {
407
                    panic!("Unexpected None for forms");
408
                }
409
            }
410
            Err(err) => panic!("Unexpected error: {:?}", err),
411
        }
412
    }
413

414
    #[test]
415
    fn test_func_read_line() {
416
        let parser = PoParser::new();
417

418
        {
419
            let source = "#~ msgid \"my-id-1\"\nmsgid \"my-id-2\"";
420
            let mut reader = make_reader(source.as_bytes(), &parser);
421

422
            match reader.read_line() {
423
                Ok(Some((line, result))) => {
424
                    assert!(result, "Result should be true");
425
                    assert_eq!(line, 1);
426
                }
427
                v => panic!("Unexpected result for first line: {:?}", v),
428
            }
429

430
            if let None = reader.lines.next() {
431
                panic!("Unexpected None as first result of next");
432
            }
433

434
            match reader.read_line() {
435
                Ok(Some((line, result))) => {
436
                    assert!(!result, "Result should be false");
437
                    assert_eq!(line, 2);
438
                }
439
                v => panic!("Unexpected result for second line: {:?}", v),
440
            }
441

442
            if let None = reader.lines.next() {
443
                panic!("Unexpected None as second result of next");
444
            }
445

446
            match reader.read_line() {
447
                Ok(None) => (),
448
                v => panic!("Unexpected result for the end: {:?}", v),
449
            }
450
        }
451

452
        {
453
            let source = "msgid \"my-error";
454
            let mut reader = make_reader(source.as_bytes(), &parser);
455

456
            match reader.read_line() {
457
                Err(err) => assert_eq!(format!("{:?}", err), "Parse error at line 2, got ‘msgid \"my-error’"),
458
                v => panic!("Unexpected result for the first error case: {:?}", v),
459
            }
460
        }
461

462
        {
463
            let source = "msgid \"my-error";
464
            let mut reader = make_reader(source.as_bytes(), &parser);
465

466
            reader.next_unit = Some(Err(Error::Unexpected(123, String::from("An error"))));
467
            match reader.read_line() {
468
                Err(err) => assert_eq!(format!("{:?}", err), "Unexpected error at line 123: An error"),
469
                v => panic!("Unexpected result for the second error case: {:?}", v),
470
            }
471
        }
472
    }
473

474
    #[test]
475
    fn test_func_parse_comments() {
476
        let parser = PoParser::new();
477

478
        {
479
            let source = "msgid \"my-error";
480
            let mut reader = make_reader(source.as_bytes(), &parser);
481
            let mut unit = Unit::default();
482

483
            match reader.parse_comments(&mut unit) {
484
                Err(err) => assert_eq!(format!("{:?}", err), "Parse error at line 2, got ‘msgid \"my-error’"),
485
                v => panic!("Unexpected result for the first error line: {:?}", v),
486
            }
487

488
            match reader.parse_comments(&mut unit) {
489
                Ok(()) => (),
490
                v => panic!("Unexpected result for the second error line: {:?}", v),
491
            }
492
        }
493

494
        {
495
            let source = "msgid \"my-id\"";
496
            let mut reader = make_reader(source.as_bytes(), &parser);
497
            let mut unit = Unit::default();
498

499
            match reader.parse_comments(&mut unit) {
500
                Ok(()) => (),
501
                v => panic!("Unexpected result for single line: {:?}", v),
502
            }
503
        }
504

505
        {
506
            let source = "# simple comment\nmsgid \"my-error";
507
            let mut reader = make_reader(source.as_bytes(), &parser);
508
            let mut unit = Unit::default();
509

510
            match reader.parse_comments(&mut unit) {
511
                Err(err) => assert_eq!(format!("{:?}", err), "Parse error at line 3, got ‘msgid \"my-error’"),
512
                v => panic!("Unexpected result for the third error line: {:?}", v),
513
            }
514

515
            match reader.parse_comments(&mut unit) {
516
                Ok(()) => assert_eq!(
517
                    unit.notes,
518
                    [Note::new(Origin::Translator, String::from("simple comment"))]
519
                ),
520
                v => panic!("Unexpected result for the fourth error line: {:?}", v),
521
            }
522
        }
523

524
        {
525
            let source = "#$ any comment\n# simple note";
526
            let mut reader = make_reader(source.as_bytes(), &parser);
527
            let mut unit = Unit::default();
528

529
            match reader.parse_comments(&mut unit) {
530
                Ok(()) => {
531
                    assert_eq!(unit.notes, [Note::new(Origin::Translator, String::from("simple note"))]);
532
                    assert_eq!(unit.comments, [Comment::new('$', String::from("any comment"))]);
533
                }
534
                Err(err) => panic!(
535
                    "Unexpected error: {:?}\nnotes={:?}, locations={:?}, flags={:?}",
536
                    err, unit.notes, unit.locations, unit.flags,
537
                ),
538
            }
539
        }
540

541
        {
542
            let source = "\
543
                #  translator comment\n\
544
                #. developer comment\n\
545
                #: Location:1 Location:2\n\
546
                #: Location:3 Location:4\n\
547
                #, flag1, flag2\n\
548
                #, flag3, fuzzy, flag4\n\
549
            ";
550

551
            let mut reader = make_reader(source.as_bytes(), &parser);
552
            let mut unit = Unit::default();
553

554
            match reader.parse_comments(&mut unit) {
555
                Ok(()) => {
556
                    assert_eq!(
557
                        unit.notes,
558
                        [
559
                            Note::new(Origin::Translator, String::from("translator comment")),
560
                            Note::new(Origin::Developer, String::from("developer comment")),
561
                        ]
562
                    );
563

564
                    assert_eq!(
565
                        unit.locations,
566
                        (1..=4).map(|i| format!("Location:{}", i)).collect::<Vec<_>>(),
567
                    );
568

569
                    assert_eq!(
570
                        unit.flags,
571
                        (1..=4)
572
                            .map(|i| format!("flag{}", i))
573
                            .chain(vec![String::from("fuzzy")])
574
                            .collect::<HashSet<_>>()
575
                    );
576

577
                    assert_eq!(unit.state, State::NeedsWork);
578
                }
579
                v => panic!("Unexpected result for single line: {:?}", v),
580
            }
581
        }
582
    }
583

584
    #[test]
585
    fn test_func_parse_unit() {
586
        let parser = PoParser::new();
587

588
        {
589
            let source = "msgid \"\"";
590
            let mut reader = make_reader(source.as_bytes(), &parser);
591

592
            match reader.parse_unit(Unit::default(), false) {
593
                Ok(None) => (),
594
                v => panic!("Unexpected result for empty `msgid`: {:?}", v),
595
            }
596
        }
597

598
        {
599
            let source = "msgid \"";
600
            let mut reader = make_reader(source.as_bytes(), &parser);
601

602
            match reader.parse_unit(Unit::default(), false) {
603
                Err(err) => assert_eq!(format!("{:?}", err), "Parse error at line 2, got ‘msgid \"’"),
604
                Ok(v) => panic!("Unexpected result for bad `msgid`: {:?}", v),
605
            }
606
        }
607

608
        {
609
            let source = make_source();
610
            let mut reader = make_reader(source.as_bytes(), &parser);
611
            let mut unit = Unit::default();
612

613
            reader
614
                .parse_comments(&mut unit)
615
                .expect("Comments should be parsed before tests");
616
            match reader.parse_unit(unit, true) {
617
                Ok(Some(unit)) => {
618
                    assert!(unit.message.is_empty(), "Message should be empty");
619
                    assert!(unit.prev_message.is_empty(), "Previous message should be empty");
620

621
                    assert_eq!(unit.message.get_text(), "Any-Header: Value\nLanguage: fr\n");
622

623
                    assert_eq!(unit.context(), Some("Any context"));
624
                    assert_eq!(unit.prev_context(), None);
625
                }
626
                v => panic!("Unexpected result for empty message (header): {:?}", v),
627
            }
628
        }
629
    }
630

631
    #[test]
632
    fn test_func_read_unit_normal() {
633
        let source = make_source();
634
        let parser = PoParser::new();
635
        let mut reader = make_reader(source.as_bytes(), &parser);
636

637
        match reader.read_unit(true) {
638
            Ok(Some(unit)) => {
639
                assert!(unit.message.is_empty(), "Message should be empty");
640
                assert!(unit.prev_message.is_empty(), "Previous message should be empty");
641
                assert!(!unit.obsolete, "This should not be obsolete");
642

643
                assert_eq!(
644
                    unit.comments,
645
                    vec![
646
                        Comment::new('$', String::from("Any comment 1")),
647
                        Comment::new('&', String::from("Any comment 2")),
648
                    ]
649
                );
650

651
                assert_eq!(
652
                    unit.locations,
653
                    ["File1:1", "File1:2", "File2:1", "File2:2",]
654
                        .into_iter()
655
                        .map(str::to_string)
656
                        .collect::<Vec<_>>()
657
                );
658

659
                assert_eq!(
660
                    unit.flags,
661
                    ["flag1", "flag2", "fuzzy"].into_iter().map(str::to_string).collect()
662
                );
663
                assert_eq!(
664
                    unit.notes,
665
                    vec![Note::new(Origin::Translator, String::from("Any note"))]
666
                );
667

668
                assert_eq!(unit.state, State::NeedsWork);
669
                assert_eq!(unit.message.get_text(), "Any-Header: Value\nLanguage: fr\n");
670

671
                assert_eq!(unit.context(), Some("Any context"));
672
                assert_eq!(unit.prev_context(), None);
673
            }
674
            v => panic!("Unexpected result for empty message (header): {:?}", v),
675
        }
676

677
        match reader.read_unit(false) {
678
            Ok(Some(unit)) => {
679
                assert_eq!(unit.state, State::Final);
680
                assert_eq!(unit.message.get_id(), "Hello, world !");
681
                assert_eq!(unit.message.get_text(), "Salut, tout le monde !");
682
            }
683
            v => panic!("Unexpected result for empty message (header): {:?}", v),
684
        }
685
    }
686

687
    #[test]
688
    fn test_func_read_unit_with_exceptions() {
689
        let parser = PoParser::new();
690

691
        {
692
            let source = "msgid \"";
693
            let mut reader = make_reader(source.as_bytes(), &parser);
694

695
            match reader.read_unit(false) {
696
                Err(err) => assert_eq!(format!("{:?}", err), "Parse error at line 2, got ‘msgid \"’"),
697
                Ok(r) => panic!("Unexpected result for the error test on parse comment: {:?}", r),
698
            }
699
        }
700

701
        {
702
            let source = "";
703
            let mut reader = make_reader(source.as_bytes(), &parser);
704

705
            match reader.read_unit(false) {
706
                Ok(None) => (),
707
                r => panic!("Unexpected result for the test on end of stream: {:?}", r),
708
            }
709
        }
710

711
        {
712
            let source = "#~ msgid \"\"";
713
            let mut reader = make_reader(source.as_bytes(), &parser);
714

715
            match reader.read_unit(false) {
716
                Ok(None) => (),
717
                r => panic!("Unexpected result for the test on \"non-message\": {:?}", r),
718
            }
719
        }
720

721
        {
722
            let source = "msgid \"\"\nmsgstr \"Something\"";
723
            let mut reader = make_reader(source.as_bytes(), &parser);
724

725
            match reader.read_unit(false) {
726
                Err(err) => assert_eq!(
727
                    format!("{:?}", err),
728
                    "Unexpected error at line 1: Source should not be empty"
729
                ),
730
                Ok(r) => panic!("Unexpected result for the test on empty messages: {:?}", r),
731
            }
732
        }
733
    }
734

735
    #[test]
736
    fn test_func_next_unit() {
737
        let source = "msgid \"my-id\"\nmsgstr \"my-text\"\nmsgid \"with-error\"\nmsgstr \"";
738
        let parser = PoParser::new();
739
        let mut reader = make_reader(source.as_bytes(), &parser);
740

741
        match reader.next_unit(false) {
742
            Some(Ok(unit)) => {
743
                assert_eq!(unit.message.get_id(), "my-id");
744
                assert_eq!(unit.message.get_text(), "my-text");
745
            }
746
            v => panic!("Unexpected result for any message: {:?}", v),
747
        }
748

749
        match reader.next_unit(false) {
750
            Some(Err(err)) => assert_eq!(format!("{:?}", err), "Parse error at line 5, got ‘msgstr \"’"),
751
            v => panic!("Unexpected result for test on error: {:?}", v),
752
        }
753

754
        match reader.next_unit(false) {
755
            None => (),
756
            v => panic!("Unexpected result for test on the end of source: {:?}", v),
757
        }
758
    }
759

760
    #[test]
761
    fn test_func_new_with_error_on_entry() {
762
        let source = "msgid: \"--";
763
        let parser = PoParser::new();
764
        let reader = PoReader::new(source.as_bytes(), &parser);
765

766
        match reader {
767
            Err(err) => assert_eq!(format!("{:?}", err), "Parse error at line 2, got ‘msgid: \"--’"),
768
            Ok(v) => panic!(
769
                "Unexpected result: forms={:?}, notes={:?}, headers={:?}, next={:?}",
770
                v.plural_forms, v.header_notes, v.header_properties, v.next_unit,
771
            ),
772
        }
773
    }
774

775
    #[test]
776
    fn test_func_new_with_error_on_plural() {
777
        let source = "msgid \"\"\nmsgstr \"\"\n\"Plural-Forms: plural=1+\"";
778
        let parser = PoParser::new();
779
        let reader = PoReader::new(source.as_bytes(), &parser);
780

781
        match reader {
782
            Err(err) => assert_eq!(
783
                format!("{:?}", err),
784
                "Unexpected error: Bad value list definition: `plural=1+`"
785
            ),
786
            Ok(v) => panic!(
787
                "Unexpected result: forms={:?}, notes={:?}, headers={:?}, next={:?}",
788
                v.plural_forms, v.header_notes, v.header_properties, v.next_unit,
789
            ),
790
        }
791
    }
792

793
    #[test]
794
    fn test_func_new_normal() {
795
        let source = make_source();
796
        let parser = PoParser::new();
797

798
        match PoReader::new(source.as_bytes(), &parser) {
799
            Ok(reader) => {
800
                assert_eq!(reader.target_language().as_ref(), "fr");
801
                assert_eq!(
802
                    reader.header_notes(),
803
                    &vec![Note::new(Origin::Translator, String::from("Any note"))]
804
                );
805

806
                assert_eq!(
807
                    reader.header_comments(),
808
                    &vec![
809
                        Comment::new('$', String::from("Any comment 1")),
810
                        Comment::new('&', String::from("Any comment 2")),
811
                    ]
812
                );
813

814
                assert_eq!(
815
                    reader.header_properties(),
816
                    &[("Any-Header", "Value"), ("Language", "fr"),]
817
                        .into_iter()
818
                        .map(|(k, v)| (k.to_string(), v.to_string()))
819
                        .collect::<HashMap<_, _>>()
820
                );
821

822
                match reader.plural_forms {
823
                    None => (),
824
                    Some(forms) => panic!("Unexpected forms: {:?}", forms),
825
                }
826
            }
827
            Err(err) => panic!("Unexpected error: {:?}", err),
828
        }
829
    }
830

831
    #[test]
832
    fn test_trait_iterator_normal() {
833
        let source = make_source();
834
        let parser = PoParser::new();
835

836
        match PoReader::new(source.as_bytes(), &parser) {
837
            Ok(mut reader) => {
838
                match reader.next() {
839
                    Some(Ok(unit)) => {
840
                        assert_eq!(unit.message.get_id(), "Hello, world !");
841
                        assert_eq!(unit.message.get_text(), "Salut, tout le monde !");
842
                    }
843
                    r => panic!("Unexpected result after the first call of `next()`: {:?}", r),
844
                }
845

846
                match reader.next() {
847
                    None => (),
848
                    Some(r) => panic!("Unexpected result after the second call of `next()`: {:?}", r),
849
                }
850
            }
851
            Err(err) => panic!("Unexpected error: {:?}", err),
852
        }
853
    }
854

855
    #[test]
856
    fn test_trait_iterator_with_error() {
857
        let source = "msgid \"msg\"\nmsgstr \"text\"\n\n#? xxx\nmsgid \"";
858
        let parser = PoParser::new();
859

860
        match PoReader::new(source.as_bytes(), &parser) {
861
            Ok(mut reader) => {
862
                match reader.next() {
863
                    Some(Ok(unit)) => {
864
                        assert_eq!(unit.message.get_id(), "msg");
865
                        assert_eq!(unit.message.get_text(), "text");
866
                    }
867
                    r => panic!("Unexpected result after the first call of `next()`: {:?}", r),
868
                }
869

870
                match reader.next() {
871
                    Some(Err(err)) => assert_eq!(format!("{:?}", err), "Parse error at line 6, got ‘msgid \"’"),
872
                    r => panic!("Unexpected result after the second call of `next()`: {:?}", r),
873
                }
874

875
                match reader.next() {
876
                    None => (),
877
                    Some(r) => panic!("Unexpected result after the third call of `next()`: {:?}", r),
878
                }
879
            }
880
            Err(err) => panic!("Unexpected error: {:?}", err),
881
        }
882
    }
883
}
884
// no-coverage:stop
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc