use clap::{Arg, ArgAction, Command};
use mlua::prelude::*;
use std::sync::{Arc, Mutex};
use crate::ops::std::create_std_module;
#[derive(Clone)]
pub struct LuaClapCommand {
pub command: Arc<Mutex<Command>>,
}
impl LuaClapCommand {
pub fn new(name: String, version: Option<String>, about: Option<String>) -> Self {
let name = leak_static(name);
let mut cmd = Command::new(name);
if let Some(v) = version {
let v = leak_static(v);
cmd = cmd.version(v);
}
if let Some(a) = about {
cmd = cmd.about(a);
}
Self {
command: Arc::new(Mutex::new(cmd)),
}
}
}
#[derive(Clone)]
pub struct LuaClapSubcommand {
pub command: Arc<Mutex<Command>>,
}
impl LuaClapSubcommand {
pub fn new(name: String, version: Option<String>, about: Option<String>) -> Self {
let name = leak_static(name);
let mut cmd = Command::new(name);
if let Some(v) = version {
let v = leak_static(v);
cmd = cmd.version(v);
}
if let Some(a) = about {
cmd = cmd.about(a);
}
Self {
command: Arc::new(Mutex::new(cmd)),
}
}
}
fn leak_static(s: String) -> &'static str {
Box::leak(s.into_boxed_str())
}
impl mlua::UserData for LuaClapSubcommand {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method(
"arg",
|_, this, (name, opts): (String, Option<mlua::Table>)| {
let mut cmd = this.command.lock().unwrap();
let name_static = leak_static(name);
let mut arg = Arg::new(name_static);
if let Some(table) = opts {
if let Ok(short) = table.get::<String>("short") {
let s = short.trim_start_matches('-');
if let Some(ch) = s.chars().next() {
arg = arg.short(ch);
}
}
if let Ok(long) = table.get::<String>("long") {
let long_static: &'static str =
Box::leak(long.trim_start_matches('-').to_string().into_boxed_str());
arg = arg.long(long_static);
}
if let Ok(help) = table.get::<String>("help") {
arg = arg.help(help);
}
if let Ok(default) = table.get::<String>("default") {
let default = leak_static(default);
arg = arg.default_value(default);
}
if let Ok(required) = table.get::<bool>("required") {
arg = arg.required(required);
}
if let Ok(num_args) = table.get::<usize>("num_args") {
arg = arg
.num_args(1..num_args)
.value_parser(clap::value_parser!(String));
}
if let Ok(trailing) = table.get::<bool>("trailing") {
if trailing {
arg = arg
.trailing_var_arg(true)
.num_args(1..)
.value_parser(clap::value_parser!(String));
}
}
}
*cmd = cmd.clone().arg(arg);
Ok(this.clone())
},
);
methods.add_method(
"flag",
|_, this, (name, opts): (String, Option<mlua::Table>)| {
let mut cmd = this.command.lock().unwrap();
let name_static = leak_static(name);
let mut arg = Arg::new(name_static).action(ArgAction::SetTrue);
if let Some(table) = opts {
if let Ok(short) = table.get::<String>("short") {
let s = short.trim_start_matches('-');
if let Some(ch) = s.chars().next() {
arg = arg.short(ch);
}
}
if let Ok(long) = table.get::<String>("long") {
let long_static: &'static str =
Box::leak(long.trim_start_matches('-').to_string().into_boxed_str());
arg = arg.long(long_static);
}
if let Ok(help) = table.get::<String>("help") {
arg = arg.help(help);
}
}
*cmd = cmd.clone().arg(arg);
Ok(this.clone())
},
);
}
}
impl mlua::UserData for LuaClapCommand {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method(
"arg",
|_, this, (name, opts): (String, Option<mlua::Table>)| {
let mut cmd = this.command.lock().unwrap();
let name_static = leak_static(name);
let mut arg = Arg::new(name_static);
if let Some(table) = opts {
if let Ok(short) = table.get::<String>("short") {
let s = short.trim_start_matches('-');
if let Some(ch) = s.chars().next() {
arg = arg.short(ch);
}
}
if let Ok(long) = table.get::<String>("long") {
let long_static: &'static str =
Box::leak(long.trim_start_matches('-').to_string().into_boxed_str());
arg = arg.long(long_static);
}
if let Ok(help) = table.get::<String>("help") {
arg = arg.help(help);
}
if let Ok(default) = table.get::<String>("default") {
let default = leak_static(default);
arg = arg.default_value(default);
}
if let Ok(required) = table.get::<bool>("required") {
arg = arg.required(required);
}
if let Ok(num_args) = table.get::<usize>("num_args") {
arg = arg
.num_args(1..num_args)
.value_parser(clap::value_parser!(String));
}
if let Ok(trailing) = table.get::<bool>("trailing") {
if trailing {
arg = arg
.trailing_var_arg(true)
.num_args(1..)
.value_parser(clap::value_parser!(String));
}
}
}
*cmd = cmd.clone().arg(arg);
Ok(this.clone())
},
);
methods.add_method(
"flag",
|_, this, (name, opts): (String, Option<mlua::Table>)| {
let mut cmd = this.command.lock().unwrap();
let name_static = leak_static(name);
let mut arg = Arg::new(name_static).action(ArgAction::SetTrue);
if let Some(table) = opts {
if let Ok(short) = table.get::<String>("short") {
let s = short.trim_start_matches('-');
if let Some(ch) = s.chars().next() {
arg = arg.short(ch);
}
}
if let Ok(long) = table.get::<String>("long") {
let long_static: &'static str =
Box::leak(long.trim_start_matches('-').to_string().into_boxed_str());
arg = arg.long(long_static);
}
if let Ok(help) = table.get::<String>("help") {
arg = arg.help(help);
}
}
*cmd = cmd.clone().arg(arg);
Ok(this.clone())
},
);
methods.add_method("subcommand", |_, this, sub_ud: mlua::AnyUserData| {
let sub = sub_ud.borrow::<LuaClapSubcommand>()?;
let sub_cmd = sub.command.lock().unwrap().clone();
let mut cmd = this.command.lock().unwrap();
*cmd = cmd.clone().subcommand(sub_cmd);
Ok(this.clone())
});
methods.add_method(
"parse",
|lua, this, (mut arg_vec, name): (Vec<String>, Option<String>)| {
let cmd = this.command.lock().unwrap().clone();
arg_vec.insert(0, name.unwrap_or(format!("lulu_command")));
let matches = cmd
.try_get_matches_from(arg_vec)
.map_err(LuaError::external)?;
fn extract<'a>(lua: &'a Lua, m: &clap::ArgMatches) -> mlua::Result<mlua::Table> {
let tbl = lua.create_table()?;
for id in m.ids() {
let key = id.as_str();
if key.ends_with("*") {
if let Ok(Some(iter)) = m.try_get_many::<String>(key) {
let arr = lua.create_table()?;
for (i, val) in iter.enumerate() {
arr.set(i + 1, val.clone())?;
}
tbl.set(key.trim_end_matches("*"), arr)?;
}
continue;
}
if let Ok(Some(v)) = m.try_get_one::<String>(key) {
tbl.set(key, v.clone())?;
} else if let Ok(Some(iter)) = m.try_get_many::<String>(key) {
let arr = lua.create_table()?;
for (i, val) in iter.enumerate() {
arr.set(i + 1, val.clone())?;
}
tbl.set(key, arr)?;
} else if m.get_flag(key) {
tbl.set(key, true)?;
} else {
tbl.set(key, mlua::Value::Nil)?;
}
}
if let Some((sub_name, sub_m)) = m.subcommand() {
let sub_tbl = extract(lua, sub_m)?;
tbl.set(sub_name.to_string(), sub_tbl)?;
}
Ok(tbl)
}
extract(lua, &matches)
},
);
}
}
pub fn into_module() {
create_std_module("clap")
.add_function("Command", |_, opts: mlua::Table| {
let name: String = opts.get("name")?;
let version: Option<String> = opts.get("version").ok();
let about: Option<String> = opts.get("about").ok();
Ok(LuaClapCommand::new(name, version, about))
})
.add_function("Subcommand", |_, opts: mlua::Table| {
let name: String = opts.get("name")?;
let version: Option<String> = opts.get("version").ok();
let about: Option<String> = opts.get("about").ok();
Ok(LuaClapSubcommand::new(name, version, about))
})
.on_register(|_, clap_mod| Ok(clap_mod))
.into();
}