kjps 0.1.2

A tool to kill a java process
Documentation
use regex::Regex;
use std::{collections::HashMap, fmt};

use ansi_term::Color;
use std::error::Error;
use std::process::Command;

pub fn get_all_process() -> Result<String, Box<dyn Error>> {
    let output = Command::new("ps")
        .arg("axo")
        .arg("pid,%cpu,%mem,command")
        .output()?;
    let result = String::from_utf8(output.stdout)?;

    return Ok(result);
}

pub fn get_jps_pid_name_map() -> Result<HashMap<String, String>, Box<dyn Error>> {
    let output = Command::new("jps").output()?;
    let str_out = String::from_utf8(output.stdout)?;
    let re = Regex::new(r"(\d+)\s*(.*)")?;
    let result: HashMap<String, String> = str_out
        .lines()
        .filter_map(|line| {
            let captures: Vec<regex::Captures> = re.captures_iter(line).collect();
            if captures.len() != 1 {
                return None;
            }
            captures
                .get(0)
                .map(|capture| (capture[1].to_owned(), capture[2].to_owned()))
        })
        .collect();
    Ok(result)
}

pub fn filter_java_process(ps_output: String) -> Result<Vec<Process>, Box<dyn Error>> {
    let pid_to_name = get_jps_pid_name_map()?;

    let java_processes: Vec<Process> = ps_output
        .lines()
        .filter_map(|line| {
            if line.contains("java") {
                let process = match Process::new(line) {
                    Ok(p) => match pid_to_name.get(&p.pid) {
                        Some(jps_name) => Some(p.with_name(jps_name)),
                        None => None,
                    },
                    Err(_) => None,
                };
                process
            } else {
                None
            }
        })
        .collect();
    return Ok(java_processes);
}

pub fn print_processes(processes: &Vec<Process>, highlight_idx: usize) -> String {
    let mut res = String::from("");
    res = res + &(format!("{:>8}{:>6}%{:>6}%    {}\r\n", "PID", "CPU", "RAM", "Comm"));
    for (idx, p) in processes.iter().enumerate() {
        if idx == highlight_idx {
            res = res
                + &(format!(
                    "{}\r\n",
                    Color::Black.on(Color::White).paint(format!("{}", p))
                ))
        } else {
            res = res + &(format!("{}\r\n", p))
        }
    }
    return res;
}

pub fn kill_process(process: &Process) -> Result<(), Box<dyn Error>> {
    Command::new("kill")
        .arg("-9")
        .arg(format!("{}", process.pid))
        .output()?;
    return Ok(());
}

#[derive(Debug, Clone)]
enum Errors {
    ParseInputError = 1,
}

impl fmt::Display for Errors {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let error_text = match self {
            Errors::ParseInputError => "fail to parse input",
        };
        write!(f, "{}", error_text)
    }
}

impl Error for Errors {}

#[derive(Debug)]
pub struct Process {
    pid: String,
    cpu: String,
    mem: String,
    name: String,
}

impl Process {
    pub fn new(s: &str) -> Result<Process, Box<dyn Error>> {
        let re = Regex::new(r"\s*(\d*)\s*([\d\.]*)\s*([\d\.]*)\s*(.*)\s*")?;
        let captures: Vec<regex::Captures> = re.captures_iter(s).collect();
        if captures.len() != 1 {
            return Err(Errors::ParseInputError.into());
        }
        let capture = captures.get(0).ok_or(Errors::ParseInputError)?;
        Ok(Process {
            pid: String::from(&capture[1]),
            cpu: String::from(&capture[2]),
            mem: String::from(&capture[3]),
            name: String::from(&capture[4]),
        })
    }

    fn shorten_name(&self) -> Result<String, Box<dyn Error>> {
        let re = Regex::new(r"-jar \S*/(\S*)")?;
        let captures: Vec<regex::Captures> = re.captures_iter(&self.name).collect();
        if captures.len() != 1 {
            return Ok(self.name.clone());
        }
        let capture = captures.get(0).ok_or(Errors::ParseInputError)?;
        return Ok(String::from(&capture[1]));
    }

    fn with_name(&self, new_name: &String) -> Process {
        Process {
            pid: self.pid.clone(),
            cpu: self.cpu.clone(),
            mem: self.mem.clone(),
            name: new_name.clone(),
        }
    }
}

impl fmt::Display for Process {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let shorten_name = self.shorten_name().map_err(|_| fmt::Error)?;
        write!(
            f,
            "{:>8}{:>6}%{:>6}%    {}",
            self.pid, self.cpu, self.mem, shorten_name
        )
    }
}