go-arg icon indicating copy to clipboard operation
go-arg copied to clipboard

Custom help text?

Open Lilja opened this issue 3 years ago • 5 comments

Is it possible to add custom help text? I'm using a custom type with a pointer receiver UnmarshalText function which takes "columns" as inputs, which is a series of columns(in the sense of a table) and validates these according to the iota.

This works fine and my validation does correct work. However, the help text isn't really helpful. Imagine if --columns took a series of columns, like --columns A,B. Which then in my "enum"

type Column int
const (
    A = iota
    B
)

type Config struct {
    Columns []column `help:"${someCoolWayToGetStringsJoinedWithCommaAsDelimiter}`
}

Lilja avatar Apr 15 '21 20:04 Lilja

Hey @Lilja- thank you for posting this. What would you like the help text to be in this case? Which strings would you like to join together?

alexflint avatar Apr 17 '21 03:04 alexflint

I think it all boils down to the question:

What values can I supply to this flag?

I'll try to share my problem with some of the context of what I'm trying to build. I'm building a SSH config parser, which read the ~/.ssh/config file and prints these out with a tool like FZF, and on enter you can ssh to it. Here is a screenshot from my python implementation. The related bit of my script to this project is to customize what columns to show in the FZF menu/filter.

In my case I have a couple of columns:

type Column int
const (
    Host = iota
    Username
    Port
)

And for my custom validation

// A helper to show translate strings into this "iota type" (new to go, sorry if I call things incorrectly)
var columnKeys = map[Column]string {
    "Host": Host,
    "Username": Username,
    "Port": Port,
}

type Columns = []Column

type Config struct {
    // Help message not ideal, doesn't show what values are accepted.
    Columns Columns `Help:"The columns to show"`
}

// and my proper validation goes something like this:
func (columns *Columns) UnmarshalText(b []byte) error {
    // Behind the scenes some split by comma and then a for loop for each element. Like --column Host,Username
    v, safe := columnKeys[string(b)]
    if !safe {
        return errors.New("Invalid value")
    }
    *columns = append(*columns, v)
    return nil
}

Now what if --columns is supplied --columns Nope, the usage string will print out that it's an invalid value(as per my error in the custom validation). All right, I'll run the program with -h and try to see what is accepted here. In this case it says --columns: The columns to show. That isn't really helpful. Now as a potential user I would ask what can I supply to this flag? I could start adding these into the string:

type Config struct {
    Columns Columns `Help:"The columns to show. Possible value: Host,Username,Port"`
}

But that isn't very maintainable as it splits the source of truth into two places. Now if I want to add an entry into the Column type I have to add it twice.

It would be sweet if I could supply some kind of function here:

type Config struct {
    // Bear in mind my Javascript syntax here. I'm more familiar with Javascript.
    Columns Columns `Help:"The columns to show, possible values: Object.keys(columnKeys)"`
}

Or maybe some kind of custom help-message function?

Lilja avatar Apr 17 '21 08:04 Lilja

I see. Thank you for explaining this, @Lilja. I understand your need. It seems reasonable to me. One way we could do it is with a convention where you can put a function on your struct to customize the help text for any given struct field. For example:

type Config struct {
  Columns Columns
}

func (c *Config) HelpTextForColumns() string {
  // do some computation here
}

Under this approach we would just make it a convention that for a field named X, you name your function HelpTextForX.

Another way would be to specify the function to call as a struct tag:

type Config struct {
  Columns Columns `helpfunc:MyCustomHelpText`
}

func (c *Config) MyCustomHelpText() string {
  // do some computation here
}

The main advantage of this approach that I see is that it would make it more obvious to people reading the code that this field does in fact have help text and that it is generated programmatically by a function.

A third approach is that you could use go:generate to slot the help text into the struct tag at "go generate" time.

A fourth approach is that you could just keep the tag updated manually, as you mention.

I will think about this some more. Thanks for taking the time to write this issue.

alexflint avatar Apr 18 '21 01:04 alexflint

For what it's worth I like this approach:

type Config struct {
  Columns Columns `helpfunc:MyCustomHelpText`
}

func (c *Config) MyCustomHelpText() string {
  // do some computation here
}

Lilja avatar Apr 18 '21 09:04 Lilja

Hello, We have been waiting for this for a long time! This is a must-have feature for where you want to translate your app. e.g using https://github.com/nicksnyder/go-i18n package.

Unfortunately, couldn't find any other alternatives that support this. However, i like this approach:

type Config struct {
  Columns Columns
}

func (c *Config) HelpTextForColumns() string {
	localizer := i18n.NewLocalizer(bundle, "en")
	result, _ := localizer.Localize(
		&i18n.LocalizeConfig{
			DefaultMessage: &i18n.Message{
				ID:    "HelpText",
				Other: "Nick has 2 cats.",
			},
		},
	)
	return result
}

GibMeMyPacket avatar May 01 '23 18:05 GibMeMyPacket