lamp 0.3.1

A Linux backlight utility inspired by acpibacklight
// This file is part of the "lamp" program.
//     Copyright (C) 2022  crapStone
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

mod cli;
mod controllers;

use std::process::exit;

use controllers::{Controller, LinController, LogController, RawController};

use crate::cli::build_cli;

fn main() {
    let app = build_cli();
    let matches = app.get_matches();

    let (default_ctrl, ctrls) = controllers::get_controllers().unwrap_or_else(|why| {
        eprintln!("an error occured when reading devices: '{why}'");
        exit(exitcode::IOERR)
    });

    let p = match matches.value_of("controller") {
        Some(ctrl) => {
            let p = ctrls.get(ctrl);
            if p == None {
                eprintln!("no device with name '{ctrl}' found");
                eprintln!("use --list to ge a list of all available devices");
                exit(exitcode::DATAERR);
            }

            p.unwrap().to_owned()
        }
        None => ctrls.get(&default_ctrl).unwrap().to_owned(),
    };

    let controller: Box<dyn Controller> = match matches.value_of("ctrl_type") {
        Some("raw") => Box::new(RawController::new(p)),
        Some("lin") => Box::new(LinController::new(p)),
        Some("log") => Box::new(LogController::new(p)),
        Some(_) | None => panic!("{ERROR_MSG}"),
    };

    if matches.is_present("list") {
        println!("{default_ctrl} [default]");
        for ctrl in ctrls.keys() {
            if ctrl != &default_ctrl {
                println!("{ctrl}");
            }
        }
        exit(exitcode::OK);
    } else if let Some(value) = matches.value_of("set") {
        let new_value = str_to_int(value);
        controller.set_brightness(new_value);
    } else if let Some(value) = matches.value_of("inc") {
        let new_value = controller.get_brightness() + str_to_int(value);
        controller.set_brightness(new_value.min(controller.get_max_brightness()));
    } else if let Some(value) = matches.value_of("dec") {
        let new_value = controller.get_brightness() - str_to_int(value);
        controller.set_brightness(new_value);
    } else if matches.is_present("get") {
        println!("{}", controller.get_brightness());
    } else if matches.is_present("zero") {
        controller.set_brightness(0);
    } else if matches.is_present("full") {
        controller.set_brightness(controller.get_max_brightness());
    } else {
        build_cli().print_long_help().unwrap();
    }
}

#[inline(always)]
fn str_to_int(value: &str) -> u32 {
    value.parse().unwrap_or_else(|_| {
        eprintln!("cannot parse '{value}' as positive integer");
        exit(exitcode::DATAERR);
    })
}

// https://xkcd.com/2200/
const ERROR_MSG: &str = r#"
    ERROR!

    If you're seeing this, the code is in what I thought was an unreachable state.

    I could give you advice for what to do. but honestly, why should you trust me?
    I clearly screwed this up. I'm writing a message that should never appear,
    yet I know it will probably appear someday.

    On a deep level, I know I'm not up to this task. I'm so sorry.
"#;