gluesql_cli/
cli.rs

1use {
2    crate::{
3        command::{Command, CommandError},
4        helper::CliHelper,
5        print::Print,
6    },
7    edit::{Builder, edit_file, edit_with_builder},
8    futures::executor::block_on,
9    gluesql_core::{
10        prelude::Glue,
11        store::{GStore, GStoreMut},
12    },
13    rustyline::{Editor, error::ReadlineError},
14    std::{
15        error::Error,
16        fs::File,
17        io::{Read, Result, Write},
18        path::Path,
19    },
20};
21
22pub struct Cli<T, W>
23where
24    T: GStore + GStoreMut,
25    W: Write,
26{
27    glue: Glue<T>,
28    print: Print<W>,
29}
30
31impl<T, W> Cli<T, W>
32where
33    T: GStore + GStoreMut,
34    W: Write,
35{
36    pub fn new(storage: T, output: W) -> Self {
37        let glue = Glue::new(storage);
38        let print = Print::new(output, None, Default::default());
39
40        Self { glue, print }
41    }
42
43    pub fn run(&mut self) -> std::result::Result<(), Box<dyn Error>> {
44        macro_rules! println {
45            ($($p:tt),*) => ( writeln!(&mut self.print.output, $($p),*)?; )
46        }
47
48        self.print.help()?;
49
50        let mut rl = Editor::<CliHelper>::new();
51        rl.set_helper(Some(CliHelper));
52
53        loop {
54            let line = match rl.readline("gluesql> ") {
55                Ok(line) => line,
56                Err(ReadlineError::Interrupted) => {
57                    println!("^C");
58                    continue;
59                }
60                Err(ReadlineError::Eof) => {
61                    println!("bye\n");
62                    break;
63                }
64                Err(e) => {
65                    println!("[unknown error] {:?}", e);
66                    break;
67                }
68            };
69
70            let line = line.trim();
71            if !(line.starts_with(".edit") || line.starts_with(".run")) {
72                rl.add_history_entry(line);
73            }
74
75            let command = match Command::parse(line, &self.print.option) {
76                Ok(command) => command,
77                Err(CommandError::LackOfTable) => {
78                    println!("[error] should specify table. eg: .columns TableName\n");
79                    continue;
80                }
81                Err(CommandError::LackOfFile) => {
82                    println!("[error] should specify file path.\n");
83                    continue;
84                }
85                Err(CommandError::NotSupported) => {
86                    println!("[error] command not supported: {}", line);
87                    println!("\n  type .help to list all available commands.\n");
88                    continue;
89                }
90                Err(CommandError::LackOfOption) => {
91                    println!("[error] should specify option.\n");
92                    continue;
93                }
94                Err(CommandError::LackOfValue(usage)) => {
95                    println!("[error] should specify value.\n{usage}\n");
96                    continue;
97                }
98                Err(CommandError::WrongOption(e)) => {
99                    println!("[error] cannot support option: {e}\n");
100                    continue;
101                }
102                Err(CommandError::LackOfSQLHistory) => {
103                    println!("[error] Nothing in SQL history to run.\n");
104                    continue;
105                }
106            };
107
108            match command {
109                Command::Help => {
110                    self.print.help()?;
111                    continue;
112                }
113                Command::Quit => {
114                    println!("bye\n");
115                    break;
116                }
117                Command::Execute(sql) => self.execute(sql)?,
118                Command::ExecuteFromFile(filename) => {
119                    if let Err(e) = self.load(&filename) {
120                        println!("[error] {}\n", e);
121                    }
122                }
123                Command::SpoolOn(path) => {
124                    self.print.spool_on(path)?;
125                }
126                Command::SpoolOff => {
127                    self.print.spool_off();
128                }
129                Command::Set(option) => self.print.set_option(option),
130                Command::Show(option) => self.print.show_option(option)?,
131                Command::Edit(file_name) => {
132                    match file_name {
133                        Some(file_name) => {
134                            let file = Path::new(&file_name);
135                            edit_file(file)?;
136                        }
137                        None => {
138                            let mut builder = Builder::new();
139                            builder.prefix("Glue_").suffix(".sql");
140                            let last = rl.history().last().map_or_else(|| "", String::as_str);
141                            let edited = edit_with_builder(last, &builder)?;
142                            rl.add_history_entry(edited);
143                        }
144                    };
145                }
146                Command::Run => {
147                    let sql = rl.history().last().ok_or(CommandError::LackOfSQLHistory);
148
149                    match sql {
150                        Ok(sql) => {
151                            self.execute(sql)?;
152                        }
153                        Err(e) => {
154                            println!("[error] {}\n", e);
155                        }
156                    };
157                }
158            }
159        }
160
161        Ok(())
162    }
163
164    fn execute(&mut self, sql: impl AsRef<str>) -> Result<()> {
165        match block_on(self.glue.execute(sql)) {
166            Ok(payloads) => self.print.payloads(&payloads)?,
167            Err(e) => {
168                println!("[error] {e}\n");
169            }
170        };
171
172        Ok(())
173    }
174
175    pub fn load<P: AsRef<Path>>(&mut self, filename: P) -> Result<()> {
176        let mut sqls = String::new();
177        File::open(filename)?.read_to_string(&mut sqls)?;
178        for sql in sqls.split(';').filter(|sql| !sql.trim().is_empty()) {
179            match block_on(self.glue.execute(sql)) {
180                Ok(payloads) => self.print.payloads(&payloads)?,
181                Err(e) => {
182                    println!("[error] {e}\n");
183                    break;
184                }
185            }
186        }
187
188        Ok(())
189    }
190}