Open
Description
A generalization for #9 .
The choices can be modeled as their display name coupled with a parsing function to the desired data type of the answer:
data MultipleChoiceOption a = MultipleChoiceOption
{ displayName :: ByteString
, parsingFun :: ByteString -> Maybe a
}
Then, we can model multiple choices as: a message prompt, a non-empty list of choices (whose default choice will always be the head), and a value for the default option:
data MultipleChoicePrompt a = MultipleChoicePrompt
{ promptMessage :: ByteString
, promptOptions :: NonEmpty (MultipleChoiceOption a)
, defaultValue :: a
}
Thus, the following prompt data:
MultipleChoicePrompt
{ promptMessage="Are you sure?"
, promptOptions=
MultipleChoiceOption
{ displayName="y"
, parsingFun = \s -> if s == "y" then Just Yes else Nothing
}
:|
[ MultipleChoiceOption
{ displayName="n"
, parsingFun = \s -> if s == "n" then Just No else Nothing
}
]
, defaultValue=Yes
}
Will represent:
Are you sure? [y]/n
Finally, we can define a function that:
- Prints the question (via an aux function that takes the
MultipleChoicePrompt
- Reads answer
- Returns the first choice that is parsed correctly (can be done with a
foldMap
over theAlt
monoid) - Or prints an error message to
stderr
in case of error, returningNothing
multipleChoiceQuestion
:: MonadIO m
=> MultipleChoicePrompt a
-> m (Maybe a)
An example run would be:
>>> multipleChoiceQuestion
MultipleChoicePrompt
{ promptMessage="Are you sure?"
, promptOptions=
MultipleChoiceOption
{ displayName="y"
, parsingFun = \s -> if s == "y" then Just Yes else Nothing
}
:|
[ MultipleChoiceOption
{ displayName="n"
, parsingFun = \s -> if s == "n" then Just No else Nothing
}
, MultipleChoiceOption
{ displayName="cancel"
, parsingFun = \s -> if s == "cancel" then Just Cancel else Nothing
}
]
, defaultValue=Yes
}
>>> n
Just No
Something to notice is that the function will take the default value and build a parser to accept empty responses:
>>> multipleChoiceQuestion
MultipleChoicePrompt
{ promptMessage="Are you sure?"
, promptOptions=
MultipleChoiceOption
{ displayName="y"
, parsingFun = \s -> if s == "y" then Just Yes else Nothing
}
:|
[ MultipleChoiceOption
{ displayName="n"
, parsingFun = \s -> if s == "n" then Just No else Nothing
}
, MultipleChoiceOption
{ displayName="cancel"
, parsingFun = \s -> if s == "cancel" then Just Cancel else Nothing
}
]
, defaultValue=Yes
}
>>>
Just Yes
Finally, some points that might need refinement:
- Default value might be different than the value that is returned by the parsing function.
- Instead of printing to
stderr
and returningNothing
, it may be better to returnEither Bytestring a
and let another function decide what to do. - Instead of having a
Non-Empty
List, we could have either a tuple with the first two options and a normalList
for the rest, or a sized container (i.e, something from Data.Size). Having a multiple option question with just one option feels wrong.
I'm willing to try and accommodate all suggestions to this implementation :)
Metadata
Metadata
Assignees
Labels
No labels