mff

package module
v0.0.0-...-7f03922 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2026 License: MIT Imports: 5 Imported by: 0

README

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

Documentation

Overview

Package mff provides mango man-page support for ff/v4 applications.

High-level API

For applications that use ff's ff.Command tree, NewManPage builds a fully-populated mango.ManPage in one call:

man, err := mff.NewManPage(1, rootCmd)

WriteManPage goes one step further and renders directly to an io.Writer:

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

Flags are rendered with their type placeholder and default value when available, so `--port STRING (default: 8080)` appears instead of `--port`. ff suppresses trivial defaults (false for booleans, empty string) and placeholders (default-false bool flags only). 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 --flag=false.

Low-level API

FFlagVisitor and FFlagCommandVisitor mirror the visitor pattern from mango-pflag and are useful when working with a flat ff.FlagSet that is not part of a ff.Command tree:

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

PopulateManPage and AddCommand let callers control ManPage construction while still delegating the tree-walking to this package.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddCommand

func AddCommand(parent *mango.Command, cmd *ff.Command) error

AddCommand recursively adds an ff.Command and all its subcommands to a mango.Command parent. Each node is populated with its own flags only; flags inherited from parent sets are omitted.

func FFlagCommandVisitor

func FFlagCommandVisitor(c *mango.Command) func(ff.Flag) error

FFlagCommandVisitor returns a function suitable for use with FlagSet.WalkFlags that adds each flag to a specific mango.Command.

if err := cmd.Flags.WalkFlags(mff.FFlagCommandVisitor(mangoCmd)); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
}

func FFlagVisitor

func FFlagVisitor(m *mango.ManPage) func(ff.Flag) error

FFlagVisitor returns a function suitable for use with FlagSet.WalkFlags that adds each flag to the root command of a mango.ManPage.

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

func NewManPage

func NewManPage(section uint, cmd *ff.Command) (*mango.ManPage, error)

NewManPage builds a mango.ManPage from an ff.Command tree.

The one-line description is taken from cmd.ShortHelp; if that is empty, the first line of cmd.LongHelp is used as a fallback. cmd.LongHelp, when non-empty, becomes the long description.

Each subcommand is recursively added via AddCommand. Flags inherited from parent flag sets are omitted from subcommand sections, as they appear in the parent section.

func PopulateManPage

func PopulateManPage(m *mango.ManPage, cmd *ff.Command) error

PopulateManPage populates a mango.ManPage from an ff.Command tree. The root command's own flags are added to manPage.Root; each subcommand is recursively added via AddCommand.

Use NewManPage when the description should come directly from cmd.ShortHelp and cmd.LongHelp. Use PopulateManPage when you need a description that differs from the command's help text — construct the mango.ManPage yourself (calling mango.NewManPage with a custom description, then mango.ManPage.WithSection etc.) and then pass it here.

func WriteManPage

func WriteManPage(section uint, cmd *ff.Command, w io.Writer, b mango.Builder) error

WriteManPage generates a man page for cmd and writes it to w using b. It is a convenience wrapper around NewManPage and mango.ManPage.Build.

Typical use, after parsing flags:

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

Types

This section is empty.

Directories

Path Synopsis
Package main demonstrates mango-ff by building a small CLI with two subcommands.
Package main demonstrates mango-ff by building a small CLI with two subcommands.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL