Compare commits

...

5 Commits
v0.0.1 ... main

Author SHA1 Message Date
jolheiser 763d358701
Mention aliases
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2022-09-03 21:40:17 -05:00
jolheiser 359d89bc7d
Add alias support
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2022-09-03 21:32:49 -05:00
jolheiser da2620a37f
Remove From
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2021-10-31 16:04:40 -05:00
jolheiser 4a13872104
Generate docs as READMEs for examples
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2021-10-30 16:52:53 -05:00
jolheiser 79d47ea88b
Add more to README
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2021-10-30 16:51:30 -05:00
12 changed files with 193 additions and 15 deletions

View File

@ -2,6 +2,69 @@
[![Go Reference](https://pkg.go.dev/badge/go.jolheiser.com/ffmd.svg)](https://pkg.go.dev/go.jolheiser.com/ffmd)
One of the things that I really liked about [urfave/cli](https://github.com/urfave/cli) was that it could
generate markdown docs for my commands.
With `ffmd` you can generate markdown docs for both `flag.FlagSet` and `ffcli.Command` (with any amount of sub-commands).
Check out [examples](examples) for some sample generated docs.
## Generate your docs
```go
//go:build generate
// +build generate
package main
import (
"os"
"go.jolheiser.com/ffmd"
)
func main() {
fi, err := os.Create("docs.md")
if err != nil {
panic(err)
}
defer fi.Close()
// cmd is the ffcli.Command
md, err := ffmd.Command(cmd)
if err != nil {
panic(err)
}
if _, err := fi.WriteString(md); err != nil {
panic(err)
}
}
```
## Aliases
While the stdlib doesn't directly support flag aliases, this library allows for neater generation using a special syntax.
```go
package main
import "flag"
func main() {
fs := flag.NewFlagSet("myapp", flag.ExitOnError)
stringFlag := fs.String("string", "", "String flag")
fs.StringVar(stringFlag, "s", *stringFlag, "--string")
}
```
In the above code, a second flag was registered with the special `Usage` that simply declared what the long-form flag name was.
In this scenario, `ffmd` will generate docs similar to
```text
--string,-s
```
See the [example](examples/alias).
## License
[MIT](LICENSE)

View File

@ -5,4 +5,5 @@ generated docs.
- [flagset](flagset) - Using only stdlib `flag.FlagSet`
- [single](single) - Using a single `ffcli.Command`
- [multiple](multiple) - Using `ffcli.Command` with multiple sub-commands
- [multiple](multiple) - Using `ffcli.Command` with multiple sub-commands
- [alias](alias) - Using `ffcli.Command` with alias setup

View File

@ -0,0 +1,39 @@
# myapp
myapp
```
myapp
```
```
[--bool-flag,-bf]
[--duration-flag,-df]=[value]
[--help]
[--int-flag,-if]=[value]
[--string-flag,-sf]=[value]
```
**Usage**:
```
myapp [FLAGS] [ARGS...]
```
**--bool-flag,-bf**: Bool flag
**--duration-flag,-df**="": Duration flag
**--help**: Show help
**--int-flag,-if**="": Int flag
**--string-flag,-sf**="": String flag
-----

View File

@ -0,0 +1,48 @@
//go:build generate
// +build generate
package main
import (
"flag"
"github.com/peterbourgon/ff/v3/ffcli"
"os"
"go.jolheiser.com/ffmd"
)
//go:generate go run main.go
func main() {
fs := flag.NewFlagSet("myapp", flag.ExitOnError)
stringFlag := fs.String("string-flag", "", "String flag")
fs.StringVar(stringFlag, "sf", *stringFlag, "--string-flag")
intFlag := fs.Int("int-flag", 0, "Int flag")
fs.IntVar(intFlag, "if", *intFlag, "--int-flag")
boolFlag := fs.Bool("bool-flag", false, "Bool flag")
fs.BoolVar(boolFlag, "bf", *boolFlag, "--bool-flag")
durFlag := fs.Duration("duration-flag", 0, "Duration flag")
fs.DurationVar(durFlag, "df", *durFlag, "--duration-flag")
cmd := &ffcli.Command{
Name: "myapp",
FlagSet: fs,
Subcommands: nil,
}
md, err := ffmd.Command(cmd)
if err != nil {
panic(err)
}
write("README.md", md)
}
func write(path, content string) {
fi, err := os.Create(path)
if err != nil {
panic(err)
}
defer fi.Close()
if _, err := fi.WriteString(content); err != nil {
panic(err)
}
}

View File

@ -23,12 +23,12 @@ func main() {
fs.Duration("duration-flag", 0, "Duration flag with no default")
fs.Duration("duration-flag-default", time.Minute*5, "Duration flag with default")
md, err := ffmd.FromFlagSet(fs)
md, err := ffmd.FlagSet(fs)
if err != nil {
panic(err)
}
write("flagset.md", md)
write("README.md", md)
}
func write(path, content string) {

View File

@ -62,11 +62,11 @@ func main() {
cmd2.Subcommands = []*ffcli.Command{cmd3, cmd5}
cmd3.Subcommands = []*ffcli.Command{cmd4}
md, err := ffmd.FromCommand(cmd)
md, err := ffmd.Command(cmd)
if err != nil {
panic(err)
}
write("command-sub.md", md)
write("README.md", md)
}
func write(path, content string) {

View File

@ -31,11 +31,11 @@ func main() {
Subcommands: nil,
}
md, err := ffmd.FromCommand(cmd)
md, err := ffmd.Command(cmd)
if err != nil {
panic(err)
}
write("single.md", md)
write("README.md", md)
}
func write(path, content string) {

39
ffmd.go
View File

@ -3,6 +3,7 @@ package ffmd
import (
"bytes"
_ "embed"
"errors"
"flag"
"fmt"
"reflect"
@ -19,17 +20,20 @@ var (
Template = template.Must(template.New("").Parse(ffmdtmpl))
)
// FromCommand turns a ffcli.Command into markdown
func FromCommand(cmd *ffcli.Command) (string, error) {
// Command turns a ffcli.Command into markdown
func Command(cmd *ffcli.Command) (string, error) {
return fromCommand(cmd, 1)
}
// FromFlagSet turns a flag.FlagSet into markdown
func FromFlagSet(fs *flag.FlagSet) (string, error) {
// FlagSet turns a flag.FlagSet into markdown
func FlagSet(fs *flag.FlagSet) (string, error) {
return flagSetCommand(fs, 2).Markdown()
}
func fromCommand(cmd *ffcli.Command, section int) (string, error) {
if cmd.FlagSet == nil {
return "", errors.New("all commands should have a flagset, even if empty")
}
c := flagSetCommand(cmd.FlagSet, section)
c.Name = cmd.Name
c.Usage = fmt.Sprintf("%s [FLAGS] [ARGS...]", cmd.Name)
@ -77,19 +81,33 @@ func flagSetCommand(fs *flag.FlagSet, section int) command {
},
},
}
aliases := make(map[string][]string)
fs.VisitAll(func(f *flag.Flag) {
_, isBool := f.Value.(boolFlag)
def := f.DefValue
if isZeroValue(f, def) {
def = ""
}
a.Flags = append(a.Flags, appFlag{
af := appFlag{
Name: f.Name,
Usage: f.Usage,
Default: def,
IsBool: isBool,
})
}
if strings.HasPrefix(af.Usage, "--") {
aliasOf := strings.TrimPrefix(af.Usage, "--")
if _, ok := aliases[aliasOf]; !ok {
aliases[aliasOf] = make([]string, 0)
}
aliases[aliasOf] = append(aliases[aliasOf], af.Name)
return
}
a.Flags = append(a.Flags, af)
})
for idx, f := range a.Flags {
f.Aliases = aliases[f.Name]
a.Flags[idx] = f
}
sort.Slice(a.Flags, func(i, j int) bool {
return a.Flags[i].Name < a.Flags[j].Name
})
@ -119,11 +137,20 @@ func (c command) Markdown() (string, error) {
type appFlag struct {
Name string
Aliases []string
Usage string
Default string
IsBool bool
}
func (a appFlag) AllNames() string {
names := []string{"--" + a.Name}
for _, alias := range a.Aliases {
names = append(names, "-"+alias)
}
return strings.Join(names, ",")
}
// From stdlib
func isZeroValue(f *flag.Flag, value string) bool {

View File

@ -10,7 +10,7 @@
{{end}}
```{{range $flag := .Flags}}
[--{{$flag.Name}}]{{if not $flag.IsBool}}=[value]{{end}}
[{{$flag.AllNames}}]{{if not $flag.IsBool}}=[value]{{end}}
{{- end}}
```
{{- if .Usage}}
@ -22,5 +22,5 @@
{{- end -}}
{{range $flag := .Flags}}
**--{{$flag.Name}}**{{if not $flag.IsBool}}=""{{end}}: {{$flag.Usage}}{{if $flag.Default}} (default: `{{$flag.Default}}`){{end}}
**{{$flag.AllNames}}**{{if not $flag.IsBool}}=""{{end}}: {{$flag.Usage}}{{if $flag.Default}} (default: `{{$flag.Default}}`){{end}}
{{end}}