Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option modifier for grouping options in help message #231

Closed
wants to merge 3 commits into from

Conversation

larskuhtz
Copy link

This PR introduces a group modifier for options, arguments, and flags.

-- | Add a description to a group of options, arguments, flags, or commands
group :: (HasGroup f) => String -> Mod f a
group groupName = fieldMod $ setGroup groupName

Options, arguments, and flags with a matching group modifier are listed in a section in the help message as shown in the following example:

Help Options:
  -?,-h,--help             Show this help message

Logging Options:
  --log-level quiet|error|warn|info|debug
                           threshold for log messages
  --exit-timeout INT       timeout for flushing the log message queue on exit
  -c,--color ARG           whether to use ANSI terminal colors in the output
  --logger-backend-handle stdout|stderr|file:<FILENAME>
                           handle where the logs are written

Implementation

The implementation is similar to the recently introduced commandGroup field modifier for subparser commands.

This PR makes the following API changes:

  • Options.Applicative.Types: add new field to the the OptReader, ArgReader, and FlagReader constructors of OptReader.
  • Options/Applicative/Builder/Internal.hs: add new record fields to OptionFields, FlagFields, and ArgumentFields.

While it clearly is an API change, it's impact is limited because the affected types are exported only through the modules where they are defined and are not re-exported in the top-level Options.Applicative module or one of the modules directly re-exported by Option.Applicative. There are common patterns to use record types in a way that is robust under addition of new record fields.

@HuwCampbell
Copy link
Collaborator

Hi,
Thanks for putting in some time for optparse.
A few comments:

  • tests?
  • the command group thing is relatively painless to use, as it's one per subcommand builder, which can hold all the commands one want to bash together. My biggest concern regarding something like this is that there's quite a lot of work required to use it, when an option is added it would be easy to get this wrong.

Thoughts?

Cheers,
Huw

@larskuhtz
Copy link
Author

Hi Huw,

To your first point: I am happy to add tests if this PR has a chance be be accepted.

To your second point: In the past I used CmdArgs which supports grouping of options in the help message, but I didn't like the impure evaluation magic of its approach. So, when optparse-applicative was released, I started using it instead but I always missed this feature. Unfortunately, I also never could find a good work around by implementing it on top of optparse-applicatives public API. Therefor I looked for a simple way to implement grouping and this PR is the easiest solution I came up with. This doesn't mean that there aren't better ways of doing it.

More concrete, I think the approach to add the group-description to each option in the group has pros and cons:

  • First, I don't think it's error prone when the group description is defined as a constant. Also performance wise, if the group description is defined by binding it to a name, it comes to down to adding a pointer to the respective option structure.
  • The group description has to be passed as argument to all options that use it, which can be inconvenient, even though Haskell provides lots of means to mitigate this kind of inconveniences.
  • Passing group descriptions to options explicitly provides more flexibility, since options can be grouped independent of program context in which they are defined.

I don't really know how important the last point is. For my use cases I would probably be fine to group options by labeling a compound parser, but I haven't explored how that could be implemented.

| ArgReader (CReader a) -- ^ argument reader
= OptReader (Maybe String) [OptName] (CReader a) ParseError -- ^ option reader
| FlagReader (Maybe String) [OptName] !a -- ^ flag reader
| ArgReader (Maybe String) (CReader a) -- ^ argument reader
| CmdReader (Maybe String)
[String] (String -> Maybe (ParserInfo a)) -- ^ command reader
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a (Maybe String) on every constructor seems a bit rough. It should probably be moved to the Option type.

instance HasGroup OptionFields where
setGroup n fields = fields { optGroup = Just n }
getGroup = optGroup
instance HasGroup CommandFields where
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having an instance for CommandGroup will almost certainly cause confusion. If I use the same name for a command and an option do they appear in the same place?

They should either be separate concepts or the same, but not both.

@HuwCampbell
Copy link
Collaborator

I don't think this is the right API for this at the moment. I'm going to close this PR, but I do hope we can continue to think about this in the future.

@HuwCampbell
Copy link
Collaborator

Thanks for contributing to optparse

@tbidne tbidne mentioned this pull request May 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants