sliders 0.0.1

sliders widget in the terminal
Documentation
use std::env;
use std::error::Error;
use std::process::Command;
use crossterm::{cursor::MoveTo,event::{read, Event, KeyCode},terminal::{size, Clear, ClearType, enable_raw_mode, disable_raw_mode}, ExecutableCommand};
use std::io::stdout;

trait SetterGetter {
    fn get(&self) -> Result<u8, Box<dyn Error>>;
    fn set(&self, value: u8) -> Result<(), Box<dyn Error>>;
}

struct Slider {
    name: String,
    setter_getter: Box<dyn SetterGetter>,
    current: u8,
}

impl Slider {

    fn get(&mut self) -> Result<u8, Box<dyn Error>> {
        self.setter_getter.get()
    }

    fn set(&mut self, value: u8) -> Result<(), Box<dyn Error>> {
        self.current = value;
        self.setter_getter.set(value)
    }

    fn inc(&mut self, n: u8) -> Result<(), Box<dyn Error>> {
        let val = self.get()?;
        if (val + n) <= 100 { 
            self.set(val + n)?;
            self.current = val + n;
        }
        Ok(())
    }

    fn dec(&mut self, n: u8) -> Result<(), Box<dyn Error>> {
        let val = self.get()?;
        if val >= n { 
            self.set(val - n)?; 
            self.current = val - n;
        }
        Ok(())
    }

    fn initialize(&mut self) -> Result<(), Box<dyn Error>> {
        self.current = self.get()?;
        Ok(())
    }
}

struct CommandLineSetterGetter {
    get_command: String,
    set_command: String,
}

impl SetterGetter for CommandLineSetterGetter {

    fn get(&self) -> Result<u8, Box<dyn Error>> {
        let output = Command::new("sh")
            .arg("-c")
            .arg(self.get_command.clone())
            .output()?;
        let mut contents = String::from_utf8_lossy(&output.stdout).to_string();
        if contents.ends_with('\n') { contents.pop(); }
        let res = contents.parse()?;
        Ok(res)
    }

    fn set(&self, value: u8) -> Result<(), Box<dyn Error>> {
        Command::new("sh")
            .arg("-c")
            .arg(self.set_command.replace("{}", format!("{}", value).as_str()))
            .output()?;
        Ok(())
    }

}

fn command_line_slider(name: String, get_command: String, set_command: String) -> Slider {

    Slider {
        name,
        setter_getter: Box::new(CommandLineSetterGetter { get_command, set_command }),
        current: 25,
    }
}

pub struct Sliders {
    sliders: Vec<Slider>
}

impl Sliders {

    fn clear() -> Result<(), Box<dyn Error>> {
        stdout().execute(Clear(ClearType::All))?;
        Ok(())
    }

    fn draw(sliders: &Vec<Slider>, current: &mut usize) -> Result<(), Box<dyn Error>> {
        let (cols, rows) = size()?; 
        Sliders::clear()?;
        let vertical_margin = 4;
        let spaces_count = (cols as usize / sliders.len() - 5) / 2;
        let spaces = format!("{:width$}", "", width=spaces_count);
        for y in 0..(rows - 1) {
            stdout() .execute(MoveTo(0, y))?;
            for (i, slider) in sliders.iter().enumerate() {
                let value = slider.current as u16;
                let start_y = (rows - vertical_margin) * (100 - value) / 100;
                if y > vertical_margin && y < (rows - vertical_margin) {
                    print!("{}", spaces);
                    if y > start_y {
                        print!("│ █ │");
                    }
                    else {
                        print!("│   │");
                    }
                    print!("{}", spaces);
                }
                else if y == (rows - vertical_margin) {
                    print!("{}", spaces);
                    print!("╰───╯");
                    print!("{}", spaces);
                }
                else if y == vertical_margin {
                    print!("{}", spaces);
                    print!("╭───╮");
                    print!("{}", spaces);
                }
                else if y == (rows - vertical_margin + 1) {
                    let title = &slider.name;
                    let spaces_count = (cols as usize / sliders.len() - title.len() - 2) / 2;
                    let spaces = format!("{:width$}", "", width=spaces_count);
                    print!("{}", spaces);
                    print!("{}", if i == *current { "<" } else { " " });
                    print!("{}", title);
                    print!("{}", if i == *current { ">" } else { " " });
                    print!("{}", spaces);
                }
            }
        }
        Ok(())
    }

    fn read_key() -> Result<KeyCode, Box<dyn Error>> {
        loop {
            if let Event::Key(e) = read()?
            {
                return Ok(e.code);
            }
        }
    }

    pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
        let mut current = 0;
        enable_raw_mode()?;
        loop {
            Sliders::draw(&self.sliders, &mut current)?;
            match Sliders::read_key()? {
                KeyCode::Char('h') | KeyCode::Left => if current > 0 { current -= 1 },
                KeyCode::Char('l') | KeyCode::Right => if current < (self.sliders.len() - 1) { current += 1 },
                KeyCode::Char('k') | KeyCode::Up => self.sliders[current].inc(1)?,
                KeyCode::Char('j') | KeyCode::Down => self.sliders[current].dec(1)?,
                KeyCode::Char('g') => self.sliders[current].set(0)?,
                KeyCode::Char('G') => self.sliders[current].set(100)?,
                KeyCode::Char('m') => self.sliders[current].set(50)?,
                KeyCode::Char('q') => break,
                _ => {},
            }
        }
        disable_raw_mode()?;
        Sliders::clear()?;
        Ok(())
    }

    pub fn from_args() -> Result<Sliders, Box<dyn Error>> {
        let mut names = vec![];
        let mut get_commands = vec![];
        let mut set_commands = vec![];

        let mut i = 1;
        let args : Vec<String> = env::args().collect();
        while i < args.len()  {
            match args[i].as_str() {
                "--name" => names.push(args[i + 1].clone()),
                "--get" => get_commands.push(args[i + 1].clone()),
                "--set" => set_commands.push(args[i + 1].clone()),
                _ => {},
            }
            i += 2;
        }

        let mut sliders = vec![];

        for i in 0..names.len() {
            let get_command = get_commands[i].clone();
            let set_command = set_commands[i].clone();
            sliders.push(command_line_slider(
                    names[i].clone(),
                    get_command,
                    set_command));
        }

        for slider in &mut sliders {
            slider.initialize()?;
        }

        Ok(Sliders { sliders })
    }

}