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

Custom help text? #146

Open
Lilja opened this issue Apr 15, 2021 · 5 comments
Open

Custom help text? #146

Lilja opened this issue Apr 15, 2021 · 5 comments

Comments

@Lilja
Copy link

Lilja commented Apr 15, 2021

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}`
}
@alexflint
Copy link
Owner

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?

@Lilja
Copy link
Author

Lilja commented Apr 17, 2021

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?

@alexflint
Copy link
Owner

alexflint commented Apr 18, 2021

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.

@Lilja
Copy link
Author

Lilja commented Apr 18, 2021

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
}

@GibMeMyPacket
Copy link

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
}

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

No branches or pull requests

3 participants