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}