basic_quick_lib 0.3.0

A basic lib to do some basic stuff with cli programming
Documentation
pub mod app;

use std::process;

use clearscreen::clear;

use crate::io_util::{input_optional_repeat, input_other_repeat};

use self::app::BasicApp;

const MENU_WHOLE_LINE: &str = "========================================\n";
const MENU_EMPTY_LINE: &str = "=                                      =\n";

pub fn build_menu_string(options: &[&str], exit_text: &str) -> String {
  let mut menu = String::new();

  menu.push_str(MENU_WHOLE_LINE);
  menu.push_str(MENU_EMPTY_LINE);

  for (index, option) in options.iter().enumerate() {
    let option = format!("{} --- {}", index + 1, option);
    menu.push_str(&format!("={:^38}=\n", option));
    menu.push_str(MENU_EMPTY_LINE);
  }

  let exit_text = format!("0 --- {}", exit_text);
  menu.push_str(&format!("={:^38}=\n", exit_text));
  menu.push_str(MENU_EMPTY_LINE);

  menu.push_str(MENU_WHOLE_LINE);

  menu
}

pub fn start_app<T>(apps: &mut [T])
where
  T: BasicApp,
{
  let options: Vec<&'static str> =
    apps.iter().map(|app| app.app_name()).collect();

  let menu = build_menu_string(options.as_slice(), "Exit");

  loop {
    clear().unwrap_or_default();
    println!("{}", menu);
    let input: usize = input_other_repeat("");

    match input {
      0 => {
        clear().unwrap_or_default();
        process::exit(0)
      }
      input if input <= apps.len() => apps[input - 1].menu(),
      _ => continue,
    }
  }
}

type NameAndFunc<'a, T> = (&'a str, fn(&mut T));

pub fn start_menu<T>(app: &mut T, menu_options: &[NameAndFunc<T>])
where
  T: BasicApp,
{
  // TODO: This code looks horrible. Maybe some improvement in the future if possible?
  // What I am trying to do here is to decouple the str from this tuple array.
  let menu = build_menu_string(
    menu_options
      .iter()
      .map(|v| v.0)
      .collect::<Vec<_>>()
      .as_slice(),
    "Back to Main Menu",
  );

  loop {
    clearscreen::clear().unwrap_or_default();
    println!("{}", menu);

    let input: usize = input_other_repeat("");
    match input {
      0 => return,
      n if n <= menu_options.len() => menu_options[n - 1].1(app),
      _ => continue,
    }
  }
}

/// default cannot be zero. It starts from 1
pub fn build_options(
  description: &str,
  options: &[&str],
  default: Option<usize>,
) -> String {
  let mut option_menu = String::new();
  option_menu.push_str(&format!("{}\n", description));

  for (index, option) in options.iter().enumerate() {
    let additional_text = match default {
      Some(value) if value == index + 1 => " (Default)",
      _ => "",
    };

    option_menu.push_str(&format!(
      "     {} --- {}{}\n",
      index + 1,
      option,
      additional_text
    ));
  }

  option_menu
}

pub fn get_option(description: &str, options: &[&str]) -> usize {
  let menu = build_options(description, options, None);
  let len = options.len();
  loop {
    match input_other_repeat::<usize>(&menu) {
      n if (1..=len).contains(&n) => return n,
      _ => continue,
    }
  }
}

/// default starts from 1 for array indexing
pub fn get_option_or_default(
  description: &str,
  options: &[&str],
  default: usize,
) -> usize {
  let menu = build_options(description, options, Some(default));
  let len = options.len();

  println!("{}", menu);

  loop {
    let value: usize = match input_optional_repeat("", default) {
      v if (1..=len).contains(&v) => v,
      _ => {
        println!("Invalid Option!");
        continue;
      }
    };

    return value;
  }
}