mssh 0.0.0

Mssh Simple SHell. Bash interpreter/compiler. Will not support all the functionalities.
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

pub struct History {
    lines: Vec<String>,
    size: usize,
    pos: usize,
    cmds: CmdToIdx,
    path: String, // where to store the hist
    nbr_of_empty_lines: u8,
}

type CmdToIdx = HashMap<String, usize>;

impl History {
    pub fn new(path: String) -> Self {
        let cmds: CmdToIdx = CmdToIdx::new();
        let lines: Vec<String> = Vec::new();
        let size = 0;
        let pos = 0;
        let nbr_of_empty_lines = 0;
        let mut h = Self {
            lines,
            size,
            cmds,
            pos,
            nbr_of_empty_lines,
            path,
        };
        h.load();
        return h;
    }

    pub fn get_previous(&mut self) -> String {
        if self.pos < self.size {
            let res = self.lines[self.size - self.pos - 1].to_owned();
            self.pos += 1;
            // jump over any ""
            if res == "" {
                self.get_previous()
            } else {
                res
            }
        } else {
            "".to_owned()
        }
    }

    pub fn get_next(&mut self) -> String {
        if self.pos > 0 {
            self.pos -= 1;
            let res = self.lines[self.size - self.pos - 1].to_owned();
            // jump over any ""
            if res == "" {
                self.get_next()
            } else {
                res
            }
        } else {
            "".to_owned()
        }
    }

    pub fn add(&mut self, line: String) {
        if let Some(idx) = self.cmds.get(&line) {
            // self._show_lines();
            //eprintln!("removing ind {}->{}", idx, line,);
            // if line exists somewhere in the history, delete it
            self.lines[*idx] = "".to_owned();
            self.nbr_of_empty_lines += 1;
            if self.nbr_of_empty_lines == MAX_EMPTY_LINES {
                self.remove_empty_lines();
                self.nbr_of_empty_lines = 0;
            }
        }
        // then insert any new command at the top
        self.cmds.insert(line.to_owned(), self.size);
        self.lines.push(line);
        self.size += 1;
        self.pos = 0;

        //self._show_lines();
    }

    pub fn save(&self) -> std::io::Result<()> {
        use std::io::Write;
        let mut output = File::create(&self.path)?;
        for line in self.lines.iter() {
            if line != "" {
                write!(output, "{}\n", line)?;
            }
        }
        Ok(())
    }
    fn remove_empty_lines(&mut self) {
        // keep non empty entries only
        self.size -= MAX_EMPTY_LINES as usize;
        let mut new_lines: Vec<String> = Vec::with_capacity(self.size);
        let mut i = 0;
        while i < self.size {
            for s in &self.lines {
                if s != "" {
                    new_lines.push(s.to_owned());
                    self.cmds.insert(s.to_owned(), i);
                    i += 1;
                }
            }
        }
        // does this leak memory ?
        self.lines = new_lines;
    }
    fn load(&mut self) {
        let mut idx = 0;
        if let Ok(lines) = read_lines(&self.path) {
            for line in lines {
                if let Ok(ip) = line {
                    if ip != "" {
                        self.cmds.insert(ip.to_owned(), idx);
                        self.lines.push(ip);
                        idx += 1;
                    }
                }
            }
            self.size = idx;
        }
    }
    fn _show_lines(&self) {
        for i in 0..self.size {
            eprintln!("{}->{}", i, &self.lines[i]);
        }
        eprintln!("-----------------");
    }
}

const MAX_EMPTY_LINES: u8 = 25;

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}