mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-19 01:24:20 +01:00
start of refactor of repl to use clap
This commit is contained in:
247
cli/app.rs
247
cli/app.rs
@@ -1,20 +1,19 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
commands::{args::EchoMode, import::ImportFile, Command, CommandParser},
|
||||||
helper::LimboHelper,
|
helper::LimboHelper,
|
||||||
import::{ImportFile, IMPORT_HELP},
|
input::{get_io, get_writer, DbLocation, OutputMode, Settings},
|
||||||
input::{get_io, get_writer, DbLocation, OutputMode, Settings, HELP_MSG},
|
|
||||||
opcodes_dictionary::OPCODE_DESCRIPTIONS,
|
opcodes_dictionary::OPCODE_DESCRIPTIONS,
|
||||||
};
|
};
|
||||||
use comfy_table::{Attribute, Cell, CellAlignment, Color, ContentArrangement, Row, Table};
|
use comfy_table::{Attribute, Cell, CellAlignment, Color, ContentArrangement, Row, Table};
|
||||||
use limbo_core::{Database, LimboError, OwnedValue, Statement, StepResult};
|
use limbo_core::{Database, LimboError, OwnedValue, Statement, StepResult};
|
||||||
|
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::Parser;
|
||||||
use rustyline::{history::DefaultHistory, Editor};
|
use rustyline::{history::DefaultHistory, Editor};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
str::FromStr,
|
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
@@ -52,116 +51,6 @@ pub struct Opts {
|
|||||||
pub experimental_mvcc: bool,
|
pub experimental_mvcc: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Command {
|
|
||||||
/// Exit this program with return-code CODE
|
|
||||||
Exit,
|
|
||||||
/// Quit the shell
|
|
||||||
Quit,
|
|
||||||
/// Open a database file
|
|
||||||
Open,
|
|
||||||
/// Display help message
|
|
||||||
Help,
|
|
||||||
/// Display schema for a table
|
|
||||||
Schema,
|
|
||||||
/// Set output file (or stdout if empty)
|
|
||||||
SetOutput,
|
|
||||||
/// Set output display mode
|
|
||||||
OutputMode,
|
|
||||||
/// Show vdbe opcodes
|
|
||||||
Opcodes,
|
|
||||||
/// Change the current working directory
|
|
||||||
Cwd,
|
|
||||||
/// Display information about settings
|
|
||||||
ShowInfo,
|
|
||||||
/// Set the value of NULL to be printed in 'list' mode
|
|
||||||
NullValue,
|
|
||||||
/// Toggle 'echo' mode to repeat commands before execution
|
|
||||||
Echo,
|
|
||||||
/// Display tables
|
|
||||||
Tables,
|
|
||||||
/// Import data from FILE into TABLE
|
|
||||||
Import,
|
|
||||||
/// Loads an extension library
|
|
||||||
LoadExtension,
|
|
||||||
/// Dump the current database as a list of SQL statements
|
|
||||||
Dump,
|
|
||||||
/// List vfs modules available
|
|
||||||
ListVfs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command {
|
|
||||||
fn min_args(&self) -> usize {
|
|
||||||
1 + match self {
|
|
||||||
Self::Exit
|
|
||||||
| Self::Quit
|
|
||||||
| Self::Schema
|
|
||||||
| Self::Help
|
|
||||||
| Self::Opcodes
|
|
||||||
| Self::ShowInfo
|
|
||||||
| Self::Tables
|
|
||||||
| Self::SetOutput
|
|
||||||
| Self::ListVfs
|
|
||||||
| Self::Dump => 0,
|
|
||||||
Self::Open
|
|
||||||
| Self::OutputMode
|
|
||||||
| Self::Cwd
|
|
||||||
| Self::Echo
|
|
||||||
| Self::NullValue
|
|
||||||
| Self::LoadExtension => 1,
|
|
||||||
Self::Import => 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::Exit => ".exit ?<CODE>",
|
|
||||||
Self::Quit => ".quit",
|
|
||||||
Self::Open => ".open <file>",
|
|
||||||
Self::Help => ".help",
|
|
||||||
Self::Schema => ".schema ?<table>?",
|
|
||||||
Self::Opcodes => ".opcodes",
|
|
||||||
Self::OutputMode => ".mode list|pretty",
|
|
||||||
Self::SetOutput => ".output ?file?",
|
|
||||||
Self::Cwd => ".cd <directory>",
|
|
||||||
Self::ShowInfo => ".show",
|
|
||||||
Self::NullValue => ".nullvalue <string>",
|
|
||||||
Self::Echo => ".echo on|off",
|
|
||||||
Self::Tables => ".tables",
|
|
||||||
Self::LoadExtension => ".load",
|
|
||||||
Self::Dump => ".dump",
|
|
||||||
Self::Import => &IMPORT_HELP,
|
|
||||||
Self::ListVfs => ".vfslist",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Command {
|
|
||||||
type Err = String;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
".exit" => Ok(Self::Exit),
|
|
||||||
".quit" => Ok(Self::Quit),
|
|
||||||
".open" => Ok(Self::Open),
|
|
||||||
".help" => Ok(Self::Help),
|
|
||||||
".schema" => Ok(Self::Schema),
|
|
||||||
".tables" => Ok(Self::Tables),
|
|
||||||
".opcodes" => Ok(Self::Opcodes),
|
|
||||||
".mode" => Ok(Self::OutputMode),
|
|
||||||
".output" => Ok(Self::SetOutput),
|
|
||||||
".cd" => Ok(Self::Cwd),
|
|
||||||
".show" => Ok(Self::ShowInfo),
|
|
||||||
".nullvalue" => Ok(Self::NullValue),
|
|
||||||
".echo" => Ok(Self::Echo),
|
|
||||||
".import" => Ok(Self::Import),
|
|
||||||
".load" => Ok(Self::LoadExtension),
|
|
||||||
".dump" => Ok(Self::Dump),
|
|
||||||
".vfslist" => Ok(Self::ListVfs),
|
|
||||||
_ => Err("Unknown command".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PROMPT: &str = "limbo> ";
|
const PROMPT: &str = "limbo> ";
|
||||||
|
|
||||||
pub struct Limbo<'a> {
|
pub struct Limbo<'a> {
|
||||||
@@ -265,7 +154,7 @@ impl<'a> Limbo<'a> {
|
|||||||
|
|
||||||
fn handle_first_input(&mut self, cmd: &str) {
|
fn handle_first_input(&mut self, cmd: &str) {
|
||||||
if cmd.trim().starts_with('.') {
|
if cmd.trim().starts_with('.') {
|
||||||
self.handle_dot_command(cmd);
|
self.handle_dot_command(&cmd[1..]);
|
||||||
} else {
|
} else {
|
||||||
self.run_query(cmd);
|
self.run_query(cmd);
|
||||||
}
|
}
|
||||||
@@ -414,12 +303,16 @@ impl<'a> Limbo<'a> {
|
|||||||
self.conn.close()
|
self.conn.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_echo(&mut self, arg: &str) {
|
fn toggle_echo(&mut self, arg: EchoMode) {
|
||||||
match arg.trim().to_lowercase().as_str() {
|
match arg {
|
||||||
"on" => self.opts.echo = true,
|
EchoMode::On => self.opts.echo = true,
|
||||||
"off" => self.opts.echo = false,
|
EchoMode::Off => self.opts.echo = false,
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
// match arg.trim().to_lowercase().as_str() {
|
||||||
|
// "on" => self.opts.echo = true,
|
||||||
|
// "off" => self.opts.echo = false,
|
||||||
|
// _ => {}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_db(&mut self, path: &str, vfs_name: Option<&str>) -> anyhow::Result<()> {
|
fn open_db(&mut self, path: &str, vfs_name: Option<&str>) -> anyhow::Result<()> {
|
||||||
@@ -522,7 +415,7 @@ impl<'a> Limbo<'a> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if line.starts_with('.') {
|
if line.starts_with('.') {
|
||||||
self.handle_dot_command(line);
|
self.handle_dot_command(&line[1..]);
|
||||||
let _ = self.reset_line(line);
|
let _ = self.reset_line(line);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -578,46 +471,44 @@ impl<'a> Limbo<'a> {
|
|||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Ok(ref cmd) = Command::from_str(args[0]) {
|
// else {
|
||||||
if args.len() < cmd.min_args() {
|
// // let _ = self.write_fmt(format_args!(
|
||||||
let _ = self.write_fmt(format_args!(
|
// // "Unknown command: {}\nenter: .help for all available commands",
|
||||||
"Insufficient arguments: USAGE: {}",
|
// // args[0]
|
||||||
cmd.usage()
|
// // ));
|
||||||
));
|
match CommandParser::try_parse_from(args) {
|
||||||
return;
|
Err(err) => {
|
||||||
|
let _ = self.write_fmt(format_args!("{err}"));
|
||||||
}
|
}
|
||||||
match cmd {
|
Ok(cmd) => match cmd.command {
|
||||||
Command::Exit => {
|
Command::Exit(args) => {
|
||||||
let code = args.get(1).and_then(|c| c.parse::<i32>().ok()).unwrap_or(0);
|
// let code = args.get(1).and_then(|c| c.parse::<i32>().ok()).unwrap_or(0);
|
||||||
std::process::exit(code);
|
std::process::exit(args.code);
|
||||||
}
|
}
|
||||||
Command::Quit => {
|
Command::Quit => {
|
||||||
let _ = self.writeln("Exiting Limbo SQL Shell.");
|
let _ = self.writeln("Exiting Limbo SQL Shell.");
|
||||||
let _ = self.close_conn();
|
let _ = self.close_conn();
|
||||||
std::process::exit(0)
|
std::process::exit(0)
|
||||||
}
|
}
|
||||||
Command::Open => {
|
Command::Open(args) => {
|
||||||
let vfs = args.get(2).map(|s| &**s);
|
if self.open_db(&args.path, args.vfs_name.as_deref()).is_err() {
|
||||||
if self.open_db(args[1], vfs).is_err() {
|
|
||||||
let _ = self.writeln("Error: Unable to open database file.");
|
let _ = self.writeln("Error: Unable to open database file.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Schema => {
|
Command::Schema(args) => {
|
||||||
let table_name = args.get(1).copied();
|
if let Err(e) = self.display_schema(args.table_name.as_deref()) {
|
||||||
if let Err(e) = self.display_schema(table_name) {
|
|
||||||
let _ = self.writeln(e.to_string());
|
let _ = self.writeln(e.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Tables => {
|
Command::Tables(args) => {
|
||||||
let pattern = args.get(1).copied();
|
if let Err(e) = self.display_tables(args.pattern.as_deref()) {
|
||||||
if let Err(e) = self.display_tables(pattern) {
|
|
||||||
let _ = self.writeln(e.to_string());
|
let _ = self.writeln(e.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Opcodes => {
|
Command::Opcodes(args) => {
|
||||||
if args.len() > 1 {
|
if let Some(opcode) = args.opcode {
|
||||||
for op in &OPCODE_DESCRIPTIONS {
|
for op in &OPCODE_DESCRIPTIONS {
|
||||||
if op.name.eq_ignore_ascii_case(args.get(1).unwrap().trim()) {
|
if op.name.eq_ignore_ascii_case(opcode.trim()) {
|
||||||
let _ = self.write_fmt(format_args!("{}", op));
|
let _ = self.write_fmt(format_args!("{}", op));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,51 +518,56 @@ impl<'a> Limbo<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::NullValue => {
|
Command::NullValue(args) => {
|
||||||
self.opts.null_value = args[1].to_string();
|
self.opts.null_value = args.value;
|
||||||
}
|
}
|
||||||
Command::OutputMode => match OutputMode::from_str(args[1], true) {
|
Command::OutputMode(args) => {
|
||||||
Ok(mode) => {
|
if let Err(e) = self.set_mode(args.mode) {
|
||||||
if let Err(e) = self.set_mode(mode) {
|
let _ = self.write_fmt(format_args!("{}", e));
|
||||||
let _ = self.write_fmt(format_args!("Error: {}", e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
}
|
||||||
let _ = self.writeln(e);
|
// OutputMode::from_str(args[1], true) {
|
||||||
}
|
// Ok(mode) => {
|
||||||
},
|
// if let Err(e) = self.set_mode(mode) {
|
||||||
Command::SetOutput => {
|
// let _ = self.write_fmt(format_args!("Error: {}", e));
|
||||||
if args.len() == 2 {
|
// }
|
||||||
if let Err(e) = self.set_output_file(args[1]) {
|
// }
|
||||||
|
// Err(e) => {
|
||||||
|
// let _ = self.writeln(e);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
Command::SetOutput(args) => {
|
||||||
|
if let Some(path) = args.path {
|
||||||
|
if let Err(e) = self.set_output_file(&path) {
|
||||||
let _ = self.write_fmt(format_args!("Error: {}", e));
|
let _ = self.write_fmt(format_args!("Error: {}", e));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.set_output_stdout();
|
self.set_output_stdout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Echo => {
|
Command::Echo(args) => {
|
||||||
self.toggle_echo(args[1]);
|
self.toggle_echo(args.mode);
|
||||||
}
|
}
|
||||||
Command::Cwd => {
|
Command::Cwd(args) => {
|
||||||
let _ = std::env::set_current_dir(args[1]);
|
let _ = std::env::set_current_dir(args.directory);
|
||||||
}
|
}
|
||||||
Command::ShowInfo => {
|
Command::ShowInfo => {
|
||||||
let _ = self.show_info();
|
let _ = self.show_info();
|
||||||
}
|
}
|
||||||
Command::Help => {
|
// Command::Help => {
|
||||||
let _ = self.writeln(HELP_MSG);
|
// let _ = self.writeln(HELP_MSG);
|
||||||
}
|
// }
|
||||||
Command::Import => {
|
Command::Import(args) => {
|
||||||
let mut import_file =
|
let mut import_file =
|
||||||
ImportFile::new(self.conn.clone(), self.io.clone(), &mut self.writer);
|
ImportFile::new(self.conn.clone(), self.io.clone(), &mut self.writer);
|
||||||
if let Err(e) = import_file.import(&args) {
|
import_file.import(args)
|
||||||
let _ = self.writeln(e.to_string());
|
// if let Err(e) = import_file.import(args) {
|
||||||
};
|
// let _ = self.writeln(e.to_string());
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
Command::LoadExtension =>
|
Command::LoadExtension(args) => {
|
||||||
{
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
if let Err(e) = self.handle_load_extension(args[1]) {
|
if let Err(e) = self.handle_load_extension(&args.path) {
|
||||||
let _ = self.writeln(&e);
|
let _ = self.writeln(&e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -686,12 +582,7 @@ impl<'a> Limbo<'a> {
|
|||||||
let _ = self.writeln(v);
|
let _ = self.writeln(v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} else {
|
|
||||||
let _ = self.write_fmt(format_args!(
|
|
||||||
"Unknown command: {}\nenter: .help for all available commands",
|
|
||||||
args[0]
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
82
cli/commands/args.rs
Normal file
82
cli/commands/args.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use clap::{Args, ValueEnum};
|
||||||
|
|
||||||
|
use crate::input::OutputMode;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct ExitArgs {
|
||||||
|
/// Exit code
|
||||||
|
#[arg(default_value_t = 0)]
|
||||||
|
pub code: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct OpenArgs {
|
||||||
|
/// Path to open database
|
||||||
|
pub path: String,
|
||||||
|
/// Name of VFS
|
||||||
|
pub vfs_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct SchemaArgs {
|
||||||
|
/// Table name to visualize schema
|
||||||
|
pub table_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct SetOutputArgs {
|
||||||
|
/// File path to send output to
|
||||||
|
pub path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct OutputModeArgs {
|
||||||
|
#[arg(value_enum)]
|
||||||
|
pub mode: OutputMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct OpcodesArgs {
|
||||||
|
/// Opcode to display description
|
||||||
|
pub opcode: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct CwdArgs {
|
||||||
|
/// Target directory
|
||||||
|
pub directory: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct NullValueArgs {
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct EchoArgs {
|
||||||
|
#[arg(value_enum)]
|
||||||
|
pub mode: EchoMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, ValueEnum, Clone)]
|
||||||
|
pub enum EchoMode {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct TablesArgs {
|
||||||
|
pub pattern: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct LoadExtensionArgs {
|
||||||
|
/// Path to extension file
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct ListVfsArgs {
|
||||||
|
/// Path to extension file
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand, Args};
|
||||||
use limbo_core::Connection;
|
use limbo_core::Connection;
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
@@ -9,14 +9,13 @@ use std::{
|
|||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static IMPORT_HELP: LazyLock<String> = LazyLock::new(|| {
|
// pub static IMPORT_HELP: LazyLock<String> = LazyLock::new(|| {
|
||||||
let empty: [&'static str; 2] = [".import", "--help"];
|
// let empty: [&'static str; 2] = [".import", "--help"];
|
||||||
let opts = ImportArgs::try_parse_from(empty);
|
// let opts = ImportArgs::try_parse_from(empty);
|
||||||
opts.map_err(|e| e.to_string()).unwrap_err()
|
// opts.map_err(|e| e.to_string()).unwrap_err()
|
||||||
});
|
// });
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Clone, Args)]
|
||||||
#[command(name = ".import")]
|
|
||||||
pub struct ImportArgs {
|
pub struct ImportArgs {
|
||||||
/// Use , and \n as column and row separators
|
/// Use , and \n as column and row separators
|
||||||
#[arg(long, default_value = "true")]
|
#[arg(long, default_value = "true")]
|
||||||
@@ -46,15 +45,16 @@ impl<'a> ImportFile<'a> {
|
|||||||
Self { conn, io, writer }
|
Self { conn, io, writer }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn import(&mut self, args: &[&str]) -> Result<(), Error> {
|
pub fn import(&mut self, args: ImportArgs) {
|
||||||
let import_args = ImportArgs::try_parse_from(args.iter());
|
self.import_csv(args);
|
||||||
match import_args {
|
// let import_args = ImportArgs::try_parse_from(args.iter());
|
||||||
Ok(args) => {
|
// match import_args {
|
||||||
self.import_csv(args);
|
// Ok(args) => {
|
||||||
Ok(())
|
// self.import_csv(args);
|
||||||
}
|
// Ok(())
|
||||||
Err(err) => Err(anyhow::anyhow!(err.to_string())),
|
// }
|
||||||
}
|
// Err(err) => Err(anyhow::anyhow!(err.to_string())),
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn import_csv(&mut self, args: ImportArgs) {
|
pub fn import_csv(&mut self, args: ImportArgs) {
|
||||||
151
cli/commands/mod.rs
Normal file
151
cli/commands/mod.rs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
pub mod args;
|
||||||
|
pub mod import;
|
||||||
|
|
||||||
|
use args::{
|
||||||
|
CwdArgs, EchoArgs, ExitArgs, LoadExtensionArgs, NullValueArgs, OpcodesArgs, OpenArgs,
|
||||||
|
OutputModeArgs, SchemaArgs, SetOutputArgs, TablesArgs,
|
||||||
|
};
|
||||||
|
use clap::Parser;
|
||||||
|
use import::ImportArgs;
|
||||||
|
|
||||||
|
use crate::input::{AFTER_HELP_MSG, BEFORE_HELP_MSG};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(
|
||||||
|
multicall = true,
|
||||||
|
arg_required_else_help(false),
|
||||||
|
before_help(BEFORE_HELP_MSG),
|
||||||
|
after_help(AFTER_HELP_MSG)
|
||||||
|
)]
|
||||||
|
pub struct CommandParser {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, clap::Subcommand)]
|
||||||
|
#[command(disable_help_flag(false), disable_version_flag(true))]
|
||||||
|
pub enum Command {
|
||||||
|
/// Exit this program with return-code CODE
|
||||||
|
#[command(subcommand_value_name = ".exit")]
|
||||||
|
Exit(ExitArgs),
|
||||||
|
/// Quit the shell
|
||||||
|
#[command(subcommand_value_name = ".quit")]
|
||||||
|
Quit,
|
||||||
|
/// Open a database file
|
||||||
|
#[command(subcommand_value_name = ".open")]
|
||||||
|
Open(OpenArgs),
|
||||||
|
// Display help message
|
||||||
|
// Help,
|
||||||
|
/// Display schema for a table
|
||||||
|
#[command(subcommand_value_name = ".schema")]
|
||||||
|
Schema(SchemaArgs),
|
||||||
|
/// Set output file (or stdout if empty)
|
||||||
|
#[command(name = "output", subcommand_value_name = ".output")]
|
||||||
|
SetOutput(SetOutputArgs),
|
||||||
|
/// Set output display mode
|
||||||
|
#[command(
|
||||||
|
name = "mode",
|
||||||
|
subcommand_value_name = ".mode",
|
||||||
|
arg_required_else_help(false)
|
||||||
|
)]
|
||||||
|
OutputMode(OutputModeArgs),
|
||||||
|
/// Show vdbe opcodes
|
||||||
|
#[command(subcommand_value_name = ".opcodes")]
|
||||||
|
Opcodes(OpcodesArgs),
|
||||||
|
/// Change the current working directory
|
||||||
|
#[command(name = "cd", subcommand_value_name = ".cd")]
|
||||||
|
Cwd(CwdArgs),
|
||||||
|
/// Display information about settings
|
||||||
|
#[command(name = "show")]
|
||||||
|
ShowInfo,
|
||||||
|
/// Set the value of NULL to be printed in 'list' mode
|
||||||
|
#[command(name = "nullvalue", subcommand_value_name = ".nullvalue")]
|
||||||
|
NullValue(NullValueArgs),
|
||||||
|
/// Toggle 'echo' mode to repeat commands before execution
|
||||||
|
#[command(subcommand_value_name = ".echo")]
|
||||||
|
Echo(EchoArgs),
|
||||||
|
/// Display tables
|
||||||
|
#[command(subcommand_value_name = ".tables")]
|
||||||
|
Tables(TablesArgs),
|
||||||
|
/// Import data from FILE into TABLE
|
||||||
|
#[command(subcommand_value_name = ".import")]
|
||||||
|
Import(ImportArgs),
|
||||||
|
/// Loads an extension library
|
||||||
|
#[command(name = "load", subcommand_value_name = ".load")]
|
||||||
|
LoadExtension(LoadExtensionArgs),
|
||||||
|
/// Dump the current database as a list of SQL statements
|
||||||
|
#[command(subcommand_value_name = ".dump")]
|
||||||
|
Dump,
|
||||||
|
/// List vfs modules available
|
||||||
|
#[command(name = "listvfs", subcommand_value_name = ".dump")]
|
||||||
|
ListVfs,
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl Command {
|
||||||
|
// pub fn min_args(&self) -> usize {
|
||||||
|
// 1 + match self {
|
||||||
|
// Self::Exit
|
||||||
|
// | Self::Quit
|
||||||
|
// | Self::Schema
|
||||||
|
// | Self::Help
|
||||||
|
// | Self::Opcodes
|
||||||
|
// | Self::ShowInfo
|
||||||
|
// | Self::Tables
|
||||||
|
// | Self::SetOutput
|
||||||
|
// | Self::Dump => 0,
|
||||||
|
// Self::Open
|
||||||
|
// | Self::OutputMode
|
||||||
|
// | Self::Cwd
|
||||||
|
// | Self::Echo
|
||||||
|
// | Self::NullValue
|
||||||
|
// | Self::LoadExtension => 1,
|
||||||
|
// Self::Import => 2,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn usage(&self) -> &str {
|
||||||
|
// match self {
|
||||||
|
// Self::Exit => ".exit ?<CODE>",
|
||||||
|
// Self::Quit => ".quit",
|
||||||
|
// Self::Open => ".open <file>",
|
||||||
|
// Self::Help => ".help",
|
||||||
|
// Self::Schema => ".schema ?<table>?",
|
||||||
|
// Self::Opcodes => ".opcodes",
|
||||||
|
// Self::OutputMode => ".mode list|pretty",
|
||||||
|
// Self::SetOutput => ".output ?file?",
|
||||||
|
// Self::Cwd => ".cd <directory>",
|
||||||
|
// Self::ShowInfo => ".show",
|
||||||
|
// Self::NullValue => ".nullvalue <string>",
|
||||||
|
// Self::Echo => ".echo on|off",
|
||||||
|
// Self::Tables => ".tables",
|
||||||
|
// Self::LoadExtension => ".load",
|
||||||
|
// Self::Dump => ".dump",
|
||||||
|
// Self::Import => &IMPORT_HELP,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl FromStr for Command {
|
||||||
|
// type Err = String;
|
||||||
|
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
// match s {
|
||||||
|
// ".exit" => Ok(Self::Exit),
|
||||||
|
// ".quit" => Ok(Self::Quit),
|
||||||
|
// ".open" => Ok(Self::Open),
|
||||||
|
// ".help" => Ok(Self::Help),
|
||||||
|
// ".schema" => Ok(Self::Schema),
|
||||||
|
// ".tables" => Ok(Self::Tables),
|
||||||
|
// ".opcodes" => Ok(Self::Opcodes),
|
||||||
|
// ".mode" => Ok(Self::OutputMode),
|
||||||
|
// ".output" => Ok(Self::SetOutput),
|
||||||
|
// ".cd" => Ok(Self::Cwd),
|
||||||
|
// ".show" => Ok(Self::ShowInfo),
|
||||||
|
// ".nullvalue" => Ok(Self::NullValue),
|
||||||
|
// ".echo" => Ok(Self::Echo),
|
||||||
|
// ".import" => Ok(Self::Import),
|
||||||
|
// ".load" => Ok(Self::LoadExtension),
|
||||||
|
// ".dump" => Ok(Self::Dump),
|
||||||
|
// _ => Err("Unknown command".to_string()),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
10
cli/input.rs
10
cli/input.rs
@@ -232,3 +232,13 @@ Usage Examples:
|
|||||||
Note:
|
Note:
|
||||||
- All SQL commands must end with a semicolon (;).
|
- All SQL commands must end with a semicolon (;).
|
||||||
- Special commands do not require a semicolon."#;
|
- Special commands do not require a semicolon."#;
|
||||||
|
|
||||||
|
pub const BEFORE_HELP_MSG: &str = r#"
|
||||||
|
|
||||||
|
Limbo SQL Shell Help
|
||||||
|
==============
|
||||||
|
Welcome to the Limbo SQL Shell! You can execute any standard SQL command here.
|
||||||
|
In addition to standard SQL commands, the following special commands are available:"#;
|
||||||
|
pub const AFTER_HELP_MSG: &str = r#"Note:
|
||||||
|
- All SQL commands must end with a semicolon (;).
|
||||||
|
- Special commands start with a dot and do not require a semicolon."#;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#![allow(clippy::arc_with_non_send_sync)]
|
#![allow(clippy::arc_with_non_send_sync)]
|
||||||
mod app;
|
mod app;
|
||||||
mod helper;
|
mod helper;
|
||||||
mod import;
|
|
||||||
mod input;
|
mod input;
|
||||||
mod opcodes_dictionary;
|
mod opcodes_dictionary;
|
||||||
|
mod commands;
|
||||||
|
|
||||||
use rustyline::{error::ReadlineError, Config, Editor};
|
use rustyline::{error::ReadlineError, Config, Editor};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|||||||
Reference in New Issue
Block a user