use std::collections::VecDeque;
use std::fmt::{Display, Error as FmtError};
use std::io::Error as IoError;
use std::sync::Arc;
use std::{error, process};
use clap::Command;
use mlua::{Error as LuaError, FromLua, Function, Lua, Table, UserData, UserDataMethods, Value};
#[derive(Debug, Clone)]
pub struct Module {
pub description: Option<String>,
pub name: String,
steps: VecDeque<Step>,
}
impl Module {
#[must_use]
pub fn make_command(&self) -> Command {
self.description.as_ref().map_or_else(
|| Command::new(&self.name),
|description| Command::new(&self.name).about(description),
)
}
#[must_use]
pub const fn new() -> Self {
Self {
description: None,
name: String::new(),
steps: VecDeque::new(),
}
}
#[must_use]
pub fn with_name(name: String) -> Self {
Self {
name,
..Self::new()
}
}
pub fn apply_through_lua(self, func: &Function) -> Result<Self, LuaError> {
func.call::<Self>(self)
}
pub fn add_sh_step(&mut self, sh_str: String) {
self.steps.push_front(Step::Sh(sh_str));
}
pub fn set_description(&mut self, description: String) {
self.description = Some(description);
}
pub fn provide_description(&mut self, description: String) -> bool {
if self.description.is_none() {
self.description = Some(description);
true
} else {
false
}
}
pub fn execute(self) -> Result<(), IoError> {
for step in self.steps {
step.execute()?;
}
Ok(())
}
}
impl Default for Module {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ModuleFromLuaError(pub &'static str);
impl From<Value> for ModuleFromLuaError {
fn from(found: Value) -> Self {
Self(found.type_name())
}
}
impl error::Error for ModuleFromLuaError {}
impl Display for ModuleFromLuaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), FmtError> {
write!(f, "Expected a builder.Module found a {}", self.0)
}
}
impl FromLua for Module {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self, LuaError> {
match value {
Value::UserData(any_user_data) => any_user_data.take(),
found => Err(LuaError::ExternalError(Arc::new(ModuleFromLuaError(
found.type_name(),
)))),
}
}
}
impl UserData for Module {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("add_sh_step", |_lua, module, shell: String| {
module.add_sh_step(shell);
Ok(())
});
methods.add_method_mut(
"provide_description",
|_lua, module, description: String| {
module
.provide_description(description)
.ok_or(LuaError::external(
"Called provide_description on a Module with a description",
))
},
);
}
}
#[derive(Debug, Clone)]
pub enum Step {
Sh(String),
}
impl Step {
pub fn execute(&self) -> Result<(), IoError> {
match self {
Self::Sh(script) => {
let _exit = process::Command::new("sh")
.arg("-c")
.arg(script)
.spawn()?
.wait()?;
Ok(())
}
}
}
}
pub fn collect_modules_from_table(table: &Table) -> Vec<Module> {
fn collect_module((key, func): (String, Function)) -> Option<Module> {
func.call::<Module>(Module::with_name(key)).ok()
}
let mut res = table
.pairs::<String, Function>()
.flatten()
.filter_map(collect_module)
.collect::<Vec<Module>>();
res.sort_by(|a, b| a.name.cmp(&b.name));
res
}