Cobra, in its default configuration, will execute a template to generate
help, usage and version outputs. Text/template execution calls MethodByName
and MethodByName disables dead code elimination in the Go linker, therefore
all programs that make use of cobra will be linked with dead code
elimination disabled, even if they end up replacing the default usage, help
and version formatters with a custom function and no actual text/template
evaluations are ever made at runtime.
Dead code elimination in the linker helps reduce disk space and memory
utilization of programs. For example, for the simple example program used by
TestDeadcodeElimination 40% of the final executable size is dead code. For a
more realistic example, 12% of the size of Delve's executable is deadcode.
This PR changes Cobra so that, in its default configuration, it does not
automatically inhibit deadcode elimination by:
1. changing Cobra's default behavior to emit output for usage and help using
simple Go functions instead of template execution
2. quarantining all calls to template execution into SetUsageTemplate,
SetHelpTemplate and SetVersionTemplate so that the linker can statically
determine if they are reachable
The completion code attempts to detect whether a flag can be specified
more than once, and therefore should provide completion even if already
set.
Currently, this code depends on conventions used in the pflag package,
which uses an "Array" or "Slice" suffix or for some types a "stringTo"
prefix.
Cobra allows custom value types to be used, which may not use the same
convention for naming, and therefore currently aren't detected to allow
multiple values.
The pflag module defines a [SliceValue] interface, which is implemented
by the Slice and Array value types it provides (unfortunately, it's not
currently implemented by the "stringTo" values).
This patch adds a reduced interface based on the [SliceValue] interface
mentioned above to allow detecting Value-types that accept multiple values.
Custom types can implement this interface to make completion work for
those values.
I deliberately used a reduced interface to keep the requirements for this
detection as low as possible, without enforcing the other methods defined
in the interface (Append, Replace) which may not apply to all custom types.
Future improvements can likely still be made, considering either implementing
the SliceValue interface for the "stringTo" values or defining a separate
"MapValue" interface for those types.
Possibly providing the reduced interface as part of the pflag module and
to export it.
[SliceValue]: d5e0c0615a/flag.go (L193-L203)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>
Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
When running tests in verbose mode (or other options), tests involving
Cobra may fail if the test does not explicitly set Command.args to an
empty slice; in this case, Cobra defaults to using `os.Args`, which
will contain arguments passed to the test (such as `-v` (verbose)).
Commits e5762054c9 and 1ef0913976
implemented a workaround for this when running (unit) tests for Cobra
itself, but this check is specifig to Cobra (checking for `cobra.test`),
and don't work on Windows (which will have a `.exe` extension),
This patch implements a more universal check, so that users of Cobra
as a module also benefit from this workaround.
go1.21 and up provides a `testing.Testing()` utility ([1]); as the Cobra
module still supports Go1.16 and up, an alternative implementation was
added for older versions, based on golang.org/x/mod/lazyregexp [2].
Before this patch:
go test -c -o foo.test
./foo.test -test.run TestNoArgs
--- FAIL: TestNoArgs (0.00s)
args_test.go:37: Unexpected output: Error: unknown command "TestNoArgs" for "c"
Usage:
c [flags]
Flags:
-h, --help help for c
args_test.go:40: Unexpected error: unknown command "TestNoArgs" for "c"
FAIL
After this patch:
go test -c -o foo.test
./foo.test -test.run TestNoArgs
PASS
[1]: https://pkg.go.dev/testing#Testing
[2]: https://cs.opensource.google/go/x/mod/+/refs/tags/v0.19.0:internal/lazyregexp/lazyre.go;l=66-78
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Marc Khouzam <marc.khouzam@gmail.com>
Co-authored-by: Marc Khouzam <marc.khouzam@gmail.com>
Since cpuguy83/go-md2man 2.0.5 no paraTag is written after "SEE ALSO".
With go-md2man 2.0.4:
.SH SEE ALSO
.PP
\fBroot-bbb(1)\fP, \fBroot-ccc(1)\fP
With go-md2man 2.0.5:
.SH SEE ALSO
\fBroot-bbb(1)\fP, \fBroot-ccc(1)\fP
See: https://github.com/cpuguy83/go-md2man/pull/122
Signed-off-by: Mikel Olasagasti Uranga <mikel@olasagasti.info>
Creating CompletionResult objects is not allowed in Powershell constrained mode, so return results as strings if constrained mode is enabled
Store results as PsCustomObjects instead of hashtables. This prevents Sort-Object from trying to convert the hashtable to a object, which is blocked in constrained mode.
PsCustomObjects are created using New-Object to work around https://github.com/PowerShell/PowerShell/issues/20767
* Fix --version help with CommandDisplayNameAnnotation
When setting Command.Version, a --version option is added. The help
message for the --version command did not consider the command display
name:
Flags:
-h, --help help for kubectl plugin
-v, --version version for kubectl-plugin
With this change the help test is consistent with other flags:
Flags:
-h, --help help for kubectl plugin
-v, --version version for kubectl plugin
* Make command DisplayName() public
This allows using the display name in templates or other code that want
to use the same value.
* Use display name in version template
The version template used `{{.Name}}` but for plugins you want to use
`{{.DisplayName}}` to be consistent with other help output.
With this change will show:
$ kubectl plugin --version
kubectl plugin version 1.0.0
* Improve site formatting
- Separate titles with blank lines
- Separate code blocks with blank lines
- Always use ``` blocks for examples
- Use console for console (bash syntax highlighting does work well with
example command output)
- Start console examples with $ (highlight command and output
differently and more friendly to other shells users)
* Unify indentation in example project structure
* Use single import line in the trivial examples
When we want to show a minimal example with single import it looks
cleaner and more minimal with a single line.
Fixing golangci-lint errors[1]:
Error: SA1019: "io/ioutil" has been deprecated since Go 1.19: As of
Go 1.16, the same functionality is now provided by package [io] or
package [os], and those implementations should be preferred in new
code. See the specific function documentation for details.
(staticcheck)
[1] https://github.com/spf13/cobra/actions/runs/10535452454/job/29194442289?pr=2180
Deprecation comments should be at the start of a paragraph [1], and because
of that have a whitespace above them [2];
> To signal that an identifier should not be used, add a paragraph to its
> doc comment that begins with Deprecated: followed by some information
> about the deprecation (...)
With the whitespace missing, some tools, including pkg.go.dev [3] don't
detect it to be deprecated.
[1]: https://go.dev/wiki/Deprecated
[2]: https://go.dev/doc/comment#paragraphs
[3]: https://pkg.go.dev/github.com/spf13/cobra@v1.8.1#Command.SetOutput
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
* Address golangci-lint linter deprecation warnings
1.59.0 outputs:
WARN [lintersdb] The name "gas" is deprecated. The linter has been renamed to: gosec.
WARN [lintersdb] The linter named "megacheck" is deprecated. It has been split into: gosimple, staticcheck, unused.
* Enable some more linters, address finding
* Remove fully inactivated linters
Currently golangci-lint fails with these errors:
ERRO [linters_context] golint: This linter is fully inactivated: it will not produce any reports.
ERRO [linters_context] interfacer: This linter is fully inactivated: it will not produce any reports.
ERRO [linters_context] maligned: This linter is fully inactivated: it will not produce any reports.
I could not find any docs explaining what "fully inactivated" mean, but
based this PR[1] it seems that these linters do nothing now. Removing
the linters fixes this issue without changing linting, as they did not
produce any report.
Looking in the linters docs[2] I did not find a replacement for
"interfacer" and "malinged" linters. "stylecheck" seems to be a
replacement for "golint", but we need to fix the code to enable it.
[1] https://github.com/golangci/golangci-lint/pull/4436
[2] https://golangci-lint.run/usage/linters/
* Add stylecheck linter, replacement for golint
This revealed 2 capitalized error messages.
https://golangci-lint.run/usage/linters/#stylecheck
Add `Annotation` suffix to the private annotations to allow nicer code
using the constants.
For example one can use the current annotation names as a temporary
variable instead of unclear shortcut. Instead of this:
rag := flagsFromAnnotation(c, f, requiredAsGroup)
me := flagsFromAnnotation(c, f, mutuallyExclusive)
or := flagsFromAnnotation(c, f, oneRequired)
We can use now:
requiredAsGrop := flagsFromAnnotation(c, f, requiredAsGroupAnnotation)
mutuallyExclusive := flagsFromAnnotation(c, f, mutuallyExclusiveAnnotation)
oneRequired := flagsFromAnnotation(c, f, oneRequiredAnnotation)
Example taken from #2105.
When creating a plugin without sub commands, the help text included the
command name (kubectl-plugin) instead of the display name (kubectl plugin):
Usage:
kubectl-plugin [flags]
The issue is that the usage line for this case does not use the
command path but the raw `Use` string, and this case was not tested.
Add a test for this case and fix UsageLine() to replace the command name
with the display name.
Tested using https://github.com/nirs/kubernetes/tree/sample-cli-plugin-help
When using `CommandDisplayNameAnnotation` we want to use it instead of
the command name in `--help` message or in the default help command.
With current code we get the wrong text in the --help usage text:
Flags:
-h, --help help for kubectl-plugin
And in the long description of the default help command:
$ kubectl cobraplugin help -h
Help provides help for any command in the application.
Simply type kubectl-plugin help [path to command] for full details.
The issue was hidden since the test checked only the Usage line.
Fixed by extracting a displayName() function and use it when creating
FlagSet and when formatting the default help flag usage and the help
command long description.
Enhance the TestPlugin to check all the lines including the command
name.
* Avoid redundant string splits
There likely isn't actually more than once to split in the source
strings in these cases, but avoid doing so anyway as we're only
interested in the first.
* Avoid redundant completion output target evaluations
The target is not to be changed while outputting completions, so resolve
it only once.
* Avoid redundant active help enablement evaluations
The enablement state is not to be changed during completion output, so
evaluate it only once.
* Preallocate some slices and maps with known size
* Avoid some unnecessary looping
* Use strings.Builder to construct suggestions