Checkpoint

This commit is contained in:
2026-08-02 18:30:34 +00:00
parent 596af4ed80
commit 6c33bcfb27
41 changed files with 6360 additions and 1 deletions
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "nye-console"
version = "0.1.0"
edition = "2024"
[dependencies]
colored = "3.1.1"
dialoguer = "0.12.0"
indicatif = { version = "0.18.6", features = ["tokio"] }
+305
View File
@@ -0,0 +1,305 @@
use std::fmt::Display;
use std::time::Duration;
use colored::Colorize;
use dialoguer::theme::Theme;
use dialoguer::{Input, Select};
use indicatif::{ProgressBar, ProgressStyle};
/// Takes a result and logs it if it's an error.
///
/// Arguments:
/// * `result` - The result.
pub fn catch_err<T, E>(result: Result<T, E>)
where
E: Display,
{
if let Err(error) = result {
err(error.to_string());
}
}
/// Displays a spinner loading bar on the console.
///
/// Arguments:
/// * `message` - The message to display with the spinner.
///
/// Returns:
/// [`ProgressBar`] - The spinner bar already being displayed on the console.
pub fn spinner(message: impl Into<String>) -> ProgressBar {
let bar = ProgressBar::new_spinner()
.with_message(message.into())
.with_style(
ProgressStyle::default_spinner()
.tick_strings(&["[\\]", "[|]", "[/]", "[-]", "^w^"])
.template("{spinner:.cyan} {msg}")
.unwrap(),
);
bar.enable_steady_tick(Duration::from_millis(200));
bar
}
/// Displays a bytes progress bar on the console.
///
/// Arguments:
/// * `message` - The message to display with the progress bar.
///
/// Returns:
/// [`ProgressBar`] - The progress bar with a ticking spinner.
pub fn progress_bytes(message: impl Into<String>, total: u64) -> ProgressBar {
let bar = ProgressBar::new(total)
.with_message(message.into())
.with_style(
ProgressStyle::default_bar()
.progress_chars("##")
.tick_strings(&["[\\]", "[|]", "[/]", "[-]", "^w^"])
.template(
"{spinner:.cyan} {wide_msg} {bar:.cyan/black} {binary_bytes}/{binary_total_bytes}",
)
.unwrap(),
);
bar.enable_steady_tick(Duration::from_millis(200));
bar
}
/// Displays a success log message.
///
/// Use this when finishing a task to indicate success.
///
/// Arguments:
/// * `message` - The message to log.
pub fn yay(message: impl Into<String>) {
let prefix = "YAY".cyan();
let message = message.into();
println!("{prefix} {message}");
}
/// Displays an info log message.
///
/// Arguments:
/// * `message` - The message to log.
pub fn info(message: impl Into<String>) {
let prefix = "III".blue();
let message = message.into();
println!("{prefix} {message}");
}
/// Displays a warning log message.
///
/// Arguments:
/// * `message` - The message to log.
pub fn warn(message: impl Into<String>) {
let prefix = "WWW".yellow();
let message = message.into();
println!("{prefix} {message}");
}
/// Displays an error log message.
///
/// Arguments:
/// * `message` - The message to log.
pub fn err(message: impl Into<String>) {
let prefix = "ERR".red();
let message = message.into();
println!("{prefix} {message}");
}
/// A theme for the dialoguer crate that is used to prompt the user for input and selection.
struct InputTheme;
impl Theme for InputTheme {
fn format_input_prompt(
&self,
f: &mut dyn std::fmt::Write,
prompt: &str,
_default: Option<&str>,
) -> std::fmt::Result {
let prefix = ">>>".purple();
write!(f, "{prefix} {prompt}")
}
fn format_input_prompt_selection(
&self,
f: &mut dyn std::fmt::Write,
prompt: &str,
sel: &str,
) -> std::fmt::Result {
let prefix = ">>>".purple();
write!(f, "{prefix} {prompt}{sel}")
}
fn format_select_prompt(&self, f: &mut dyn std::fmt::Write, prompt: &str) -> std::fmt::Result {
let prefix = ">>>".purple();
write!(f, "{prefix} {prompt}")
}
fn format_select_prompt_item(
&self,
f: &mut dyn std::fmt::Write,
text: &str,
active: bool,
) -> std::fmt::Result {
if active {
write!(f, "{}", format!(" * {text}").purple())
} else {
write!(f, " {text}")
}
}
fn format_select_prompt_selection(
&self,
f: &mut dyn std::fmt::Write,
prompt: &str,
sel: &str,
) -> std::fmt::Result {
let prefix = ">>>".purple();
write!(f, "{prefix} {prompt}{}", sel.purple())
}
}
/// Prompts the user for input.
///
/// Arguments:
/// * `prompt` - The message to prompt the user with.
///
/// Returns:
/// * `Ok(String)` - The user's input.
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
pub fn input(prompt: impl Into<String>) -> Result<String, dialoguer::Error> {
Input::with_theme(&InputTheme)
.with_prompt(prompt)
.interact_text()
}
/// Prompts the user for input with a default value.
///
/// Arguments:
/// * `prompt` - The message to prompt the user with.
/// * `default` - The default value.
///
/// Returns:
/// * `Ok(String)` - The user's input.
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
pub fn input_with_default(
prompt: impl Into<String>,
default: impl Into<String>,
) -> Result<String, dialoguer::Error> {
Input::with_theme(&InputTheme)
.with_prompt(prompt)
.default(default.into())
.interact_text()
}
/// Prompts the user to select an option.
///
/// Arguments:
/// * `prompt` - The message to prompt the user with.
/// * `options` - The options to give the user for selection.
///
/// Returns:
/// * `Ok(usize)` - The index of the option selected by the user.
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
pub fn select(
prompt: impl Into<String>,
options: impl IntoIterator<Item = impl ToString>,
) -> Result<usize, dialoguer::Error> {
Select::with_theme(&InputTheme)
.with_prompt(prompt)
.items(options)
.interact()
}
/// Prompts the user to select an option with an option selected by default.
///
/// Arguments:
/// * `prompt` - The message to prompt the user with.
/// * `options` - The options to give the user for selection.
/// * `default` - The index of the default option.
///
/// Returns:
/// * `Ok(usize)` - The index of the option selected by the user.
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
pub fn select_with_default(
prompt: impl Into<String>,
options: impl IntoIterator<Item = impl ToString>,
default: usize,
) -> Result<usize, dialoguer::Error> {
Select::with_theme(&InputTheme)
.with_prompt(prompt)
.items(options)
.default(default)
.interact()
}
/// Prompts the user to select an option with an option selected by default, without reporting the
/// selection.
///
/// Arguments:
/// * `prompt` - The message to prompt the user with.
/// * `options` - The options to give the user for selection.
/// * `default` - The index of the default option.
///
/// Returns:
/// * `Ok(usize)` - The index of the option selected by the user.
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
pub fn select_with_default_without_report(
prompt: impl Into<String>,
options: impl IntoIterator<Item = impl ToString>,
default: usize,
) -> Result<usize, dialoguer::Error> {
Select::with_theme(&InputTheme)
.with_prompt(prompt)
.items(options)
.default(default)
.report(false)
.interact()
}
/// Formats items into a human-readable list.
///
/// For example,
/// - `[1] => "1"`
/// - `[1, 2] => "1 and 2"`
/// - `[1, 2, 3] => "1, 2, and 3"`.
/// - `[1, 2, 3, 4] => "1, 2, 3, and 4"`.
///
/// Arguments:
/// * `items` - The items of the list.
///
/// Returns:
/// [`String`] -> The formatted list.
pub fn list<T>(items: &[T]) -> String
where
T: Display,
{
let mut string = String::new();
for (i, item) in items.iter().enumerate() {
let is_first = i == 0;
let is_penultimate = i == items.len() - 2;
let is_last = i == items.len() - 1;
match (is_first, is_penultimate, is_last) {
(false, false, false) => string.push_str(&format!("{item}, ")),
(false, false, true) => string.push_str(&item.to_string()),
(false, true, false) => string.push_str(&format!("{item}, and ")),
(false, true, true) => unreachable!(),
(true, false, false) => string.push_str(&format!("{item}, ")),
(true, false, true) => string.push_str(&item.to_string()),
(true, true, false) => string.push_str(&format!("{item} and ")),
(true, true, true) => unreachable!(),
}
}
string
}