formations 0.1.0

A terminal forms/multiple choice library
Documentation
//! # Formations
//! 
//! Formations is a rust library to allow you to easily create terminal forms, where a user is presented with multiple options and has to pick one. It looks something like:
//! 
//! ```txt
//! 1) Option 1  2) Option 2
//! 3) Option 3  4) Option 4
//! ```
//! 
//! It can be programmed as follows:
//! 
//! ```rust
//! use formations::FormElement;
//! 
//! fn main() -> std::io::Result<()> {
//!     println!("Pick your favourite pet.");
//!     let mut form = vec![
//!         FormElement::new("cat", "Small mammal with fur"),
//!         FormElement::new("dog", "Likes a bone"),
//!         FormElement::new("mouse", "Likes cheese"),
//!     ];
//!     println!("{}", FormElement::run(&form)?.description.unwrap());
//! 
//!     Ok(())
//! }
//! ```
//! 

use std::fmt::Display;
use std::io::{self, Write};
use unicode_width::UnicodeWidthStr;
use yansi::Paint;

/// A structure to represent an individual option of a form.
pub struct FormElement<T: Display, Q: Display> {
    pub name: T,
    pub description: Option<Q>,
}

impl<T: Display, Q: Display> FormElement<T, Q> {
    #[must_use]
    pub fn new(name: T, description: Option<Q>) -> Self {
        Self { name, description }
    }

    /// Displays a table of choices
    pub fn display(list: &[Self]) {
        match termsize::get() {
            Some(size) => {
                // Get the largest length
                let mut len = 0;
                for i in list {
                    len = len.max(UnicodeWidthStr::width(i.name.to_string().as_str()));
                }

                // Work out how long the number will be
                let nwidth = list.len().to_string().len();

                // Work out how many columns we can have.
                let columns = usize::from(size.cols) / (len + 6 + 1);

                for (n, i) in list.iter().enumerate() {
                    // Newlines
                    if n % columns == 0 {
                        println!();
                    }

                    // Gets the string for the number.
                    let mut nstr = Paint::blue(n).bold().to_string();
                    nstr.push(')');

                    // Print it in the form of:
                    // NN) MESSAGE
                    print!(
                        "{:<nwidth$} {:width$}",
                        nstr,
                        i.name,
                        nwidth = nwidth + 11,
                        width = len + 5 - nwidth,
                    );
                }
            }
            None => {
                for (n, i) in list.iter().enumerate() {
                    println!(
                        "{} {}{}",
                        n,
                        i.name,
                        i.description
                            .as_ref()
                            .map(|x| format!("- {}", x))
                            .unwrap_or_default(),
                    );
                }
            }
        }
    }

    /// Runs the form, that is to display it and then check for a valid response
    /// from the user.
    #[must_use]
    pub fn run<'a>(list: &'a [Self]) -> io::Result<&'a Self> {
        Self::display(list);

        // Query the user for input.
        loop {
            print!("\nEnter Choice <`r` to reprint, `dNN` for description> : ");
            io::stdout().flush()?;

            let mut buffer = String::new();
            io::stdin().read_line(&mut buffer)?;

            match buffer.trim().chars().next() {
                Some('r') => Self::display(list),
                Some('d') => match buffer.trim().get(1..).unwrap_or_default().parse::<usize>() {
                    Ok(n) => match list.get(n) {
                        Some(val) => println!(
                            "{}",
                            val.description
                                .as_ref()
                                .map(|x| x.to_string())
                                .unwrap_or("No description available".to_string())
                        ),
                        None => println!("{}", Paint::red("Invalid Choice")),
                    },
                    Err(_) => {
                        println!("{}", Paint::red("Please enter a valid number"));
                    }
                },
                _ => match buffer.parse::<usize>() {
                    Ok(n) => match list.get(n) {
                        Some(val) => return Ok(val),
                        None => println!("{}", Paint::red("Invalid Choice")),
                    },
                    Err(_) => {
                        println!("{}", Paint::red("Please enter a valid number"));
                    }
                },
            }
        }
    }
}