docs: generate CLI help (#1785)

This commit is contained in:
Dominik Menke 2023-01-08 14:53:15 +01:00 committed by GitHub
parent 1cad41db65
commit 091e03f071
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 317 additions and 123 deletions

View file

@ -204,7 +204,6 @@
[[issues.exclude-rules]]
path = "providers/dns/sakuracloud/client.go"
text = "mu is a global variable"
[[issues.exclude-rules]]
path = "providers/dns/hosttech/internal/client_test.go"
text = "Duplicate words \\(0\\) found"

View file

@ -74,7 +74,7 @@ generate-dns:
validate-doc: generate-dns
validate-doc: DOC_DIRECTORIES := ./docs/ ./cmd/
validate-doc:
if git diff --exit-code --quiet $(DOC_DIRECTORIES) 2>/dev/null; then \
@if git diff --exit-code --quiet $(DOC_DIRECTORIES) 2>/dev/null; then \
echo 'All documentation changes are done the right way.'; \
else \
echo 'The documentation must be regenerated, please use `make generate-dns`.'; \

View file

@ -3,7 +3,6 @@ package cmd
import (
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
@ -28,16 +27,16 @@ func createDNSHelp() *cli.Command {
func dnsHelp(ctx *cli.Context) error {
code := ctx.String("code")
if code == "" {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0)
ew := &errWriter{w: w}
ew.writeln(`Credentials for DNS providers must be passed through environment variables.`)
ew.writeln()
ew.writeln(`To display the documentation for a DNS providers:`)
ew.writeln(`To display the documentation for a specific DNS provider, run:`)
ew.writeln()
ew.writeln("\t$ lego dnshelp -c code")
ew.writeln()
ew.writeln("All DNS codes:")
ew.writeln("Supported DNS providers:")
ew.writef("\t%s\n", allDNSCodes())
ew.writeln()
ew.writeln("More information: https://go-acme.github.io/lego/dns")
@ -49,7 +48,7 @@ func dnsHelp(ctx *cli.Context) error {
return w.Flush()
}
return displayDNSHelp(strings.ToLower(code))
return displayDNSHelp(ctx.App.Writer, strings.ToLower(code))
}
type errWriter struct {

View file

@ -56,24 +56,27 @@ func createRenew() *cli.Command {
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
&cli.BoolFlag{
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
" Only works if the CSR is generated by lego.",
},
&cli.StringFlag{
Name: "renew-hook",
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
},
&cli.StringFlag{
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." +
" If no match, the default offered chain will be used.",
},
&cli.StringFlag{
Name: "always-deactivate-authorizations",
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
},
&cli.BoolFlag{
Name: "no-random-sleep",
Usage: "Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way.",
Name: "no-random-sleep",
Usage: "Do not add a random sleep before the renewal." +
" We do not recommend using this flag if you are doing your renewals in an automated way.",
},
},
}

View file

@ -18,8 +18,13 @@ func createRevoke() *cli.Command {
Usage: "Keep the certificates after the revocation instead of archiving them.",
},
&cli.UintFlag{
Name: "reason",
Usage: "Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. 0(unspecified),1(keyCompromise),2(cACompromise),3(affiliationChanged),4(superseded),5(cessationOfOperation),6(certificateHold),8(removeFromCRL),9(privilegeWithdrawn),10(aACompromise)",
Name: "reason",
Usage: "Identifies the reason for the certificate revocation." +
" See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." +
" Valid values are:" +
" 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged)," +
" 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL)," +
" 9 (privilegeWithdrawn), or 10 (aACompromise).",
Value: acme.CRLReasonUnspecified,
},
},

View file

@ -36,16 +36,18 @@ func createRun() *cli.Command {
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
&cli.BoolFlag{
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
" Only works if the CSR is generated by lego.",
},
&cli.StringFlag{
Name: "run-hook",
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
},
&cli.StringFlag{
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." +
" If no match, the default offered chain will be used.",
},
&cli.StringFlag{
Name: "always-deactivate-authorizations",

View file

@ -77,8 +77,9 @@ func CreateFlags(defaultPath string) []cli.Flag {
Value: "Host",
},
&cli.StringFlag{
Name: "http.webroot",
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge",
Name: "http.webroot",
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge." +
" This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge",
},
&cli.StringSliceFlag{
Name: "http.memcached-host",
@ -102,8 +103,10 @@ func CreateFlags(defaultPath string) []cli.Flag {
Usage: "By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.",
},
&cli.StringSliceFlag{
Name: "dns.resolvers",
Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
Name: "dns.resolvers",
Usage: "Set the resolvers to use for performing recursive DNS queries." +
" Supported: host:port." +
" The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
},
&cli.IntFlag{
Name: "http-timeout",

View file

@ -5,7 +5,7 @@ package cmd
import (
"fmt"
"os"
"io"
"sort"
"strings"
"text/tabwriter"
@ -127,8 +127,8 @@ func allDNSCodes() string {
return strings.Join(providers, ", ")
}
func displayDNSHelp(name string) error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
func displayDNSHelp(w io.Writer, name string) error {
w = tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
ew := &errWriter{w: w}
switch name {
@ -2439,9 +2439,8 @@ func displayDNSHelp(name string) error {
return fmt.Errorf("%q is not yet supported", name)
}
if ew.err != nil {
return fmt.Errorf("error: %w", ew.err)
if flusher, ok := w.(interface{ Flush() error }); ok {
return flusher.Flush()
}
return w.Flush()
return nil
}

View file

@ -8,91 +8,7 @@ weight: 4
## Usage
{{< tabs >}}
{{% tab name="lego --help" %}}
```slim
NAME:
lego - Let's Encrypt client written in Go
USAGE:
lego [global options] command [command options] [arguments...]
COMMANDS:
run Register an account, then create and install a certificate
revoke Revoke a certificate
renew Renew a certificate
dnshelp Shows additional help for the '--dns' global option
list Display certificates and accounts information.
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--domains value, -d value Add a domain to the process. Can be specified multiple times.
--server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory")
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false)
--email value, -m value Email used for registration and recovery contact.
--csr value, -c value Certificate signing request filename, if an external CSR is to be used.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false)
--kid value Key identifier from External CA. Used for External Account Binding.
--hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.
--key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "ec256")
--filename value (deprecated) Filename of the generated certificate.
--path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH]
--http Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges. (default: false)
--http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80")
--http.proxy-header value Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. (default: "Host")
--http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge
--http.memcached-host value Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.
--tls Use the TLS challenge to solve challenges. Can be mixed with other types of challenges. (default: false)
--tls.port value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port. (default: ":443")
--dns value Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.
--dns.disable-cp By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers. (default: false)
--dns.resolvers value Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. (default: 10)
--pem Generate a .pem file by concatenating the .key and .crt files together. (default: false)
--pfx Generate a .pfx (PKCS#12) file by with the .key and .crt and issuer .crt files together. (default: false)
--pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit")
--cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--help, -h show help (default: false)
--version, -v print the version (default: false)
```
{{% /tab %}}
{{% tab name="lego run --help" %}}
```slim
NAME:
lego run - Register an account, then create and install a certificate
USAGE:
lego run [command options] [arguments...]
OPTIONS:
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
--run-hook value Define a hook. The hook is executed when the certificates are effectively created.
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
```
{{% /tab %}}
{{% tab name="lego renew --help" %}}
```slim
NAME:
lego renew - Renew a certificate
USAGE:
lego renew [command options] [arguments...]
OPTIONS:
--days value The number of days left on a certificate to renew it. (default: 30)
--reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false)
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
--renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed.
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
--no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false)
```
{{% /tab %}}
{{< /tabs >}}
{{< clihelp >}}
When using the standard `--path` option, all certificates and account configurations are saved to a folder `.lego` in the current working directory.

131
docs/data/zz_cli_help.toml Normal file
View file

@ -0,0 +1,131 @@
# THIS FILE IS AUTO-GENERATED. PLEASE DO NOT EDIT.
[[command]]
title = "lego help"
content = """
NAME:
lego - Let's Encrypt client written in Go
USAGE:
lego [global options] command [command options] [arguments...]
COMMANDS:
run Register an account, then create and install a certificate
revoke Revoke a certificate
renew Renew a certificate
dnshelp Shows additional help for the '--dns' global option
list Display certificates and accounts information.
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false)
--cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--csr value, -c value Certificate signing request filename, if an external CSR is to be used.
--dns value Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. (default: 10)
--dns.disable-cp By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers. (default: false)
--dns.resolvers value [ --dns.resolvers value ] Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--domains value, -d value [ --domains value, -d value ] Add a domain to the process. Can be specified multiple times.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false)
--email value, -m value Email used for registration and recovery contact.
--filename value (deprecated) Filename of the generated certificate.
--help, -h show help (default: false)
--hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.
--http Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges. (default: false)
--http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
--http.memcached-host value [ --http.memcached-host value ] Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.
--http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80")
--http.proxy-header value Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. (default: "Host")
--http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge
--key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "ec256")
--kid value Key identifier from External CA. Used for External Account Binding.
--path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH]
--pem Generate a .pem file by concatenating the .key and .crt files together. (default: false)
--pfx Generate a .pfx (PKCS#12) file by with the .key and .crt and issuer .crt files together. (default: false)
--pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit")
--server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory")
--tls Use the TLS challenge to solve challenges. Can be mixed with other types of challenges. (default: false)
--tls.port value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port. (default: ":443")
--user-agent value Add to the user-agent sent to the CA to identify an application embedding lego-cli
"""
[[command]]
title = "lego help run"
content = """
NAME:
lego run - Register an account, then create and install a certificate
USAGE:
lego run [command options] [arguments...]
OPTIONS:
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--run-hook value Define a hook. The hook is executed when the certificates are effectively created.
"""
[[command]]
title = "lego help renew"
content = """
NAME:
lego renew - Renew a certificate
USAGE:
lego renew [command options] [arguments...]
OPTIONS:
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
--days value The number of days left on a certificate to renew it. (default: 30)
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
--no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false)
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed.
--reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false)
"""
[[command]]
title = "lego help revoke"
content = """
NAME:
lego revoke - Revoke a certificate
USAGE:
lego revoke [command options] [arguments...]
OPTIONS:
--keep, -k Keep the certificates after the revocation instead of archiving them. (default: false)
--reason value Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. Valid values are: 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged), 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL), 9 (privilegeWithdrawn), or 10 (aACompromise). (default: 0)
"""
[[command]]
title = "lego help list"
content = """
NAME:
lego list - Display certificates and accounts information.
USAGE:
lego list [command options] [arguments...]
OPTIONS:
--accounts, -a Display accounts. (default: false)
--names, -n Display certificate common names only. (default: false)
"""
[[command]]
title = "lego dnshelp"
content = """
Credentials for DNS providers must be passed through environment variables.
To display the documentation for a specific DNS provider, run:
$ lego dnshelp -c code
Supported DNS providers:
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, desec, designate, digitalocean, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, ns1, oraclecloud, otc, ovh, pdns, porkbun, rackspace, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, servercow, simply, sonic, stackpath, tencentcloud, transip, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, wedos, yandex, yandexcloud, zoneee, zonomi
More information: https://go-acme.github.io/lego/dns
"""

View file

@ -0,0 +1,24 @@
<div class="tab-panel">
<div class="tab-nav">
{{ $commands := index $.Site.Data.zz_cli_help "command" }}
{{ range $idx, $tab := $commands }}
<button
data-tab-item="{{ .title }}"
data-tab-group="cli-help"
class="tab-nav-button btn {{ cond (eq $idx 0) "active" ""}}"
onclick="switchTab('cli-help','{{ .title }}')"
>{{ .title }}</button>
{{ end }}
</div>
<div class="tab-content">
{{ range $idx, $tab := $commands }}
<div
data-tab-item="{{ .title }}"
data-tab-group="cli-help"
class="tab-item {{ cond (eq $idx 0) "active" ""}}"
>
<pre>{{ .content }}</pre>
</div>
{{ end }}
</div>
</div>

View file

@ -0,0 +1,114 @@
package main
//go:generate go run .
import (
"bytes"
"fmt"
"log"
"os"
"strings"
"text/template"
"github.com/go-acme/lego/v4/cmd"
"github.com/urfave/cli/v2"
)
const outputFile = "../../../docs/data/zz_cli_help.toml"
const baseTemplate = `# THIS FILE IS AUTO-GENERATED. PLEASE DO NOT EDIT.
{{ range .}}
[[command]]
title = "{{.Title}}"
content = """
{{.Help}}
"""
{{end -}}
`
type commandHelp struct {
Title string
Help string
}
func main() {
log.SetFlags(0)
err := generate()
if err != nil {
log.Fatal(err)
}
log.Println("cli_help.toml updated")
}
func generate() error {
app := createStubApp()
outputTpl := template.Must(template.New("output").Parse(baseTemplate))
// collect output of various help pages
var help []commandHelp
for _, args := range [][]string{
{"lego", "help"},
{"lego", "help", "run"},
{"lego", "help", "renew"},
{"lego", "help", "revoke"},
{"lego", "help", "list"},
{"lego", "dnshelp"},
} {
content, err := run(app, args)
if err != nil {
return fmt.Errorf("running %s failed: %w", args, err)
}
help = append(help, content)
}
f, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("cannot open cli_help.toml: %w", err)
}
err = outputTpl.Execute(f, help)
defer func() { _ = f.Close() }()
if err != nil {
return fmt.Errorf("failed to write cli_help.toml: %w", err)
}
return nil
}
// createStubApp Construct cli app, very similar to cmd/lego/main.go.
// Notable differences:
// - substitute "." for CWD in default config path, as the user will very likely see a different path
// - do not include version information, because we're likely running against a snapshot
// - skip DNS help and provider list, as initialization takes time, and we don't generate `lego dns --help` here.
func createStubApp() *cli.App {
app := cli.NewApp()
app.Name = "lego"
app.HelpName = "lego"
app.Usage = "Let's Encrypt client written in Go"
app.Flags = cmd.CreateFlags("./.lego")
app.Commands = cmd.CreateCommands()
return app
}
func run(app *cli.App, args []string) (h commandHelp, err error) {
w := app.Writer
defer func() { app.Writer = w }()
var buf bytes.Buffer
app.Writer = &buf
if err := app.Run(args); err != nil {
return h, err
}
return commandHelp{
Title: strings.Join(args, " "),
Help: strings.TrimSpace(buf.String()),
}, nil
}

View file

@ -5,7 +5,7 @@ package cmd
import (
"fmt"
"os"
"io"
"sort"
"strings"
"text/tabwriter"
@ -22,8 +22,8 @@ func allDNSCodes() string {
return strings.Join(providers, ", ")
}
func displayDNSHelp(name string) error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
func displayDNSHelp(w io.Writer, name string) error {
w = tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
ew := &errWriter{w: w}
switch name {
@ -55,9 +55,8 @@ func displayDNSHelp(name string) error {
return fmt.Errorf("%q is not yet supported", name)
}
if ew.err != nil {
return fmt.Errorf("error: %w", ew.err)
if flusher, ok := w.(interface{ Flush() error }); ok {
return flusher.Flush()
}
return w.Flush()
}
return nil
}