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
18pub 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
39pub 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 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 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 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 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
82pub struct FileLinesBuilder<P: AsRef<Path>> {
84 filename: P,
85 replace_variables: bool,
86}
87
88impl<P: AsRef<Path>> FileLinesBuilder<P> {
89 pub fn file(path: P) -> Self {
91 Self {
92 filename: path,
93 replace_variables: false,
94 }
95 }
96
97 pub fn replace_variables(self) -> Self {
99 Self {
100 filename: self.filename,
101 replace_variables: true,
102 }
103 }
104
105 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}