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}