-
Notifications
You must be signed in to change notification settings - Fork 26
Wrapper around help text #11
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
Conversation
ff5026a
to
856132f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! 👍
entrypoint/help_utils.go
Outdated
|
||
const HELP_TEXT_LINE_WIDTH = 80 | ||
|
||
const CLI_APP_HELP_TEMPLATE = `Usage: {{.HelpName}} {{range $index, $option := .VisibleFlags}}[{{$option.GetName | PrefixedFirstFlagName}}] {{end}}{{if .Commands}}command [options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[args]{{end}}{{if .Description}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is different about these templates than the default urfave ones? May be worth mentioning in a comment.
entrypoint/help_utils.go
Outdated
// text := "one two three" | ||
// out := SplitKeepDelimiter(re, text) | ||
// out == ["one ", "two ", "three"] | ||
func SplitKeepDelimiter(re *regexp.Regexp, str string) (out []string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why declare out
in the return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, does SplitAfterN not do the same thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why declare out in the return value?
No strong reason, just that I noticed a few places in sample go code preferring to declare in return value if you are just going to var out
. Is it generally preferable to declare return vars in the function?
Also, does SplitAfterN not do the same thing?
strings.SplitAfter
does do the same thing, but it requires a specific substring. Here, we need to split on regex because the separator is volatile (at least one whitespace char) and so there is no exact match string that can be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it generally preferable to declare return vars in the function?
I don't know what "idiomatic" Go code does (TBH, I'm not sure it's mature enough of a language for that to be well defined), but I find it easier to read and reason about the code if all variable declarations are in the body of the method, rather than some there and some in the return value.
strings.SplitAfter does do the same thing, but it requires a specific substring. Here, we need to split on regex because the separator is volatile (at least one whitespace char) and so there is no exact match string that can be used.
Ah, gotcha.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed this func to RegexpSplitAfter
to make it clearer that this is the regex version of strings.SplitAfter
.
entrypoint/help_utils.go
Outdated
} | ||
|
||
// Wrap text to line width, while preserving any indentation | ||
func TabAwareWrapText(text string, lineWidth int, tab string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add some examples to the comments to provide context on what this function is doing
func NewApp() *cli.App { | ||
cli.HelpPrinter = WrappedHelpPrinter | ||
cli.AppHelpTemplate = CLI_APP_HELP_TEMPLATE | ||
cli.CommandHelpTemplate = CLI_COMMAND_HELP_TEMPLATE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, in some of our apps, we overwrite these help templates. E.g., Terragrunt: https://github.com/gruntwork-io/terragrunt/blob/master/cli/cli_app.go#L151 (note, we have not yet migrated Terragrunt to use gruntwork-cli
under the hood, so it's just an example). Will the wrapped help text work for these apps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should work. Actually, these are global variables in urfave/cli
that are read in as part of the help
command, so a later overwrites will take precedence. You just have to override after the NewApp
call, as opposed to before:
cli := entrypoint.NewAll()
cli.AppHelpTemplate = HELP_TEMPLATE
That said, it doesn't handle the elastic tabstop output table so for that example the output is not so nice (pasted below). Once I switched the template to raw tabs, it looked better so may not be worth generalizing.
No tabs:
DESCRIPTION:
terragrunt - Terragrunt is a thin wrapper for Terraform that provides extra
tools for working with multiple
Terraform modules, remote state, and locking. For documentation, see
https://github.com/gruntwork-io/terragrunt/.
USAGE:
terragrunt <COMMAND>
COMMANDS:
plan-all Display the plans of a 'stack' by running 'terragrunt
plan' in each subfolder
apply-all Apply a 'stack' by running 'terragrunt apply' in each
subfolder
output-all Display the outputs of a 'stack' by running 'terragrunt
output' in each subfolder
destroy-all Destroy a 'stack' by running 'terragrunt destroy' in
each subfolder
validate-all Validate 'stack' by running 'terragrunt validate' in
each subfolder
* Terragrunt forwards all other commands directly to
Terraform
GLOBAL OPTIONS:
terragrunt-config Path to the Terragrunt config file.
Default is terraform.tfvars.
terragrunt-tfpath Path to the Terraform binary. Default is
terraform (on PATH).
terragrunt-no-auto-init Don't automatically run 'terraform init'
during other terragrunt commands. You must run 'terragrunt init' manually.
terragrunt-no-auto-retry Don't automatically re-run command in
case of transient errors.
terragrunt-non-interactive Assume "yes" for all prompts.
terragrunt-working-dir The path to the Terraform templates.
Default is current directory.
terragrunt-download-dir The path where to download Terraform
code. Default is .terragrunt-cache in the working directory.
terragrunt-source Download Terraform configurations from
the specified source into a temporary folder, and run Terraform in that
temporary folder.
terragrunt-source-update Delete the contents of the temporary
folder to clear out any old, cached source code before downloading new source
code into it.
terragrunt-iam-role Assume the specified IAM role
before executing Terraform.
Can also be set via the
TERRAGRUNT_IAM_ROLE
environment variable.
terragrunt-ignore-dependency-errors *-all commands continue processing
components even if a dependency fails.
terragrunt-exclude-dir Unix-style glob of directories to
exclude when running *-all commands
VERSION:
AUTHOR(S):
Gruntwork <www.gruntwork.io>
with tabs
DESCRIPTION: [65/7627]
terragrunt - Terragrunt is a thin wrapper for Terraform that provides extra
tools for working with multiple
Terraform modules, remote state, and locking. For documentation, see
https://github.com/gruntwork-io/terragrunt/.
USAGE:
terragrunt <COMMAND>
COMMANDS:
plan-all Display the plans of a 'stack' by running 'terragrunt plan'
in each subfolder
apply-all Apply a 'stack' by running 'terragrunt apply' in each
subfolder
output-all Display the outputs of a 'stack' by running 'terragrunt
output' in each subfolder
destroy-all Destroy a 'stack' by running 'terragrunt destroy' in each
subfolder
validate-all Validate 'stack' by running 'terragrunt validate' in each
subfolder
* Terragrunt forwards all other commands directly to Terraform
GLOBAL OPTIONS:
terragrunt-config Path to the Terragrunt config file. Default is
terraform.tfvars.
terragrunt-tfpath Path to the Terraform binary. Default is terraform
(on PATH).
terragrunt-no-auto-init Don't automatically run 'terraform init'
during other terragrunt commands. You must run
'terragrunt init' manually.
terragrunt-no-auto-retry Don't automatically re-run command in case of
transient errors.
terragrunt-non-interactive Assume "yes" for all prompts.
terragrunt-working-dir The path to the Terraform templates. Default is
current directory.
terragrunt-download-dir The path where to download Terraform code.
Default is .terragrunt-cache in the working
directory.
terragrunt-source Download Terraform configurations from the specified
source into a temporary folder, and run Terraform in
that temporary folder.
terragrunt-source-update Delete the contents of the temporary folder
to clear out any old, cached source code
before downloading new source code into it.
terragrunt-iam-role Assume the specified IAM role before executing
Terraform. Can also be set via the
TERRAGRUNT_IAM_ROLE environment variable.
terragrunt-ignore-dependency-errors *-all commands continue processing
components even if a dependency
fails.
terragrunt-exclude-dir Unix-style glob of directories to exclude when
running *-all commands
VERSION:
AUTHOR(S):
Gruntwork <www.gruntwork.io>
// char. | ||
tabLength := 8 | ||
currentLineLength := TabAwareStringLength(wrapped, tabLength) | ||
for _, word := range words[1:] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, this sort of code can be easier to read and maintain if written recursively:
func TabAwareWrapText(words []string, lineWidth int, tab string, outputLinesSoFar []string): string {
if len(words) == 0 {
return strings.Join(outputLinesSoFar, "\n")
}
head = words[0]
tail = words[1:]
if lineLengthExceeded(outputLinesSoFar, head, lineWidth) {
return TabAwareWrapText(tail, lineWidth, tab, addWordOnNewLine(outputLinesSoFar, head))
} else {
return TabAwareWrapText(tail, lineWidth, tab, addWordToExistingLine(outputLinesSoFar, head))
}
}
Not sure it's worth refactoring in this case, as (a) Go isn't exactly a functional programming language and (b) I'm not sure it does tail call optimization, which may be a problem with long strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. I was going to write recursively, but then I learned that the maintainers of Go isn't too keen on supporting tail call optimization so opted not to. Sad :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gah. Stupid Go.
} | ||
} | ||
|
||
var wrapTextTests = []struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably a good idea to add some real-world help text (with many lines) as tests, such as the ones in the PR description.
77fa72f
to
cbbcb92
Compare
UPDATE:
TODO:
|
cbbcb92
to
9e4c64a
Compare
Ok added some unit tests with real world examples for the overall printers. @brikis98 Ready for final review before merge! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great. Nice work!
app := cli.NewApp() | ||
// Create a new CLI app. This will return a urfave/cli App with some | ||
// common initialization. | ||
app := entrypoint.NewApp() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure to call out this change in the release notes!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify, you mean https://github.com/gruntwork-io/gruntwork-cli/releases and newsletter and not a special file in here that tracks changes? Also, this should be a minor release (0.2.0
) because it is a backwards compatible feature release right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes on all counts!
|
||
` | ||
|
||
const EXPECTED_EXEC_CMD_HELP_OUT_120_LINES = `Usage: houston exec [options] <profile> -- <command> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice tests 👍
Ok merging and doing a release! Thanks! |
This adds the wrapped help text printer to
gruntwork-cli
and a function to create a newcli.App
that takes advantage of it.Sample output:
Main help
Original
New:
Command help
Original:
New: