use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReplCommand {
Load {
path: String,
},
Datasets,
Use {
name: String,
},
Info,
Head {
n: usize,
},
Schema,
QualityCheck,
QualityScore {
suggest: bool,
json: bool,
badge: bool,
},
DriftDetect {
reference: String,
},
Convert {
format: String,
},
Export {
what: String,
json: bool,
},
Validate {
schema: String,
},
History {
export: bool,
},
Help {
topic: Option<String>,
},
Quit,
}
pub struct CommandParser;
impl CommandParser {
pub fn parse(input: &str) -> Result<ReplCommand> {
let input = input.trim();
if input.is_empty() {
return Err(Error::parse("Empty command"));
}
let parts: Vec<&str> = input.split_whitespace().collect();
let cmd = parts[0].to_lowercase();
let args = &parts[1..];
match cmd.as_str() {
"load" => Self::parse_load(args),
"datasets" => Ok(ReplCommand::Datasets),
"use" => Self::parse_use(args),
"info" => Ok(ReplCommand::Info),
"head" => Self::parse_head(args),
"schema" => Ok(ReplCommand::Schema),
"quality" => Self::parse_quality(args),
"drift" => Self::parse_drift(args),
"convert" => Self::parse_convert(args),
"export" => Self::parse_export(args),
"validate" => Self::parse_validate(args),
"history" => Ok(Self::parse_history(args)),
"help" | "?" => Ok(Self::parse_help(args)),
"quit" | "exit" | "q" => Ok(ReplCommand::Quit),
_ => Err(Error::parse(format!("Unknown command: '{}'", cmd))),
}
}
fn parse_load(args: &[&str]) -> Result<ReplCommand> {
if args.is_empty() {
return Err(Error::parse("load requires a file path"));
}
Ok(ReplCommand::Load {
path: args[0].to_string(),
})
}
fn parse_use(args: &[&str]) -> Result<ReplCommand> {
if args.is_empty() {
return Err(Error::parse("use requires a dataset name"));
}
Ok(ReplCommand::Use {
name: args[0].to_string(),
})
}
fn parse_head(args: &[&str]) -> Result<ReplCommand> {
let n = if args.is_empty() {
10 } else {
args[0]
.parse()
.map_err(|_| Error::parse(format!("Invalid number: '{}'", args[0])))?
};
Ok(ReplCommand::Head { n })
}
fn parse_quality(args: &[&str]) -> Result<ReplCommand> {
if args.is_empty() {
return Err(Error::parse("quality requires subcommand: check, score"));
}
match args[0].to_lowercase().as_str() {
"check" => Ok(ReplCommand::QualityCheck),
"score" => {
let suggest = args.contains(&"--suggest");
let json = args.contains(&"--json");
let badge = args.contains(&"--badge");
Ok(ReplCommand::QualityScore {
suggest,
json,
badge,
})
}
_ => Err(Error::parse(format!(
"Unknown quality subcommand: '{}'. Use: check, score",
args[0]
))),
}
}
fn parse_drift(args: &[&str]) -> Result<ReplCommand> {
if args.is_empty() {
return Err(Error::parse("drift requires subcommand: detect"));
}
match args[0].to_lowercase().as_str() {
"detect" => {
if args.len() < 2 {
return Err(Error::parse("drift detect requires a reference file"));
}
Ok(ReplCommand::DriftDetect {
reference: args[1].to_string(),
})
}
_ => Err(Error::parse(format!(
"Unknown drift subcommand: '{}'. Use: detect",
args[0]
))),
}
}
fn parse_convert(args: &[&str]) -> Result<ReplCommand> {
if args.is_empty() {
return Err(Error::parse(
"convert requires a format: csv, parquet, json",
));
}
let format = args[0].to_lowercase();
match format.as_str() {
"csv" | "parquet" | "json" => Ok(ReplCommand::Convert { format }),
_ => Err(Error::parse(format!(
"Unknown format: '{}'. Use: csv, parquet, json",
args[0]
))),
}
}
fn parse_export(args: &[&str]) -> Result<ReplCommand> {
if args.is_empty() {
return Err(Error::parse("export requires what to export: quality"));
}
let what = args[0].to_lowercase();
let json = args.iter().any(|f| *f == "--json" || *f == "-j");
Ok(ReplCommand::Export { what, json })
}
fn parse_validate(args: &[&str]) -> Result<ReplCommand> {
let schema_idx = args.iter().position(|f| *f == "--schema" || *f == "-s");
let schema = if let Some(idx) = schema_idx {
if idx + 1 < args.len() {
args[idx + 1].to_string()
} else {
return Err(Error::parse("--schema requires a file path"));
}
} else {
return Err(Error::parse("validate requires --schema <file>"));
};
Ok(ReplCommand::Validate { schema })
}
fn parse_history(args: &[&str]) -> ReplCommand {
let export = args.iter().any(|f| *f == "--export" || *f == "-e");
ReplCommand::History { export }
}
fn parse_help(args: &[&str]) -> ReplCommand {
let topic = args.first().map(|s| (*s).to_string());
ReplCommand::Help { topic }
}
#[must_use]
pub fn command_names() -> Vec<&'static str> {
vec![
"load", "datasets", "use", "info", "head", "schema", "quality", "drift", "convert",
"export", "validate", "history", "help", "quit", "exit",
]
}
#[must_use]
pub fn subcommands(command: &str) -> Vec<&'static str> {
match command {
"quality" => vec!["check", "score"],
"drift" => vec!["detect"],
_ => vec![],
}
}
#[must_use]
pub fn flags(command: &str, subcommand: Option<&str>) -> Vec<&'static str> {
match (command, subcommand) {
("quality", Some("score")) => vec!["--suggest", "--json", "--badge"],
("export", _) => vec!["--json"],
("validate", _) => vec!["--schema"],
("history", _) => vec!["--export"],
_ => vec![],
}
}
}