Skip to content

StevenACoffman/mango-ff

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mango-ff

Go Reference Software License

ff/v4 adapter for mango.

Generates man pages from an ff.Command tree. Each command's flags, subcommands, and help text are extracted automatically. Flag entries include the type placeholder and default value that ff itself computes — --port STRING (default: 8080) appears in the man page exactly as it does in -h output.

Install

go get github.com/StevenACoffman/mango-ff

Usage

From an ff.Command tree

Pass your root ff.Command to NewManPage after parsing, then call Build to render the roff output.

A --man flag on the root flag set lets users request the man page at runtime. ff separates parsing from execution — cmd.Parse sets flag values, cmd.Run calls command logic — so checking --man between the two means all flag values are resolved but no command has executed yet. The --man flag itself appears in the generated man page, which is correct: every configurable knob should be documented.

import (
    "context"
    "fmt"
    "os"

    mff "github.com/StevenACoffman/mango-ff"
    "github.com/muesli/roff"
    "github.com/peterbourgon/ff/v4"
)

func main() {
    fs := ff.NewFlagSet("myapp")
    man := fs.BoolLong("man", "print man page to stdout and exit")
    // ... define other flags and subcommands ...

    cmd := &ff.Command{
        Name:      "myapp",
        ShortHelp: "does something useful",
        Flags:     fs,
        // Subcommands: []*ff.Command{...},
    }

    if err := cmd.Parse(os.Args[1:]); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    if *man {
        manPage, err := mff.NewManPage(1, cmd)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
        manPage = manPage.
            WithSection("Authors", "Your Name <https://github.com/you/myapp>").
            WithSection("Copyright", "Released under MIT license.")
        fmt.Print(manPage.Build(roff.NewDocument()))
        os.Exit(0)
    }

    if err := cmd.Run(context.Background()); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

NewManPage takes cmd.ShortHelp as the one-line description and cmd.LongHelp as the long description. If ShortHelp is empty, the first line of LongHelp is used as a fallback.

Writing directly to a file or pipe

WriteManPage combines NewManPage and Build in a single call. Use it when you do not need to add custom sections:

if *man {
    if err := mff.WriteManPage(1, cmd, os.Stdout, roff.NewDocument()); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    os.Exit(0)
}

Use NewManPage when you need to chain WithSection calls on the result.

With a flat FlagSet

If you are not using ff.Command — for example, a simple tool with no subcommands — use the visitor functions, which mirror the pattern from mango-pflag:

import (
    "fmt"

    mff "github.com/StevenACoffman/mango-ff"
    "github.com/muesli/mango"
    "github.com/muesli/roff"
    "github.com/peterbourgon/ff/v4"
)

func main() {
    fs := ff.NewFlagSet("mytool")
    // ... define flags ...

    manPage := mango.NewManPage(1, "mytool", "does something useful").
        WithLongDescription("mytool is a demonstration of mango-ff.")

    if err := fs.WalkFlags(mff.FFlagVisitor(manPage)); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    fmt.Println(manPage.Build(roff.NewDocument()))
}

Manual ManPage construction

Use PopulateManPage when you want full control over the mango.ManPage — for example, to provide a description that differs from cmd.ShortHelp, or to write the long description by hand:

manPage := mango.NewManPage(1, cmd.Name, "custom one-line description").
    WithLongDescription("Extended description written by hand.").
    WithSection("Environment", "MYAPP_TOKEN  API token (overrides --token)").
    WithSection("See Also", "myapp-serve(1), myapp-config(1)")

if err := mff.PopulateManPage(manPage, cmd); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
}

fmt.Println(manPage.Build(roff.NewDocument()))

API

Function Description
NewManPage(section, cmd) Build a *mango.ManPage from an ff.Command tree; returns an error if subcommand names collide
WriteManPage(section, cmd, w, b) Build and write to an io.Writer in one call; use when no custom sections are needed
PopulateManPage(m, cmd) Walk an ff.Command tree into a pre-constructed *mango.ManPage
AddCommand(parent, cmd) Recursively add one ff.Command subtree to a mango.Command
FFlagVisitor(m) Visitor for FlagSet.WalkFlags — adds flags to manPage.Root
FFlagCommandVisitor(c) Visitor for FlagSet.WalkFlags — adds flags to a specific mango.Command

Flag rendering

ff computes a type placeholder and a default value for every flag; mango-ff forwards both into the man page entry without any extra work from the caller:

Flag definition Man page entry
fs.Bool('v', "verbose", "log verbose output") -v, --verbose
fs.String('p', "port", "8080", "HTTP port") -p, --port STRING (default: 8080)
fs.String('f', "file", "", "config file path") -f, --file STRING
fs.BoolLongDefault("feature", true, "enable X") --feature BOOL (default: true)

ff suppresses trivial placeholders and defaults: default-false bool flags (Bool, BoolLong, BoolShort) produce neither, and empty string defaults are omitted. A bool flag defined with BoolDefault/BoolLongDefault and a true default is not trivial — it renders with a BOOL placeholder and a (default: true) annotation so readers know the flag is active unless explicitly negated with --feature=false. See the ff documentation for the full flag API.

Subcommand flag inheritance

Each subcommand section in the man page shows only the flags defined at that level, not the ones inherited from parent flag sets. This matches what a user sees when running myapp subcmd -h.

Internally, ff's WalkFlags traverses the full parent chain, so without filtering a subcommand would repeat all root flags. mango-ff filters by flag set identity (f.GetFlags() == ownFlagSet) to show each flag exactly once, in the section where it was defined.

See also

  • ff/v4 — flags-first CLI configuration: one registered flag is parsed from CLI args, environment variables, and config files, and described in -h output
  • mango — man page generator for Go
  • mango-pflag — mango adapter for pflag
  • mango-kong — mango adapter for Kong
  • roff — roff document builder used by mango

About

Mango (man page generator) integration for peterbourgon/ff/v4 Flags-first applications

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages