use std::env;
use std::fs;
use toml::{Value, de::Error};
use std::process::exit;
use toml::value::Table;
use tokio::spawn;
use futures::FutureExt;
use futures::future::BoxFuture;
use std::collections::HashMap;
#[derive(Debug)]
struct Command {
arguments: Vec<String>,
shell: String,
env: HashMap<String, String>,
args: HashMap<String, String>,
}
fn find_command(command: Vec<&str>, table: &Table) -> Option<Value> {
let mut result: Option<Value> = Option::None;
for (k, v) in table {
if k == command[0] {
match v {
Value::String(_) => {
result = Option::Some(v.clone());
break;
}
Value::Integer(_) => {}
Value::Float(_) => {}
Value::Boolean(_) => {}
Value::Datetime(_) => {}
Value::Array(_) => {
result = Option::Some(v.clone());
break;
}
Value::Table(t) => {
if command.len() == 1 {
result = Option::Some(v.clone());
break;
} else if command.len() > 1 {
result = find_command(Vec::from(&command[1..]), t);
}
}
}
}
}
result
}
#[derive(Debug)]
enum VecOrCommand {
Vec(Vec<VecOrCommand>),
Com(Command),
}
fn create_command_chain(value: Value, inherited: Option<HashMap<String, HashMap<String, String>>>) -> Vec<VecOrCommand> {
let mut commands: Vec<VecOrCommand> = vec![];
let mut parent_args: HashMap<String, String> = HashMap::new();
let mut parent_env: HashMap<String, String> = HashMap::new();
if inherited.is_some() {
let parent = inherited.unwrap();
if parent.contains_key("args") {
for (k, v) in parent.get("args").unwrap() {
parent_args.insert(k.clone(), v.clone());
}
}
if parent.contains_key("env") {
for (k, v) in parent.get("env").unwrap() {
parent_env.insert(k.clone(), v.clone());
}
}
}
match value {
Value::String(s) => {
commands.push(VecOrCommand::Com(Command {
arguments: vec![s.clone()],
shell: "".to_string(),
env: parent_env.clone(),
args: parent_args.clone(),
}))
}
Value::Integer(_) => {}
Value::Float(_) => {}
Value::Boolean(_) => {}
Value::Datetime(_) => {}
Value::Array(a) => {
for x in a {
let mut send_inherit: HashMap<String, HashMap<String, String>> = HashMap::new();
send_inherit.insert("env".to_string(), parent_env.clone());
send_inherit.insert("args".to_string(), parent_args.clone());
for n in create_command_chain(x, Option::Some(send_inherit)) {
commands.push(n)
}
}
}
Value::Table(t) => {
if t.contains_key("cmd") {
let mut shell: String = "".to_string();
if t.contains_key("shell") {
shell = t.get("shell").unwrap().to_string();
}
let mut parallel: bool = false;
if t.contains_key("parallel") {
parallel = t.get("parallel").unwrap().as_bool().unwrap();
}
let mut env: HashMap<String, String> = HashMap::new();
if t.contains_key("env") {
for x in t.get("env").unwrap().as_array().unwrap() {
match x {
Value::String(s) => {
let y: Vec<&str> = s.split("=").collect();
env.insert(y[0].to_string(), y[1].to_string());
}
Value::Integer(_) => {}
Value::Float(_) => {}
Value::Boolean(_) => {}
Value::Datetime(_) => {}
Value::Array(_) => {}
Value::Table(_) => {}
}
}
}
for (k, v) in parent_env.clone() {
env.insert(k, v);
}
let mut args: HashMap<String, String> = HashMap::new();
if t.contains_key("args") {
for x in t.get("args").unwrap().as_array().unwrap() {
let values: Vec<&str> = x.as_str().unwrap().split("=").collect();
if values.len() == 2 {
args.insert(values[0].to_string(), values[1].to_string());
} else {
args.insert(values[0].to_string(), "".to_string());
}
}
}
for (k, v) in parent_args.clone() {
args.insert(k, v);
}
match t.get("cmd").unwrap() {
Value::String(s) => {
commands.push(VecOrCommand::Com(Command {
arguments: vec![s.clone()],
shell: shell,
env: env,
args: args,
}))
}
Value::Integer(_) => {}
Value::Float(_) => {}
Value::Boolean(_) => {}
Value::Datetime(_) => {}
Value::Array(a) => {
let mut sub_commands: Vec<VecOrCommand> = vec![];
for x in a {
let mut send_inherit: HashMap<String, HashMap<String, String>> = HashMap::new();
let mut temp_args: HashMap<String, String> = HashMap::new();
for (k, v) in parent_args.clone() {
temp_args.insert(k, v);
}
for (k, v) in args.clone() {
temp_args.insert(k, v);
}
let mut temp_envs: HashMap<String, String> = HashMap::new();
for (k, v) in parent_env.clone() {
temp_envs.insert(k, v);
}
for (k, v) in env.clone() {
temp_envs.insert(k, v);
}
send_inherit.insert("args".to_string(), temp_args.clone());
send_inherit.insert("env".to_string(), temp_envs.clone());
for n in create_command_chain(x.clone(), Option::Some(send_inherit)) {
if parallel {
sub_commands.push(n)
} else {
commands.push(n)
}
}
}
for n in sub_commands.iter_mut() {
match n {
VecOrCommand::Vec(_) => {}
VecOrCommand::Com(c) => {
c.shell = shell.clone();
}
}
}
if parallel {
commands.push(VecOrCommand::Vec(sub_commands))
}
for n in commands.iter_mut() {
match n {
VecOrCommand::Vec(_) => {}
VecOrCommand::Com(c) => {
c.shell = shell.clone()
}
}
}
}
Value::Table(_) => {}
}
} else {
for (_, v) in t {
let mut send_inherit: HashMap<String, HashMap<String, String>> = HashMap::new();
send_inherit.insert("env".to_string(), parent_env.clone());
send_inherit.insert("args".to_string(), parent_args.clone());
for n in create_command_chain(v, Option::Some(send_inherit)) {
commands.push(n)
}
}
}
}
}
commands
}
fn run_commands(command: VecOrCommand) -> BoxFuture<'static, ()> {
async move {
let mut handlers = vec![];
match command {
VecOrCommand::Vec(v) => {
for c in v {
handlers.push(spawn(run_commands(c)))
}
futures::future::join_all(handlers).await;
}
VecOrCommand::Com(mut c) => {
let program: &str;
c.shell = c.shell.strip_prefix("\"").unwrap_or_else(|| &c.shell).strip_suffix("\"").unwrap_or_else(|| &c.shell).to_string();
let mut shell: Vec<&str> = c.shell.split(" ").collect();
if c.shell == "".to_string() {
if cfg!(target_os = "windows") {
shell = Vec::from(["cmd", "/C"])
} else {
shell = Vec::from(["sh", "-c"])
}
}
program = shell[0];
shell.remove(0);
let args: Vec<String> = env::args().collect();
let mut arguments_index = 2;
if args.len() >= 3 && args[1] == "cmd" {
arguments_index = 3;
}
if args.len() >= arguments_index {
let mut args_map: HashMap<String, String> = HashMap::new();
for i in arguments_index..args.len() {
let values: Vec<&str> = args[i].split("=").collect();
args_map.insert(values[0].to_string(), values[1].to_string());
}
for i in 0..c.arguments.len() {
let mut new_arg = c.arguments[i].clone();
for (k, v) in &c.args {
if args_map.contains_key(k) {
let key = format!("{}{}", "$", k);
new_arg = new_arg.replace(key.as_str(), args_map.get(k).unwrap().as_str())
} else {
let key = format!("{}{}", "$", k);
new_arg = new_arg.replace(key.as_str(), v.as_str())
}
}
c.arguments[i] = new_arg
}
}
for n in shell.iter() {
c.arguments.insert(0, n.to_string());
}
let child_process = std::process::Command::new(program)
.args(c.arguments)
.envs(c.env)
.spawn()
.expect("failed to execute process");
let _ = child_process.wait_with_output();
}
}
}.boxed()
}
async fn execute_command(command: &Value) {
let commands: Vec<VecOrCommand> = create_command_chain(command.clone(), Option::None);
for n in commands {
run_commands(n).await;
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let args: Vec<String> = env::args().collect();
if args.len() == 1 {
println!("No command was given.");
exit(0)
}
if args.len() == 2 && args[1] == "cmd" {
println!("No command was given.");
exit(0)
}
let mut command_index = 1;
if args.len() >= 3 && args[1] == "cmd" {
command_index = 2;
}
let run_command: Vec<&str> = args[command_index].as_str().split(".").collect();
let mut using_cargo: bool = false;
let mut cargo_path = env::current_dir().unwrap();
let mut previous: bool = true;
while previous {
cargo_path.push("Cargo.toml");
if fs::metadata(&cargo_path).is_ok() {
break;
} else {
cargo_path.pop();
if !cargo_path.pop() {
previous = false;
cargo_path.push("Cargo.toml");
}
}
}
if fs::metadata(&cargo_path).is_ok() {
let cargo_toml_file: String = fs::read_to_string(&cargo_path).expect("Something went wrong reading the file");
let cargo_toml: Value = toml::from_str(&cargo_toml_file)?;
if cargo_toml.as_table().unwrap().contains_key("commands") {
let command = find_command(run_command.clone(), cargo_toml.as_table().unwrap().get("commands").unwrap().as_table().unwrap());
if command.is_some() {
using_cargo = true;
execute_command(&command.unwrap()).await
}
}
}
let mut using_commands: bool = false;
if !using_cargo {
let mut commands_path = env::current_dir().unwrap();
let mut previous: bool = true;
while previous {
commands_path.push("Commands.toml");
if fs::metadata(&commands_path).is_ok() {
break;
} else {
commands_path.pop();
if !commands_path.pop() {
previous = false;
commands_path.push("Commands.toml");
}
}
}
if fs::metadata(&commands_path).is_ok() {
let commands_toml_file: String = fs::read_to_string(&commands_path).expect("Something went wrong reading the file");
let commands_toml: Value = toml::from_str(&commands_toml_file)?;
let command = find_command(run_command.clone(), commands_toml.as_table().unwrap());
if command.is_some() {
using_commands = true;
execute_command(&command.unwrap()).await
}
}
}
if !using_cargo && !using_commands {
println!("No command found!")
}
Ok(())
}