sphinx/
utils.rs

1use core::fmt::{Display, Write, Formatter, self};
2use std::io;
3use std::io::BufRead;
4use std::collections::VecDeque;
5
6// useful for writing string literals, to ensure that a gigantic string doesnt swamp the output
7pub fn trim_str(target: &impl AsRef<str>, maxlen: usize) -> impl Display + '_ {
8    TrimStr {
9        target: target.as_ref(),
10        maxlen,
11    }
12}
13
14// captures the arguments to trim_str(), to implement trimming in fmt() without requiring an extra string buffer
15struct TrimStr<'s> {
16    target: &'s str,
17    maxlen: usize,
18}
19
20// note, this gives us a blanket implementation of ToString as well, so if you do want a new string buffer you can get that too
21impl Display for TrimStr<'_> {
22    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
23        if self.target.len() > self.maxlen {
24            write!(fmt, "{}...", &self.target[..(self.maxlen-3)])
25        } else {
26            fmt.write_str(self.target)
27        }
28    }
29}
30
31pub fn title_case(s: &impl AsRef<str>) -> impl Display + '_ {
32    TitleCase { target: s.as_ref() }
33}
34
35struct TitleCase<'s> {
36    target: &'s str,
37}
38
39impl Display for TitleCase<'_> {
40    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
41        let mut prev = None;
42        for next in self.target.chars() {
43            if next.is_alphabetic() && prev.map_or(true, char::is_whitespace) {
44                for c in next.to_uppercase() {
45                    fmt.write_char(c)?;
46                }
47            } else {
48                fmt.write_char(next)?;
49            }
50            
51            prev.replace(next);
52        }
53        Ok(())
54    }
55}
56
57pub fn fmt_join<'a>(sep: impl Display + 'a, items: &'a [impl Display]) -> impl Display + 'a {
58    DisplayJoin { sep, items }
59}
60
61struct DisplayJoin<'a, S, D> where S: Display, D: Display {
62    sep: S,
63    items: &'a [D],
64}
65
66impl<S, D> Display for DisplayJoin<'_, S, D> where S: Display, D: Display {
67    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
68        for item in self.items.iter().take(self.items.len() - 1) {
69            item.fmt(fmt)?;
70            self.sep.fmt(fmt)?;
71        }
72        if let Some(item) = self.items.last() {
73            item.fmt(fmt)?;
74        }
75        Ok(())
76    }
77}
78
79
80// This struct is born out of a desire to read a file into unicode characters 
81// without pulling the entire file into a buffer
82
83#[derive(Debug)]
84pub struct ReadChars<R> where R: BufRead {
85    read: R,
86    linebuf: String,
87    charbuf: VecDeque<char>,
88}
89
90impl<R> ReadChars<R> where R: BufRead {
91    pub fn new(read: R) -> Self {
92        ReadChars {
93            read,
94            linebuf: String::new(),
95            charbuf: VecDeque::new(),
96        }
97    }
98}
99
100impl<R> Iterator for ReadChars<R> where R: BufRead {
101    type Item = io::Result<char>;
102    
103    fn next(&mut self) -> Option<io::Result<char>> {
104        let next = self.charbuf.pop_front().map(Ok);
105        if next.is_some() {
106            return next;
107        }
108        
109        // refill linebuf with the next line
110        
111        self.linebuf.clear();
112        
113        let mut safety = 0;
114        while self.linebuf.is_empty() && safety < 0xFFFF {
115            
116            match self.read.read_line(&mut self.linebuf) {
117                Err(error) => return Some(Err(error)),
118                Ok(0) => return None, // EOF
119                _ => { safety += 1 },
120            }
121            
122        }
123        
124        self.charbuf.extend(self.linebuf.chars());
125        self.charbuf.pop_front().map(Ok)
126    }
127}
128
129
130
131// Formatter that uses a closure
132// Useful to avoid a lot of boilerplate when there are multiple ways to Display a struct
133
134pub fn make_display<F>(fmt_func: F) -> impl fmt::Display where F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result {
135    FnFormatter { fmt_func }
136}
137
138struct FnFormatter<F> where F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result {
139    fmt_func: F,
140}
141
142impl<F> fmt::Display for FnFormatter<F> where F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result {
143    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144        (self.fmt_func)(fmt)
145    }
146}
147
148
149// Formats an error that may have a message and/or a source error
150pub fn format_error(fmt: &mut fmt::Formatter<'_>, title: &str, message: Option<&str>, source: Option<&dyn std::error::Error>) -> fmt::Result {
151    // empty messages are formatted the same as no message
152    let message =
153        if let Some("") = message { None }
154        else { message };
155    
156    match (message, source) {
157        (None, None) => fmt.write_str(title),
158        (None, Some(error)) => write!(fmt, "{}: {}", title, error),
159        (Some(message), None) => write!(fmt, "{}: {}", title, message),
160        (Some(message), Some(error)) => write!(fmt, "{}: {}: {}", title, message, error),
161    }
162}