ruby_string/
iterator.rs

1use crate::*;
2
3///A part of a [`RubyString`](struct.RubyString.html) that either has no ruby glosses or exactly
4///one ruby gloss attached to it. This type appears:
5///
6///- when iterating over the segments of a `RubyStrings` using its `segments` method, or
7///- when `extend()`ing a `RubyString` using an `impl Iterator<Item = Segment>`,
8///- when building a `RubyString` through `collect()` on an `impl Iterator<Item = Segment>`.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum Segment<'a> {
11    ///A piece of text that does not have any ruby glosses attached to it.
12    Plain { text: &'a str },
13    ///A piece of text that has exactly one ruby gloss attached to its entirety.
14    Rubied { text: &'a str, ruby: &'a str },
15}
16
17impl<'a> Segment<'a> {
18    ///Returns only the plain text in this segment, ignoring any ruby glosses attached to it.
19    pub fn plain_text(&self) -> &'a str {
20        match *self {
21            Segment::Plain { text } => text,
22            Segment::Rubied { text, .. } => text,
23        }
24    }
25
26    ///Returns an encoding of this segment as a plain String using interlinear annotation
27    ///characters.
28    ///
29    ///```
30    ///# use ruby_string::Segment;
31    ///let s = Segment::Plain { text: "です" };
32    ///assert_eq!(s.to_interlinear_encoding(), "です");
33    ///let s = Segment::Rubied { text: "東京", ruby: "とうきょう" };
34    ///assert_eq!(s.to_interlinear_encoding(), "\u{FFF9}東京\u{FFFA}とうきょう\u{FFFB}");
35    ///```
36    pub fn to_interlinear_encoding(&self) -> String {
37        match *self {
38            Segment::Plain { text } => text.into(),
39            Segment::Rubied { text, ruby } => format!("\u{FFF9}{}\u{FFFA}{}\u{FFFB}", text, ruby),
40        }
41    }
42}
43
44///A segment iterator for [`RubyString`](struct.RubyString.html).
45///
46///This struct is created by the `segments` method on `RubyString`. See its documentation for more.
47#[derive(Clone)]
48pub struct SegmentIterator<'a> {
49    pub(crate) string: &'a RubyString,
50    ///Start of the next segment.
51    pub(crate) next_text_start: usize,
52    ///The index of the placement that starts directly at `.next_text_start` or as close as
53    ///possible after it.
54    pub(crate) next_placement_idx: usize,
55}
56
57impl<'a> Iterator for SegmentIterator<'a> {
58    type Item = Segment<'a>;
59
60    fn next(&mut self) -> Option<Segment<'a>> {
61        if self.next_text_start >= self.string.packed_text.len() {
62            //nothing left at all in the RubyString
63            return None;
64        }
65        if self.next_placement_idx >= self.string.placements.len() {
66            //only unrubied text left in the RubyString
67            let text_end = self.string.packed_text.len();
68            let text = &self.string.packed_text[self.next_text_start..text_end];
69            self.next_text_start = text_end;
70            return Some(Segment::Plain { text });
71        }
72        let placement = self.string.placements[self.next_placement_idx];
73        if self.next_text_start < placement.text_start {
74            //we have not reached the next rubied part yet - yield the plain text until there
75            let text = &self.string.packed_text[self.next_text_start..placement.text_start];
76            self.next_text_start = placement.text_start;
77            Some(Segment::Plain { text })
78        } else {
79            //yield a rubied part
80            let text = &self.string.packed_text[placement.text_start..placement.text_end];
81            let ruby = &self.string.packed_ruby[placement.ruby_start..placement.ruby_end];
82            self.next_text_start = placement.text_end;
83            self.next_placement_idx += 1;
84            Some(Segment::Rubied { text, ruby })
85        }
86    }
87}