screen_printer 0.1.3

A crate for creating and/or printing grids
Documentation
pub use crate::dynamic_printer::*;
use std::cmp::Ordering;
use std::fmt;
use std::{io, io::Write};

/// These are the possible ways the program can fail.
///
/// Each error will contain 'ErrorData' which holds the
/// expected and outcome results in the event of the error.
#[derive(Debug)]
pub enum PrintingError {
  RowTooLong(LengthErrorData),
  RowTooShort(LengthErrorData),

  /// In the context of creating a grid from a full list of characters
  TooManyCharacters(LengthErrorData),
  /// In the context of creating a grid from a full list of characters
  TooLittleCharacters(LengthErrorData),

  RowsDontMatchLengths,

  InvalidGridInput(LengthErrorData),
  CursorError(String),
}

impl fmt::Display for PrintingError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{:?}", self)
  }
}

/// The namespace for all methods used to create and print a grid
///
/// Also used to store data for the dynamic printing method
#[derive(Debug)]
pub struct Printer {
  pub previous_grid: String,

  /// Creation will be on first print
  pub origin_position: (usize, usize),

  pub grid_height: usize,
  pub grid_width: usize,
}

/// Error data for when incorrect sizes are detected in a method
#[derive(Debug)]
pub struct LengthErrorData {
  pub expected_length: usize,
  pub got_length: usize,
}

impl LengthErrorData {
  /// Creates a new LengthErrorData for the expected length and actual length
  #[allow(clippy::new_without_default)]
  pub fn new(expected_length: usize, got_length: usize) -> Self {
    Self {
      expected_length,
      got_length,
    }
  }
}

impl Printer {
  /// Creates a new Printer, this is not needed for most methods since Printer
  /// is only there for the namespace
  ///
  /// However you will need to create a Printer for using the [`dynamic_print()`](crate::printer::Printer::dynamic_print()) method.
  #[allow(clippy::new_without_default)]
  pub fn new(grid_width: usize, grid_height: usize) -> Self {
    Self {
      previous_grid: String::new(),
      origin_position: (0, 0),
      grid_width,
      grid_height,
    }
  }

  /// Creates a grid of the given size with the given character.
  ///
  /// It's recommended that the passed in item is only 1 character long.
  ///
  /// # Example
  /// ```
  /// use screen_printer::printer::*;
  ///
  /// let character = "a";
  /// let expected_grid = "aaa\naaa\naaa";
  ///
  /// let grid = Printer::create_grid_from_single_character(&character, 3, 3);
  ///
  /// assert_eq!(expected_grid, grid);
  /// ```
  pub fn create_grid_from_single_character<T>(character: &T, width: usize, height: usize) -> String
  where
    T: fmt::Display,
  {
    let row = Self::get_row_of_character(character, width);

    Self::create_grid_from_single_row(&row, height)
  }

  /// Creates a grid of the given height with the given row
  ///
  /// # Example
  /// ```
  /// use screen_printer::printer::*;
  ///
  /// let row = "abcd";
  /// let expected_grid = "abcd\nabcd\nabcd";
  ///
  /// let grid = Printer::create_grid_from_single_row(&row, 3);
  ///
  /// assert_eq!(expected_grid, grid);
  /// ```
  pub fn create_grid_from_single_row<T>(row: &T, height: usize) -> String
  where
    T: fmt::Display,
  {
    let rows = (0..height).fold(Vec::new(), |mut rows, _| {
      rows.push(&row);

      rows
    });

    Printer::create_grid_from_multiple_rows(&rows).unwrap()
  }

