rusty_lines/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use std::{
4    fmt::{Debug, Display},
5    fs::File,
6    path::Path,
7};
8
9use editor::LineEditor;
10use file::FileReader;
11use rustyline::error::ReadlineError;
12
13mod editor;
14mod file;
15
16type Result<T, E = Error> = std::result::Result<T, E>;
17
18/// Wrapper for rustlyline ReadlineError
19pub struct Error(ReadlineError);
20
21impl std::fmt::Debug for Error {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        Debug::fmt(&self.0, f)
24    }
25}
26
27impl std::fmt::Display for Error {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        Display::fmt(&self.0, f)
30    }
31}
32
33impl std::error::Error for Error {
34    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
35        self.0.source()
36    }
37}
38
39/// Builder for lines read from the tty or stdin
40pub struct TTYLinesBuilder<P: AsRef<Path>> {
41    exit_terms: &'static [&'static str],
42    history_filename: Option<P>,
43    prompt_name: String,
44}
45
46impl<P: AsRef<Path>> TTYLinesBuilder<P> {
47    /// prompt to display is using the tty as input
48    pub fn prompt(prompt: &str) -> Self {
49        Self {
50            prompt_name: prompt.to_owned(),
51            exit_terms: &[],
52            history_filename: None,
53        }
54    }
55
56    /// set the words that stop the iteration
57    pub fn exit_on(self, exit_terms: &'static [&'static str]) -> Self {
58        Self {
59            prompt_name: self.prompt_name,
60            exit_terms,
61            history_filename: self.history_filename,
62        }
63    }
64
65    /// Set history on when using the tty. History is saved to a file with filenam
66    pub fn history(self, filename: P) -> Self {
67        Self {
68            prompt_name: self.prompt_name,
69            exit_terms: self.exit_terms,
70            history_filename: Some(filename),
71        }
72    }
73
74    /// Construct the line iterator
75    pub fn build(self) -> Result<Box<dyn Iterator<Item = Result<String>>>> {
76        let reader = LineEditor::try_new(&self.prompt_name, self.exit_terms, self.history_filename)
77            .map_err(Error)?;
78        Ok(Box::new(reader))
79    }
80}
81
82/// Builder for lines read from a file
83pub struct FileLinesBuilder<P: AsRef<Path>> {
84    filename: P,
85    replace_variables: bool,
86}
87
88impl<P: AsRef<Path>> FileLinesBuilder<P> {
89    /// path sets the filename on the filesystem to read from
90    pub fn file(path: P) -> Self {
91        Self {
92            filename: path,
93            replace_variables: false,
94        }
95    }
96
97    /// if used then environment variables will be captured and replaced by their value
98    pub fn replace_variables(self) -> Self {
99        Self {
100            filename: self.filename,
101            replace_variables: true,
102        }
103    }
104
105    /// construct the line iterator
106    pub fn build(self) -> Result<Box<dyn Iterator<Item = Result<String, Error>>>> {
107        let reader = FileReader::<File>::try_new(self.filename, self.replace_variables)?;
108        Ok(Box::new(reader))
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use crate::{FileLinesBuilder, TTYLinesBuilder};
115
116    #[test]
117    fn options() {
118        let mut input = FileLinesBuilder::file("Cargo.toml").build().unwrap();
119        assert_eq!(input.next().unwrap().unwrap(), "[package]".to_owned());
120    }
121
122    #[test]
123    fn file_options_builder() {
124        let mut input = Some("Cargo.toml")
125            .map(|f| FileLinesBuilder::file(f).replace_variables().build())
126            .unwrap_or(
127                TTYLinesBuilder::prompt("tipctl")
128                    .exit_on(&["exit", "quit"])
129                    .history("history.txt")
130                    .build(),
131            )
132            .unwrap();
133        assert_eq!(input.next().unwrap().unwrap(), "[package]".to_owned());
134    }
135}