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}