  /// Creates a grid of the given size with the given list of characters
  ///
  /// An error is returned when the grid size doesn't match the amount of characters given
  ///
  /// # Example
  /// ```
  /// use screen_printer::printer::*;
  ///
  /// let characters = vec!["a", "b", "c", "d", "e", "f", "g", "h", "i"];
  /// let expected_grid = "abc\ndef\nghi";
  ///
  /// let grid = Printer::create_grid_from_full_character_list(&characters, 3, 3).unwrap();
  ///
  /// assert_eq!(expected_grid, grid);
  /// ```
  pub fn create_grid_from_full_character_list<T>(
    characters: &Vec<T>,
    width: usize,
    height: usize,
  ) -> Result<String, PrintingError>
  where
    T: fmt::Display,
  {
    let grid_size = width * height;

    match characters.len().cmp(&grid_size) {
      Ordering::Less => Err(PrintingError::TooLittleCharacters(LengthErrorData::new(
        characters.len(),
        grid_size,
      ))),
      Ordering::Greater => Err(PrintingError::TooManyCharacters(LengthErrorData::new(
        characters.len(),
        width * height,
      ))),
      Ordering::Equal => Ok(create_grid_from_characters(characters, width)),
    }
  }

  /// Creates a grid of the given rows
  ///
  /// An error is returned if any of the rows isn't the same length as the first
  ///
  /// # Example
  /// ```
  /// use screen_printer::printer::*;
  ///
  /// let rows = vec![
  ///   "abc",
  ///   "def",
  ///   "ghi",
  /// ];
  ///
  /// let expected_grid = "abc\ndef\nghi";
  ///
  /// let grid = Printer::create_grid_from_multiple_rows(&rows).unwrap();
  ///
  /// assert_eq!(expected_grid, grid);
  /// ```
  pub fn create_grid_from_multiple_rows<T>(rows: &[T]) -> Result<String, PrintingError>
  where
    T: fmt::Display,
  {
    let rows: Vec<String> = rows.iter().map(|row| format!("{}", row)).collect();
    let width = rows[0].chars().count();

    if Self::rows_have_same_lengths(&rows, width) {
      Ok(rows.join("\n"))
    } else {
      Err(PrintingError::RowsDontMatchLengths)
    }
  }

  /// Moves the cursor up by the given height and prints the given grid.
  ///
  /// This is for printing over the previously printed grid.
  /// It's recommended to add some whitespace before your first print so the grid
  /// doesn't print into anything that was printed before this method was called.
  ///
  /// # Example
  /// ```
  /// use screen_printer::printer::*;
  ///
  /// let height = 10;
  /// let width = 10;
  /// let grid = Printer::create_grid_from_single_character(&"a", width, height);
  ///
  /// print!("{}", "\n".repeat(height + 5)); // add some space for the grid
  /// Printer::print_over_previous_grid(grid, height);
  /// ```
  pub fn print_over_previous_grid(grid: String, height: usize) {
    print!("\x1b[{};A", height - 1);
    print!("\r{}", grid);
    let _ = io::stdout().flush();
  }

  /// Creates a row of the given width with the given character.
  ///
  /// # Example
  /// ```
  /// use screen_printer::printer::*;
  ///
  /// let width = 3;
  /// let row = Printer::get_row_of_character(&"a", width);
  ///
  /// assert_eq!(row, "aaa".to_string());
  /// ```
  pub fn get_row_of_character<T>(character: &T, width: usize) -> String
  where
    T: fmt::Display,
  {
    (0..width).fold(String::new(), |row, _| format!("{}{}", row, character))
  }

  /// Returns true of all given rows have the same amount of characters as the expected input.
  fn rows_have_same_lengths(rows: &[String], expected_width: usize) -> bool {
    rows.iter().all(|row| row.chars().count() == expected_width)
  }
}

/// Creates a grid of the given width out of the given 1D array of characters.
fn create_grid_from_characters<T>(characters: &[T], width: usize) -> String
where
  T: fmt::Display,
{
  characters
    .chunks(width)
    .map(|row| {
      row.iter().fold(String::new(), |mut row, character| {
        row.push_str(format!("{}", character).as_str());

        row
      })
    })
    .collect::<Vec<String>>()
    .join("\n")
}