lunar-lib 0.8.0

Common utilities for lunar applications
Documentation
use std::{borrow::Cow, fmt::Display};

pub trait ChoicePrompter {
    /// Tells the prompter to start a multiple option prompt
    ///
    /// This prompt requires the user to pick a single option from any of the given options
    ///
    /// This function should return [`None`] if the result couldn't be parsed
    fn choose<'a, C: Display>(&self, msg: ChoicePrompt<'a, C>) -> Option<C>;
}

pub struct Choice<'a, C: Display> {
    pub option: C,
    pub format: Option<Cow<'a, str>>,
}

impl<'a, C: Display> Choice<'a, C> {
    pub fn into_value(self) -> C {
        self.option
    }
}

impl<C: Display> Display for Choice<'_, C> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Some(cow) = &self.format {
            cow.fmt(f)
        } else {
            self.option.fmt(f)
        }
    }
}

impl<'a, C: Display> Choice<'a, C> {
    pub fn new(option: C) -> Self {
        Self {
            option,
            format: None,
        }
    }

    /// Overides the display, displaying the format string instead of the [`Display`] implementation of `C`
    pub fn with_format(mut self, format: impl Into<Cow<'a, str>>) -> Self {
        self.format = Some(format.into());
        self
    }
}

impl<T: Display> From<T> for Choice<'_, T> {
    fn from(value: T) -> Self {
        Choice {
            option: value,
            format: None,
        }
    }
}

pub struct ChoicePrompt<'a, C: Display> {
    pub prompt: Cow<'a, str>,
    pub options: Vec<Choice<'a, C>>,
    pub default: usize,
}

impl<'a, C: Display> ChoicePrompt<'a, C> {
    pub fn new(prompt: Cow<'a, str>) -> Self {
        Self {
            prompt,
            options: Vec::new(),
            default: 0,
        }
    }

    /// Replaces the options in the prompt
    pub fn with_options<I>(mut self, options: I) -> Self
    where
        I: IntoIterator,
        I::Item: Into<Choice<'a, C>>,
    {
        self.options = options.into_iter().map(Into::into).collect();
        self
    }

    /// Adds a option to the prompt
    pub fn with_added_option(mut self, option: impl Into<Choice<'a, C>>) -> Self {
        self.options.push(option.into());
        self
    }

    /// Sets the index of default option. This will always default to 0 if unset
    ///
    /// Out of bounds indices are entirely up to the caller to handle, even if that means panics
    pub fn with_default_index(mut self, index: usize) -> Self {
        self.default = index;
        self
    }
}