mortal/
util.rs

1//! Miscellaneous utility functions
2
3use std::str::CharIndices;
4
5/// Returns the width of a character in the terminal.
6///
7/// Returns `None` or `Some(0)` for control characters.
8#[inline]
9pub fn char_width(ch: char) -> Option<usize> {
10    use unicode_width::UnicodeWidthChar;
11
12    ch.width()
13}
14
15/// Returns whether the given character is a combining mark.
16#[inline]
17pub fn is_combining_mark(ch: char) -> bool {
18    use unicode_normalization::char::is_combining_mark;
19
20    is_combining_mark(ch)
21}
22
23const CTRL_MASK: u8 = 0x1f;
24const UNCTRL_BIT: u8 = 0x40;
25
26/// Returns the control character corresponding to the given character.
27///
28/// # Examples
29///
30/// ```
31/// # use mortal::util::ctrl;
32/// // Ctrl-C
33/// assert_eq!(ctrl('c'), '\x03');
34/// ```
35#[inline]
36pub fn ctrl(ch: char) -> char {
37    ((ch as u8) & CTRL_MASK) as char
38}
39
40/// Returns whether the given character is a control character.
41///
42/// Control characters are in the range `'\0'` ... `'\x1f'`, inclusive.
43#[inline]
44pub fn is_ctrl(ch: char) -> bool {
45    let ch = ch as u32;
46    ch & (CTRL_MASK as u32) == ch
47}
48
49/// Returns the ASCII character corresponding to the given control character.
50///
51/// If `ch` is not a control character, the result is unspecified.
52///
53/// # Examples
54///
55/// ```
56/// # use mortal::util::unctrl_upper;
57/// // Ctrl-C
58/// assert_eq!(unctrl_upper('\x03'), 'C');
59/// ```
60#[inline]
61pub fn unctrl_upper(ch: char) -> char {
62    ((ch as u8) | UNCTRL_BIT) as char
63}
64
65/// Returns the lowercase ASCII character corresponding to the given control character.
66///
67/// If `ch` is not a control character, the result is unspecified.
68///
69/// # Examples
70///
71/// ```
72/// # use mortal::util::unctrl_lower;
73///
74/// // Ctrl-C
75/// assert_eq!(unctrl_lower('\x03'), 'c');
76/// ```
77#[inline]
78pub fn unctrl_lower(ch: char) -> char {
79    unctrl_upper(ch).to_ascii_lowercase()
80}
81
82/// Iterator over string prefixes.
83///
84/// An instance of this type is returned by the free function [`prefixes`].
85///
86/// [`prefixes`]: fn.prefixes.html
87pub struct Prefixes<'a> {
88    s: &'a str,
89    iter: CharIndices<'a>,
90}
91
92/// Returns an iterator over all non-empty prefixes of `s`, beginning with
93/// the shortest.
94///
95/// If `s` is an empty string, the iterator will yield no elements.
96///
97/// # Examples
98///
99/// ```
100/// # use mortal::util::prefixes;
101/// let mut pfxs = prefixes("foo");
102///
103/// assert_eq!(pfxs.next(), Some("f"));
104/// assert_eq!(pfxs.next(), Some("fo"));
105/// assert_eq!(pfxs.next(), Some("foo"));
106/// assert_eq!(pfxs.next(), None);
107/// ```
108#[inline]
109pub fn prefixes(s: &str) -> Prefixes {
110    Prefixes{
111        s,
112        iter: s.char_indices(),
113    }
114}
115
116impl<'a> Iterator for Prefixes<'a> {
117    type Item = &'a str;
118
119    fn next(&mut self) -> Option<&'a str> {
120        self.iter.next().map(|(idx, ch)| &self.s[..idx + ch.len_utf8()])
121    }
122}
123
124#[cfg(test)]
125mod test {
126    use super::{ctrl, is_ctrl, unctrl_lower, unctrl_upper, prefixes};
127
128    #[test]
129    fn test_unctrl() {
130        for ch in 0u8..255 {
131            let ch = ch as char;
132
133            if is_ctrl(ch) {
134                assert_eq!(ch, ctrl(unctrl_lower(ch)));
135                assert_eq!(ch, ctrl(unctrl_upper(ch)));
136            }
137        }
138    }
139
140    #[test]
141    fn test_prefix_iter() {
142        let mut pfxs = prefixes("foobar");
143
144        assert_eq!(pfxs.next(), Some("f"));
145        assert_eq!(pfxs.next(), Some("fo"));
146        assert_eq!(pfxs.next(), Some("foo"));
147        assert_eq!(pfxs.next(), Some("foob"));
148        assert_eq!(pfxs.next(), Some("fooba"));
149        assert_eq!(pfxs.next(), Some("foobar"));
150        assert_eq!(pfxs.next(), None);
151
152        let mut pfxs = prefixes("a");
153
154        assert_eq!(pfxs.next(), Some("a"));
155        assert_eq!(pfxs.next(), None);
156
157        let mut pfxs = prefixes("");
158
159        assert_eq!(pfxs.next(), None);
160    }
161}