Giter VIP home page Giter VIP logo

Comments (4)

mikaelmello avatar mikaelmello commented on May 31, 2024 1

Hey! Thanks for your suggestion, I'll first try to give you a solution and then later we can discuss if it is indeed a good one and think about possible changes!

When implementing the generalization of selectable prompts, I had planned to solve this problem via the new-type pattern. As a personal example, I have a struct which maps a db entity:

#[derive(AsChangeset, Identifiable, Insertable, Queryable, Serialize, PartialEq, Eq)]
#[table_name = "tag"]
#[changeset_options(treat_none_as_null = "true")]
pub struct Tag {
    pub id: Ulid,
    pub title: String,
    pub description: Option<String>,
}

Then, by using the new type pattern I was able to implement a nice implementation that has one single clear purpose: formatting tags for being selected in a prompt:

struct TagSelectView(Tag);

impl Display for TagSelectView {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
        write!(f, "{}", self.0.title)?;

        if let Some(desc) = &self.0.description {
            write!(f, " ({:.40})", desc)?;
        }

        Ok(())
    }
}

On a first glance it looks like too much boilerplate code, but from my experience when trying different ways, it was basically the same amount of code used for a formatter, except for the struct and impl lines. But on the other hand I felt it did improve readability.

Finally, just as an example, I select tags with this static method:

pub struct TagSelector;

impl TagSelector {
    pub fn select_skippable_from(
        msg: &str,
        tags: Vec1<Tag>,
        help_msg: &str,
    ) -> Result<Option<Tag>> {
        let tags = tags.into_iter().map(TagSelectView).collect();
        let tag = inquire::Select::new(msg, tags)
            .with_help_message(help_msg)
            .prompt_skippable()?
            .map(|t| t.0);

        Ok(tag)
    }
}

In your personal example, it could be solved similarly:

struct CustomStructView(String, CustomStruct);

impl CustomStructView {
    fn filter(input: &str, obj: &CustomStructView, str_value: &str, idx: usize) -> bool {
        input.contains(&obj.0)
    }
}

impl Display for CustomStructView {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
        write!(f, "{} ({})", self.0, self.1.description)
    }
}

pub fn select(objs: Vec1<CustomStruct>) -> Result<CustomStruct> {
    let objs = tags.into_iter()
        .map(|cs| CustomStructView("something".to_string(), cs))
        .collect();
        
    inquire::Select::new("Select one please:", objs)
        .with_help_message("Help message")
        .with_filter(&CustomStructView::filter)
        .prompt()
        .map(|t| t.1)
}

Also, I just noticed that the way filters are implemented could be more ergonomic, probably a trait instead of a static function in this use-case, but we would lost the ability to define filters in closures. But that's something to be discussed in another issue.

from inquire.

mainrs avatar mainrs commented on May 31, 2024 1

Such a simple solution that I couldn't think off yesterday! That does indeed work perfectly fine. I'll close this. I do think that the above is better solution!

from inquire.

adsick avatar adsick commented on May 31, 2024

I fully agree on that, selecting over Strings is kinda bad

from inquire.

AstrickHarren avatar AstrickHarren commented on May 31, 2024

what about a solution like this?

pub struct Formatted<'a, T: ?Sized> {
    fmt: &'a dyn Fn(&T) -> String,
    value: T,
}

impl<'a, T> Formatted<'a, T> {
    pub fn new(value: T, fmt: &'a impl Fn(&T) -> String) -> Self {
        Self { fmt, value }
    }
}

impl<T: ?Sized> Display for Formatted<'_, T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        (self.fmt)(&self.value).fmt(f)
    }
}

impl<T> Deref for Formatted<'_, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

so that in their case it can be

fn select(objs: Vec<(String, CustomStruct)>) {
    let objs = objs
        .into_iter()
        .map(|o| Formatted::new(o, &|o| format!("{}: {}", o.0, o.1.desc)))
        .collect();
    let select = Select::new("Select an object", objs).prompt().unwrap();
    // use select as if it were (String, CustomStruct)
    println!("{}", select.0);
}

It's not perfect though, because you can't manually deref select as a box, because apparently box is kinda special.

from inquire.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.