cmd-runner 0.0.2

A simple task cmd runner
Documentation
//! 分析命令行文件语法格式

#![allow(dead_code)]

use std::{
    collections::HashMap,
    fs::File,
    io::{prelude::*, BufReader},
    path::Path,
    process::Command,
};

/// 单行命令行语法类型
#[derive(Debug)]
pub enum NodeType {
    /// 赋值语句
    Assign(String, String),
    /// 命令语句
    Command(Vec<String>),
    /// 注释
    Comment(String),
    /// 空行
    Space,
}

/// 语法节点
#[derive(Debug)]
pub struct ParseNode {
    node_type: NodeType,
    line: usize,
}

impl ParseNode {
    pub fn new(node_type: NodeType, line: usize) -> Self {
        Self { node_type, line }
    }
}

/// 语法树
#[derive(Debug)]
pub struct Parser {
    nodes: Vec<ParseNode>,
    pub assign_map: HashMap<String, String>,
}

impl Parser {
    /// 从文件中获取每行代码 [去除空格]
    pub fn load<P: AsRef<Path>>(file_path: P) -> Self {
        let file = File::open(file_path);

        if let Ok(file) = file {
            let codes = BufReader::new(file)
                .lines()
                .map(|line| line.unwrap().trim().to_string())
                .collect();
            Self::parse_lines(codes)
        } else {
            println!("Load file error: {:?}", file.err().unwrap());
            std::process::exit(0);
        }
    }

    pub fn parse_line(s: &String) -> NodeType {
        if s.len() == 0 {
            return NodeType::Space;
        }

        if s.starts_with("#") {
            return NodeType::Comment(s[1..].trim().to_string());
        }

        let s: Vec<&str> = s.split(" ").filter(|s| s.len() > 0).collect();

        if s.len() >= 3 && s[0].starts_with("$") && s[1] == "=" {
            return NodeType::Assign(s[0].to_string(), s[2..].join(" "));
        }

        NodeType::Command(s.into_iter().map(|s| s.to_string()).collect())
    }

    pub fn parse_lines(codes: Vec<String>) -> Self {
        let mut nodes = Vec::new();
        let mut assign_map = HashMap::new();
        for (line, code) in codes.into_iter().enumerate() {
            match Self::parse_line(&code) {
                node @ NodeType::Space => {
                    nodes.push(ParseNode::new(node, line + 1));
                    continue;
                }

                node @ NodeType::Comment(_) => {
                    nodes.push(ParseNode::new(node, line + 1));
                    continue;
                }

                node @ NodeType::Command(_) => {
                    nodes.push(ParseNode::new(node, line + 1));
                    continue;
                }

                NodeType::Assign(k, v) => {
                    assign_map.insert(k.to_string(), v.to_string());
                    nodes.push(ParseNode::new(NodeType::Assign(k, v), line + 1));
                    continue;
                }
            }
        }

        Self { nodes, assign_map }
    }

    pub fn run_line(assign_map: &HashMap<String, String>, node: &NodeType, line: usize) {
        match node {
            NodeType::Command(ref v) => {
                let mut cmd = v[0].to_string();
                let mut command = Command::new(&v[0]);
                if v.len() > 1 {
                    for s in &v[1..] {
                        let mut arg = s;
                        if s.starts_with("$") && assign_map.get(s).is_some() {
                            arg = assign_map.get(s).unwrap();
                        }
                        cmd.push_str(format!(" {}", arg).as_str());
                        command.arg(arg);
                    }
                }

                println!("Running {:?} at line {}...", cmd, line);
                let output = command
                    .output()
                    .expect(&format!("{:?} causes an error at line[{}]", cmd, line));

                println!("{}", String::from_utf8(output.stdout).unwrap());
            }
            _ => {}
        }
    }

    pub fn run(&self) {
        for node in &self.nodes {
            Self::run_line(&self.assign_map, &node.node_type, node.line);
        }
    }
}