Skip to main content

zql_cli/core/
system.rs

1use crate::core::editor::CommandEditor;
2use crate::db::driver::Driver;
3use crate::error::{EngineError, MyError, MyResult};
4use rustyline::error::ReadlineError;
5use std::collections::BTreeMap;
6use std::fs;
7use std::fs::File;
8use std::io::{ErrorKind, Read, Write};
9use std::path::PathBuf;
10
11pub trait System<F: Read + Write> {
12    fn open_config(&mut self) -> MyResult<Option<F>>;
13
14    fn create_config(&self) -> MyResult<F>;
15
16    fn load_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()>;
17
18    fn save_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()>;
19
20    fn remove_history(&self, drivers: &BTreeMap<String, Driver>) -> MyResult<()>;
21}
22
23pub struct FileSystem;
24
25impl FileSystem {
26    pub fn new() -> Self {
27        Self { }
28    }
29}
30
31impl System<File> for FileSystem {
32    fn open_config(&mut self) -> MyResult<Option<File>> {
33        let path = config_path()?;
34        match File::open(&path) {
35            Ok(file) => Ok(Some(file)),
36            Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
37            Err(error) => Err(MyError::Io(error)),
38        }
39    }
40
41    fn create_config(&self) -> MyResult<File> {
42        let path = config_path()?;
43        if let Some(parent) = path.parent() {
44            fs::create_dir_all(parent)?;
45        }
46        let writer = File::create(&path)?;
47        Ok(writer)
48    }
49
50    fn load_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()> {
51        let path = history_path(alias)?;
52        match editor.load_history(&path) {
53            Ok(_) => Ok(()),
54            Err(ReadlineError::Io(error)) if error.kind() == ErrorKind::NotFound => Ok(()),
55            Err(error) => Err(MyError::Readline(error)),
56        }
57    }
58
59    fn save_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()> {
60        let path = history_path(alias)?;
61        if let Some(parent) = path.parent() {
62            fs::create_dir_all(parent)?;
63        }
64        editor.save_history(&path)?;
65        Ok(())
66    }
67
68    fn remove_history(&self, drivers: &BTreeMap<String, Driver>) -> MyResult<()> {
69        let dir = history_dir()?;
70        if let Ok(entries) = dir.read_dir() {
71            entries
72                .into_iter()
73                .filter_map(|entry| entry.ok())
74                .filter(|entry| entry.path().is_file())
75                .filter(|entry| !drivers.contains_key(entry.file_name().to_string_lossy().as_ref()))
76                .for_each(|entry| fs::remove_file(entry.path()).unwrap_or_default());
77        }
78        Ok(())
79    }
80}
81
82fn parent_dir() -> MyResult<PathBuf> {
83    match dirs::home_dir() {
84        Some(dir) => Ok(dir.join(".config").join("zql")),
85        None => Err(MyError::Engine(EngineError::HomeDirectory)),
86    }
87}
88
89fn config_path() -> MyResult<PathBuf> {
90    let parent = parent_dir()?;
91    let path = parent.join("drivers.yaml");
92    Ok(path)
93}
94
95fn history_dir() -> MyResult<PathBuf> {
96    let parent = parent_dir()?;
97    let path = parent.join("history");
98    Ok(path)
99}
100
101fn history_path(alias: &str) -> MyResult<PathBuf> {
102    let parent = parent_dir()?;
103    let path = parent.join("history").join(alias);
104    Ok(path)
105}
106
107#[cfg(any(test, debug_assertions))]
108pub mod tests {
109    use crate::core::editor::CommandEditor;
110    use crate::core::system::System;
111    use crate::db::driver::Driver;
112    use crate::error::MyResult;
113    use std::collections::BTreeMap;
114    use std::io::Cursor;
115
116    pub struct MockSystem {
117        buffer: Vec<u8>,
118    }
119
120    impl MockSystem {
121        pub fn new(source: &str) -> Self {
122            let buffer = Vec::from(source.as_bytes());
123            Self { buffer }
124        }
125    }
126
127    impl System<Cursor<Vec<u8>>> for MockSystem {
128        fn open_config(&mut self) -> MyResult<Option<Cursor<Vec<u8>>>> {
129            let buffer = self.buffer.drain(..).collect();
130            let reader = Cursor::new(buffer);
131            Ok(Some(reader))
132        }
133
134        fn create_config(&self) -> MyResult<Cursor<Vec<u8>>> {
135            let writer = Cursor::new(Vec::new());
136            Ok(writer)
137        }
138
139        fn load_history(&self, _editor: &mut CommandEditor, _alias: &str) -> MyResult<()> {
140            Ok(())
141        }
142
143        fn save_history(&self, _editor: &mut CommandEditor, _alias: &str) -> MyResult<()> {
144            Ok(())
145        }
146
147        fn remove_history(&self, _drivers: &BTreeMap<String, Driver>) -> MyResult<()> {
148            Ok(())
149        }
150    }
151}