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

How do I print help info with colored command names? #478

Open
ghost opened this issue May 26, 2023 · 14 comments · May be fixed by #482
Open

How do I print help info with colored command names? #478

ghost opened this issue May 26, 2023 · 14 comments · May be fixed by #482

Comments

@ghost
Copy link

ghost commented May 26, 2023

Hello! I'm having trouble finding a solution to this. Is something like this currently possible?

optparse-applicative version: 0.17.0.0

Currently, what I tried to so far -

import Options.Applicative (Parser, (<**>))
import qualified Options.Applicative as O
import qualified Options.Applicative.Help as OH

main :: IO ()
main = pure =<< O.customExecParser (O.prefs O.showHelpOnEmpty) opts
  where
    opts =
      O.info
        (myCommands <**> O.helper)
        ( O.fullDesc <> O.progDesc "")

myCommands :: Parser a
myCommands =
  O.subparser $
    O.command
      "myCommand"
      ( O.info
          O.empty
          (O.progDesc "description here")
      )
      <> O.style OH.green

but I got the "metavar" part of it colored green instead, not myCommand which is what I'm looking for:
image

@ghost ghost changed the title How do I color a command name? How do I print help info with colored command names? May 26, 2023
@HuwCampbell
Copy link
Collaborator

Sorry this isn't possible right now.

@Martinsos
Copy link

Ah that is too bad!

Before optparse-applicative, our help looked like this:
image

but now, with optparse, it looks like this:

image

It is not terrible, but we had titles for the groups of commands bold, and commands where yellowish, and we would like to keep those styles.

Are there any considerations to add this in the future?
If we wanted to make a PR for it ourselves, could you recommend a place where to start, the direction?

@HuwCampbell
Copy link
Collaborator

Yeah.

So one of the things I'm considering is adding semantic annotations to the pretty printer document which are then compiled to ANSI codes.

Thanks for sharing.

@HuwCampbell
Copy link
Collaborator

Just a hint, the extra (Command | Command) can be fixed with hidden on one of them.

@Martinsos
Copy link

Martinsos commented Jun 1, 2023

Yeah.

So one of the things I'm considering is adding semantic annotations to the pretty printer document which are then compiled to ANSI codes.

Thanks for sharing.

That sounds interesting.

I was hoping there would be some easy way to provide our own logic for "styling" the help output. For example, I could provide a function that takes subgroup name as an input and returns it stylized (well, surrounded with ANSI codes in my case but could be anything, emojis or something) and then that is used in its place. There would also be a function for command names.

EDIT: I am guessing that might mess up the length calculation for those, which is important for correct indentation / wrapping, but we can solve that by having the function return visual length.

@HuwCampbell
Copy link
Collaborator

The way forward is to use a SemanticDoc type internally, which annotates the Doc tree with what each printed item is (like, a command, or optional flag).

The user facing API should stay the same.

Then, the help generator would take those annotations, and convert them to ANSI codes.

I'm happy to consider PRs but I'm also pretty cautious here. Migrating to prettyprinter had some unexpected exponential blow ups.

Things to consider are opting out with the NOCOLOUR environment variable and in via modifiers.

@Martinsos
Copy link

So something like:

data HelpPrinterOptions = HelpPrinterOptions {
  showSubgroup :: Text -> (Text, Int),
  showCommandName :: Text -> (Text, Int),
  showCommandUsage :: Text -> (Text, Int)
}

and then I could do something like

HelpPrinterOptions {
  showSubgroup = \subgroupName -> (Term.style [Term.Bold] subgroupName, length subgroupName),
  showCommandName = ...,
  showCommandUsage = \u -> (u, length u)
}

@Martinsos
Copy link

The way forward is to use a SemanticDoc type internally, which annotates the Doc tree with what each printed item is (like, a command, or optional flag).

The user facing API should stay the same.

Then, the help generator would take those annotations, and convert them to ANSI codes.

I'm happy to consider PRs but I'm also pretty cautious here. Migrating to prettyprinter had some unexpected exponential blow ups.

Things to consider are opting out with the NOCOLOUR environment variable and in via modifiers.

I don't quite get how the styling would work then -> you would have predefined styles? Could user, via API, define styles for subgroups / commands? If API doesn't change, I don't see how they could?

@HuwCampbell
Copy link
Collaborator

There would be a new modifier to override reannotation.

The default would do nothing, but you could, for example, make optional things dull.

@HuwCampbell
Copy link
Collaborator

I should have said wouldn't change in a breaking way instead of stay the same.

@Martinsos
Copy link

Martinsos commented Jun 1, 2023

Thanks @HuwCampbell -> I don't quite understand the details of what you described, as I probably don't know enough about optparse yet. I am most interested if this would allow relatively a lot of freedom to style our help usage -> but if I got it right, it would not, and would still be quite limited in what it offers?

I was hoping for such simple interface as the one I described, but I undrestand you had bad experience with pretty printing before. Still, to me it does sound like a feature that couldn't mess up much and would be quite on the edge of the existing logic -> but I don't know anything about the optparse codebase.

Thanks for your time to discuss this, I won't bother you more at the moment as I think the initial goal of sketching out some directions was accomplished.

@HuwCampbell
Copy link
Collaborator

Sorry I was on my phone so my responses were a bit brief. I was mostly talking about internals, but I think the end result will allow the sort of customisation you're after.

@Martinsos
Copy link

Sorry I was on my phone so my responses were a bit brief. I was mostly talking about internals, but I think the end result will allow the sort of customisation you're after.

Ah ok that does make more sense now! I don't know much about internals so I couldn't make much out of it. Thanks!

@Martinsos
Copy link

I read a bit of optparse internals now -> I get what you were saying about the Doc now. It is structure offered by prettyprinter package, and it is what prettyprinter takes an argument when printing.

What you do is convert Parser into ParserHelp, where ParserHelp is really just a bunch of Docs.
I believe that happens here https://github.com/pcapriotti/optparse-applicative/blob/master/src/Options/Applicative/Help/Core.hs#L238 ?
At that moment we lose information about which node in Doc is what -> semantic info.

I can see two solutions:

  1. Here, at the moment of conversion to Doc, use the user-provided functions for pretty printing when creating Doc nodes -> apply them while creating Doc nodes, while we still have semantic info.
  2. Ensure semantic knowledge about nodes is preserved in the Doc nodes. Since Doc is defined as Doc ann (based on https://hackage.haskell.org/package/prettyprinter-1.7.1/docs/Prettyprinter.html), I believe we just need to modify this inner type so that it contains more information -> to also contain information about what is the type of the node (usage, option name, subgroup title, ...)? And then we could use that in the Pretty module itself to look up appropriate user-defined functions for pretty printing and apply them at the right moment.

What makes this a bit hard for me is that I don't have experience with free monads so I haven't yet grasped what Chunk is. Also, code is a bit dense at some points (short names, plenty of operators) so some parts will take me a bit longer to decifer.

Does this make sense as a general direction?

  • You were mentioning SemanticDoc -> I am not sure if that is some existing type (couldn't find it) or is it a data type you would like to define?
  • You mentioned modifiers -> so you would allow user to provide "style" modifiers for each part of the Parser? That sounds interesting, a bit more control than I imagined, I imagined they would insetad define styles for types of elements (usage, title, option, ...).

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 a pull request may close this issue.

2 participants