From 15f94234cd360c350b1c41cbdae690f11874d069 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Mon, 6 Oct 2025 19:23:25 -0300 Subject: [PATCH] cmd/loop: add commands "loop man", "loop markdown" Produce the documentations in man .1 and markdown formats. The template for markdown was patched to removed column "Environment variables" Upstream PR: https://github.com/urfave/cli-docs/pull/15 Also the input has to be pre-processed to remove nested "help" subcommands from each subcommand to improve readability. Upstream PR: https://github.com/urfave/cli-docs/pull/16 --- cmd/loop/docs.go | 152 ++++++++++++++++++++++++++++ cmd/loop/main.go | 1 + cmd/loop/markdown_tabular.md.gotmpl | 80 +++++++++++++++ go.mod | 3 + go.sum | 6 ++ 5 files changed, 242 insertions(+) create mode 100644 cmd/loop/docs.go create mode 100644 cmd/loop/markdown_tabular.md.gotmpl diff --git a/cmd/loop/docs.go b/cmd/loop/docs.go new file mode 100644 index 00000000..f5b49a9a --- /dev/null +++ b/cmd/loop/docs.go @@ -0,0 +1,152 @@ +package main + +import ( + "context" + _ "embed" + "fmt" + + docs "github.com/urfave/cli-docs/v3" + "github.com/urfave/cli/v3" +) + +//go:embed markdown_tabular.md.gotmpl +var markdownTabularDocTemplate string + +// We have a copy of this template taken from +// https://github.com/urfave/cli-docs where we remove column +// "Environment variables" if it has no values. +// TODO: remove this when https://github.com/urfave/cli-docs/pull/15 +// is merged. +func init() { + docs.MarkdownTabularDocTemplate = markdownTabularDocTemplate +} + +var printManCommand = &cli.Command{ + Name: "man", + Usage: "prints man file", + Description: "Prints documentation of loop CLI in man format", + Action: printMan, + Hidden: true, +} + +func printMan(_ context.Context, cmd *cli.Command) error { + root := filterNestedHelpCommands(cmd.Root()) + + const userCommandsSection = 1 + man, err := docs.ToManWithSection(root, userCommandsSection) + if err != nil { + return fmt.Errorf("failed to produce man: %w", err) + } + + fmt.Println(man) + + return nil +} + +var printMarkdownCommand = &cli.Command{ + Name: "markdown", + Usage: "prints markdown file", + Description: "Prints documentation of loop CLI in markdown format", + Action: printMarkdown, + Hidden: true, +} + +func printMarkdown(_ context.Context, cmd *cli.Command) error { + root := filterNestedHelpCommands(cmd.Root()) + + md, err := docs.ToTabularMarkdown(root, "loop") + if err != nil { + return fmt.Errorf("failed to produce man: %w", err) + } + + fmt.Println(md) + + return nil +} + +// filterNestedHelpCommands clones cmd, drops nested help commands, and normalises +// flag defaults so generated documentation avoids absolute paths. +func filterNestedHelpCommands(cmd *cli.Command) *cli.Command { + cloned := cloneCommand(cmd, 0) + overrideDocFlags(cloned) + return cloned +} + +// cloneCommand clones the command, filtering out nested "help" subcommands. +func cloneCommand(cmd *cli.Command, depth int) *cli.Command { + if cmd == nil { + return nil + } + + cloned := *cmd + if len(cmd.Commands) == 0 { + return &cloned + } + + filtered := make([]*cli.Command, 0, len(cmd.Commands)) + for _, sub := range cmd.Commands { + if sub == nil { + continue + } + childDepth := depth + 1 + + // TODO: remove when https://github.com/urfave/cli-docs/pull/16 + if childDepth > 0 && sub.Name == "help" { + continue + } + + filtered = append(filtered, cloneCommand(sub, childDepth)) + } + + cloned.Commands = filtered + return &cloned +} + +// overrideDocFlags walks the command tree and replaces string flag defaults +// that leak user-specific filesystem paths, keeping generated docs stable. +func overrideDocFlags(cmd *cli.Command) { + if cmd == nil { + return + } + + if len(cmd.Flags) > 0 { + clonedFlags := make([]cli.Flag, len(cmd.Flags)) + for i, fl := range cmd.Flags { + clonedFlags[i] = cloneFlagWithOverrides(fl) + } + cmd.Flags = clonedFlags + } + + for _, sub := range cmd.Commands { + overrideDocFlags(sub) + } +} + +// docFlagOverrides maps global flag names to the canonical values we want to +// show in documentation instead of user-specific absolute paths. +var docFlagOverrides = map[string]string{ + loopDirFlag.Name: "~/.loop", + tlsCertFlag.Name: "~/.loop/mainnet/tls.cert", + macaroonPathFlag.Name: "~/.loop/mainnet/loop.macaroon", +} + +// cloneFlagWithOverrides returns a copy of flag with overridden default values +// when the flag participates in docFlagOverrides. Non-string flags are reused +// unchanged to minimise allocations. +func cloneFlagWithOverrides(flag cli.Flag) cli.Flag { + sf, ok := flag.(*cli.StringFlag) + if !ok { + return flag + } + + value, ok := docFlagOverrides[sf.Name] + if !ok { + return flag + } + + cloned := *sf + cloned.Value = value + cloned.DefaultText = value + + return &cloned +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index cf71c882..a0f5a5bd 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -90,6 +90,7 @@ var ( setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand, getInfoCommand, abandonSwapCommand, reservationsCommands, instantOutCommand, listInstantOutsCommand, + printManCommand, printMarkdownCommand, } ) diff --git a/cmd/loop/markdown_tabular.md.gotmpl b/cmd/loop/markdown_tabular.md.gotmpl new file mode 100644 index 00000000..4697d33c --- /dev/null +++ b/cmd/loop/markdown_tabular.md.gotmpl @@ -0,0 +1,80 @@ +{{ define "flags" }} +{{- $hasEnvVars := false -}} +{{- range . -}} +{{- if and (not $hasEnvVars) .EnvVars -}} +{{- $hasEnvVars = true -}} +{{- end -}} +{{- end }} +| Name | Description | Type | Default value {{ if $hasEnvVars }}| Environment variables {{ end }}| +|------|-------------|------|:-------------:{{ if $hasEnvVars }}|:---------------------:{{ end }}| +{{ range $flag := . -}} +{{- /**/ -}} | `{{ $flag.Name }}{{ if $flag.TakesValue }}="…"{{ end }}` {{ if $flag.Aliases }}(`{{ join $flag.Aliases "`, `" }}`) {{ end }} +{{- /**/ -}} | {{ $flag.Usage }} +{{- /**/ -}} | {{ $flag.Type }} +{{- /**/ -}} | {{ if $flag.Default }}`{{ $flag.Default }}`{{ end }} +{{- if $hasEnvVars -}} +{{- /**/ -}} | {{ if $flag.EnvVars }}`{{ join $flag.EnvVars "`, `" }}`{{ else }}*none*{{ end }} +{{- end -}} +{{- /**/ -}} | +{{ end }} +{{ end }} + +{{ define "command" }} +### `{{ .Name }}` {{ if gt .Level 0 }}sub{{ end }}command{{ if .Aliases }} (aliases: `{{ join .Aliases "`, `" }}`){{ end }} +{{ if .Usage }} +{{ .Usage }}. +{{ end }} +{{ if .UsageText }} +{{ range $line := .UsageText -}} +> {{ $line }} +{{ end -}} +{{ end }} +{{ if .Description }} +{{ .Description }}. +{{ end }} +Usage: + +```bash +$ {{ .AppPath }} [GLOBAL FLAGS] {{ .Name }}{{ if .Flags }} [COMMAND FLAGS]{{ end }} {{ if .ArgsUsage }}{{ .ArgsUsage }}{{ else }}[ARGUMENTS...]{{ end }} +``` + +{{ if .Flags -}} +The following flags are supported: +{{ template "flags" .Flags }} +{{ end -}} + +{{ if .SubCommands -}} +{{ range $subCmd := .SubCommands -}} +{{ template "command" $subCmd }} +{{ end -}} +{{ end -}} +{{ end }} + +## CLI interface{{ if .Name }} - {{ .Name }}{{ end }} + +{{ if .Description }}{{ .Description }}. +{{ end }} +{{ if .Usage }}{{ .Usage }}. +{{ end }} +{{ if .UsageText }} +{{ range $line := .UsageText -}} +> {{ $line }} +{{ end -}} +{{ end }} +Usage: + +```bash +$ {{ .AppPath }}{{ if .GlobalFlags }} [GLOBAL FLAGS]{{ end }} [COMMAND] [COMMAND FLAGS] {{ if .ArgsUsage }}{{ .ArgsUsage }}{{ else }}[ARGUMENTS...]{{ end }} +``` + +{{ if .GlobalFlags }} +Global flags: + +{{ template "flags" .GlobalFlags }} + +{{ end -}} +{{ if .Commands -}} +{{ range $cmd := .Commands -}} +{{ template "command" $cmd }} +{{ end }} +{{- end }} diff --git a/go.mod b/go.mod index 162d9496..a247e8ba 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/lightningnetwork/lnd/tor v1.1.6 github.com/ory/dockertest/v3 v3.10.0 github.com/stretchr/testify v1.10.0 + github.com/urfave/cli-docs/v3 v3.1.0 github.com/urfave/cli/v3 v3.4.1 go.etcd.io/bbolt v1.3.11 golang.org/x/sync v0.12.0 @@ -72,6 +73,7 @@ require ( github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/lru v1.1.2 // indirect github.com/docker/cli v28.0.1+incompatible // indirect @@ -149,6 +151,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/fastuuid v1.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index 0fdaba69..5c95d9cb 100644 --- a/go.sum +++ b/go.sum @@ -736,6 +736,8 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -1263,6 +1265,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -1309,6 +1313,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4 github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= +github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw= +github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to= github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=