Skip to main content

zql_cli/core/
pager.rs

1use crate::regex;
2use std::collections::VecDeque;
3use std::process::{Child, Command, Stdio};
4
5pub struct Pager {
6    command: String,
7    args: VecDeque<String>,
8    failed: bool,
9}
10
11impl Pager {
12    pub fn new(pager: String) -> Self {
13        let regex = regex!(r"\bless\b");
14        let mut args = pager.split_whitespace().map(String::from).collect::<VecDeque<_>>();
15        let command = args.pop_front().unwrap_or_else(|| String::from("less"));
16        if regex.is_match(&command) {
17            // -F or --quit-if-one-screen
18            //     Causes less to automatically exit if the entire file
19            //     can be displayed on the first screen.
20            // -R or --RAW-CONTROL-CHARS
21            //     Like -r, but only ANSI "color" escape sequences and OSC 8
22            //     hyperlink sequences are output in "raw" form.  Unlike -r,
23            //     the screen appearance is maintained correctly, provided
24            //     that there are no escape sequences in the file other than
25            //     these types of escape sequences.  Color escape sequences
26            //     are only supported when the color is changed within one
27            //     line, not across lines.  In other words, the beginning
28            //     of each line is assumed to be normal (non-colored),
29            //     regardless of any escape sequences in previous lines.
30            //     For the purpose of keeping track of screen appearance,
31            //     these escape sequences are assumed to not move the cursor.
32            // -X or --no-init
33            //     Disables sending the termcap initialization and
34            //     deinitialization strings to the terminal.  This is
35            //     sometimes desirable if the deinitialization string
36            //     does something unnecessary, like clearing the screen.
37            args.push_back(String::from("-FRX"));
38        }
39        Self { command, args, failed: false }
40    }
41
42    pub fn spawn_child(&mut self) -> Option<Child> {
43        if !self.failed {
44            let child = spawn_child(&self.command, &self.args);
45            self.failed = child.is_none();
46            child
47        } else {
48            None
49        }
50    }
51}
52
53fn spawn_child(command: &str, args: &VecDeque<String>) -> Option<Child> {
54    Command::new(command).args(args).stdin(Stdio::piped()).spawn().ok()
55}
56
57#[cfg(test)]
58mod tests {
59    use crate::core::pager::Pager;
60    use pretty_assertions::assert_eq;
61
62    #[test]
63    fn test_empty_command_is_set_to_less() {
64        let pager = Pager::new(String::from("  "));
65        assert_eq!(pager.command, "less");
66        assert_eq!(pager.args, vec!["-FRX"]);
67    }
68
69    #[test]
70    fn test_options_are_extended_for_less() {
71        let pager = Pager::new(String::from("  less  "));
72        assert_eq!(pager.command, "less");
73        assert_eq!(pager.args, vec!["-FRX"]);
74
75        let pager = Pager::new(String::from("  less  -abc  -def  "));
76        assert_eq!(pager.command, "less");
77        assert_eq!(pager.args, vec!["-abc", "-def", "-FRX"]);
78    }
79
80    #[test]
81    fn test_options_not_extended_for_more() {
82        let pager = Pager::new(String::from("  more  "));
83        assert_eq!(pager.command, "more");
84        assert_eq!(pager.args, Vec::<String>::new());
85
86        let pager = Pager::new(String::from("  more  -abc  -def  "));
87        assert_eq!(pager.command, "more");
88        assert_eq!(pager.args, vec!["-abc", "-def"]);
89    }
90}