pub mod cat;
pub mod columns;
pub mod date;
pub mod describe;
pub mod diff;
pub mod dropna;
pub mod eval;
pub mod fillna;
pub mod head;
pub mod help;
pub mod merge;
pub mod open;
pub mod query;
pub mod rename;
pub mod rolling;
pub mod round;
pub mod shape;
pub mod show;
pub mod slice;
pub mod slugify;
pub mod sort;
pub mod statistics;
pub mod strip;
pub mod tail;
use crate::{cli::Invocation, error::PhError, io};
pub fn validate(inv: &Invocation) -> Result<(), PhError> {
match inv.command.as_str() {
"open" => {
inv.arity(2, 2)?;
if inv.args[0] != "excel" {
return Err(PhError::new(format!(
"Unknown open format: {}.",
inv.args[0]
)));
}
if let Some((key, _)) = inv
.kwargs
.iter()
.find(|(key, _)| !matches!(key.as_str(), "sheet" | "sheet_name"))
{
return Err(PhError::new(format!("Unknown option --{key}.")));
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"cat" => {
for (key, _) in &inv.kwargs {
if key != "axis" {
return Err(PhError::new(format!("Unknown option --{key}.")));
}
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"merge" => {
inv.arity(2, 2)?;
for (key, _) in &inv.kwargs {
if !["how", "on", "left", "right"].contains(&key.as_str()) {
return Err(PhError::new(format!("Unknown option --{key}.")));
}
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"sum" | "mean" | "median" | "std" | "min" | "max" => {
for (key, _) in &inv.kwargs {
if !["axis", "skipna", "numeric_only", "ddof", "min_count"].contains(&key.as_str())
{
return Err(PhError::new(format!("Unknown option --{key}.")));
}
if key == "ddof" && inv.command != "std" {
return Err(PhError::new("--ddof is only supported for std."));
}
if key == "min_count" && inv.command != "sum" {
return Err(PhError::new("--min_count is only supported for sum."));
}
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"rolling" => {
inv.arity(1, usize::MAX)?;
for (key, _) in &inv.kwargs {
if !["how", "min_periods", "center", "ddof"].contains(&key.as_str()) {
return Err(PhError::new(format!("Unknown option --{key}.")));
}
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"dropna" => {
inv.arity(0, 0)?;
for (key, _) in &inv.kwargs {
if !["axis", "how", "thresh"].contains(&key.as_str()) {
return Err(PhError::new(format!("Unknown option --{key}.")));
}
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"fillna" => {
inv.arity(0, 1)?;
for (key, _) in &inv.kwargs {
if !["method", "limit"].contains(&key.as_str()) {
return Err(PhError::new(format!("Unknown option --{key}.")));
}
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"help" => {
inv.no_options()?;
inv.arity(0, 1)
}
"round" => {
inv.arity(1, 2)?;
if let Some((key, _)) = inv.kwargs.iter().find(|(key, _)| key != "decimals") {
return Err(PhError::new(format!("Unknown option --{key}.")));
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"columns" => inv.no_options(),
"date" => {
inv.arity(1, 1)?;
if let Some((key, _)) = inv
.kwargs
.iter()
.find(|(key, _)| !matches!(key.as_str(), "format" | "dayfirst" | "utc"))
{
return Err(PhError::new(format!("Unknown option --{key}.")));
}
if let Some(key) = inv
.flags
.iter()
.find(|key| !matches!(key.as_str(), "dayfirst" | "utc"))
{
return Err(PhError::new(format!("Unknown flag --{key}.")));
}
Ok(())
}
"describe" => {
inv.no_options()?;
inv.arity(0, 0)
}
"diff" => {
if let Some((key, _)) = inv
.kwargs
.iter()
.find(|(key, _)| key != "periods" && key != "axis")
{
return Err(PhError::new(format!("Unknown option --{key}.")));
}
if let Some(flag) = inv.flags.first() {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"head" | "tail" => {
inv.no_options()?;
inv.arity(0, 1)
}
"rename" => {
inv.no_options()?;
inv.arity(2, 2)
}
"sort" => {
inv.arity(1, 1)?;
Ok(())
}
"show" => {
inv.arity(0, 0)?;
if let Some((key, _)) = inv.kwargs.first() {
return Err(PhError::new(format!("Unknown option --{key}.")));
}
if let Some(flag) = inv.flags.iter().find(|flag| flag.as_str() != "no-index") {
return Err(PhError::new(format!("Unknown flag --{flag}.")));
}
Ok(())
}
"slice" => {
inv.no_options()?;
inv.arity(1, 1)
}
"shape" => {
inv.no_options()?;
inv.arity(0, 0)
}
"query" | "eval" => {
inv.no_options()?;
inv.arity(1, 1)
}
"strip" => inv.no_options(),
"slugify" => {
inv.no_options()?;
inv.arity(0, 0)
}
_ => Err(PhError::new(format!(
"Unknown command {}.\nUsage: ph command [args]",
inv.command
))),
}
}
pub fn execute(inv: &Invocation, data: &[u8]) -> Result<Vec<u8>, PhError> {
if inv.command == "help" {
return help::run(inv);
}
if inv.command == "open" {
return open::run(inv);
}
if inv.command == "cat" {
return cat::run(inv, data);
}
if inv.command == "merge" {
return merge::run(inv);
}
let df = io::read_csv(data)?;
match inv.command.as_str() {
"sum" | "mean" | "median" | "std" | "min" | "max" => statistics::run(df, inv),
"rolling" => rolling::run(df, inv),
"round" => round::run(df, inv),
"dropna" => dropna::run(df, inv),
"fillna" => fillna::run(df, inv),
"columns" => columns::run(df, inv),
"date" => date::run(df, inv),
"describe" => describe::run(df),
"diff" => diff::run(df, inv),
"head" => head::run(df, inv),
"tail" => tail::run(df, inv),
"rename" => rename::run(df, inv),
"sort" => sort::run(df, inv),
"slice" => slice::run(df, inv),
"shape" => shape::run(df),
"show" => show::run(df, inv),
"query" => query::run(df, inv),
"eval" => eval::run(df, inv),
"strip" => strip::run(df, inv),
"slugify" => slugify::run(df),
_ => unreachable!("validated before CSV input"),
}
}
pub fn count_arg(inv: &Invocation, default: i64) -> Result<i64, PhError> {
match inv.args.first() {
None => Ok(default),
Some(v) => v
.parse::<i64>()
.map_err(|_| PhError::new(format!("Invalid row count: {v}."))),
}
}