{"id":13491515,"url":"https://github.com/peterbourgon/ff","last_synced_at":"2025-05-12T20:50:37.732Z","repository":{"id":24594026,"uuid":"102001041","full_name":"peterbourgon/ff","owner":"peterbourgon","description":"Flags-first package for configuration","archived":false,"fork":false,"pushed_at":"2025-03-14T23:58:11.000Z","size":220,"stargazers_count":1389,"open_issues_count":12,"forks_count":62,"subscribers_count":22,"default_branch":"main","last_synced_at":"2025-04-09T02:02:07.599Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/peterbourgon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-31T12:59:55.000Z","updated_at":"2025-04-03T05:08:34.000Z","dependencies_parsed_at":"2023-09-28T18:59:45.739Z","dependency_job_id":"af93f198-5aa3-4947-8de2-23cfce608cca","html_url":"https://github.com/peterbourgon/ff","commit_stats":{"total_commits":91,"total_committers":19,"mean_commits":"4.7894736842105265","dds":0.5824175824175823,"last_synced_commit":"4e42878b21c9c40090eca819d84310b572438766"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterbourgon%2Fff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterbourgon%2Fff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterbourgon%2Fff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterbourgon%2Fff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peterbourgon","download_url":"https://codeload.github.com/peterbourgon/ff/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250479581,"owners_count":21437388,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T19:00:57.768Z","updated_at":"2025-04-23T17:27:22.808Z","avatar_url":"https://github.com/peterbourgon.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# ff \n\n[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white\u0026style=flat-square)](https://pkg.go.dev/github.com/peterbourgon/ff/v4) \n[![Latest Release](https://img.shields.io/github/v/release/peterbourgon/ff?style=flat-square)](https://github.com/peterbourgon/ff/releases/latest) \n![Build Status](https://github.com/peterbourgon/ff/actions/workflows/test.yaml/badge.svg?branch=main)\n\nff is a flags-first approach to configuration.\n\nThe basic idea is that `myprogram -h` should always show the complete\nconfiguration \"surface area\" of a program. Therefore, every config parameter\nshould be defined as a flag. This module provides a simple and robust way to\ndefine those flags, and to parse them from command-line arguments, environment\nvariables, and/or config files.\n\nBuilding a command-line application in the style of `kubectl` or `docker`?\n[ff.Command](#ffcommand) offers a declarative approach that may be simpler to\nwrite, and easier to maintain, than many common alternatives.\n\n## Note\n\nThis README describes the pre-release version v4 of ff. For the stable version,\nsee [ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3).\n\n## Usage\n\n### Parse a flag.FlagSet\n\nParse a flag.FlagSet from commandline args, env vars, and/or a config file, by\nusing [ff.Parse][ffparse] instead of flag.FlagSet.Parse. Use\n[options][option] to control parse behavior.\n\n[ffparse]: https://pkg.go.dev/github.com/peterbourgon/ff/v4#Parse\n[option]: https://pkg.go.dev/github.com/peterbourgon/ff/v4#Option\n\n```go\nfs := flag.NewFlagSet(\"myprogram\", flag.ContinueOnError)\nvar (\n\tlistenAddr = fs.String(\"listen\", \"localhost:8080\", \"listen address\")\n\trefresh    = fs.Duration(\"refresh\", 15*time.Second, \"refresh interval\")\n\tdebug      = fs.Bool(\"debug\", false, \"log debug information\")\n\t_          = fs.String(\"config\", \"\", \"config file (optional)\")\n)\n\nff.Parse(fs, os.Args[1:],\n\tff.WithEnvVarPrefix(\"MY_PROGRAM\"),\n\tff.WithConfigFileFlag(\"config\"),\n\tff.WithConfigFileParser(ff.PlainParser),\n)\n\nfmt.Printf(\"listen=%s refresh=%s debug=%v\\n\", *listen, *refresh, *debug)\n```\n\n```shell\n$ myprogram -listen=localhost:9090\nlisten=localhost:9090 refresh=15s debug=false\n\n$ env MY_PROGRAM_DEBUG=1 myprogram\nlisten=localhost:8080 refresh=15s debug=true\n\n$ printf 'refresh 30s \\n debug \\n' \u003e my.conf\n$ myprogram -config=my.conf\nlisten=localhost:8080 refresh=30s debug=true\n```\n\n### Upgrade to an ff.FlagSet\n\nAlternatively, you can use the getopts(3)-inspired [ff.FlagSet][flagset], which\nprovides short (-f) and long (--foo) flag names, more useful flag types, and\nother niceities.\n\n[flagset]: https://github.com/peterbourgon/ff/v4#FlagSet\n\n```go\nfs := ff.NewFlagSet(\"myprogram\")\nvar (\n\taddrs     = fs.StringSet('a', \"addr\", \"remote address (repeatable)\")\n\tcompress  = fs.Bool('c', \"compress\", \"enable compression\")\n\ttransform = fs.Bool('t', \"transform\", \"enable transformation\")\n\tloglevel  = fs.StringEnum('l', \"log\", \"log level: debug, info, error\", \"info\", \"debug\", \"error\")\n\t_         = fs.StringLong(\"config\", \"\", \"config file (optional)\")\n)\n\nff.Parse(fs, os.Args[1:],\n\tff.WithEnvVarPrefix(\"MY_PROGRAM\"),\n\tff.WithConfigFileFlag(\"config\"),\n\tff.WithConfigFileParser(ff.PlainParser),\n)\n\nfmt.Printf(\"addrs=%v compress=%v transform=%v loglevel=%v\\n\", *addrs, *compress, *transform, *loglevel)\n```\n\n```shell\n$ env MY_PROGRAM_LOG=debug myprogram -afoo -a bar --addr=baz --addr qux -ct\naddrs=[foo bar baz qux] compress=true transform=true loglevel=debug\n```\n\n### Parent flag sets\n\nff.FlagSet supports the notion of a parent flag set, which allows a \"child\" flag\nset to parse all \"parent\" flags, in addition to their own flags.\n\n```go\nparentfs := ff.NewFlagSet(\"parentcommand\")\nvar (\n\tloglevel = parentfs.StringEnum('l', \"log\", \"log level: debug, info, error\", \"info\", \"debug\", \"error\")\n\t_        = parentfs.StringLong(\"config\", \"\", \"config file (optional)\")\n)\n\nchildfs := ff.NewFlagSet(\"childcommand\").SetParent(parentfs)\nvar (\n\tcompress  = childfs.Bool('c', \"compress\", \"enable compression\")\n\ttransform = childfs.Bool('t', \"transform\", \"enable transformation\")\n\trefresh   = childfs.DurationLong(\"refresh\", 15*time.Second, \"refresh interval\")\n)\n\nff.Parse(childfs, os.Args[1:],\n\tff.WithEnvVarPrefix(\"MY_PROGRAM\"),\n\tff.WithConfigFileFlag(\"config\"),\n\tff.WithConfigFileParser(ff.PlainParser),\n)\n\nfmt.Printf(\"loglevel=%v compress=%v transform=%v refresh=%v\\n\", *loglevel, *compress, *transform, *refresh)\n```\n\n```shell\n$ myprogram --log=debug --refresh=1s\nloglevel=debug compress=false transform=false refresh=1s\n\n$ printf 'log error \\n refresh 5s \\n' \u003e my.conf\n$ myprogram --config my.conf\nloglevel=error compress=false transform=false refresh=5s\n```\n\n### Help output\n\nUnlike flag.FlagSet, the ff.FlagSet doesn't emit help text to os.Stderr as an\ninvisible side effect of a failed parse. When using an ff.FlagSet, callers are\nexpected to check the error returned by parse, and to emit help text to the user\nas appropriate. [Package ffhelp][ffhelp] provides functions that produce help\ntext in a standard format, and tools for creating your own help text format.\n\n[ffhelp]: https://pkg.go.dev/github.com/peterbourgon/ff/v4/ffhelp\n\n```go\nparentfs := ff.NewFlagSet(\"parentcommand\")\nvar (\n\tloglevel  = parentfs.StringEnum('l', \"log\", \"log level: debug, info, error\", \"info\", \"debug\", \"error\")\n\t_         = parentfs.StringLong(\"config\", \"\", \"config file (optional)\")\n)\n\nchildfs := ff.NewFlagSet(\"childcommand\").SetParent(parentfs)\nvar (\n\tcompress  = childfs.Bool('c', \"compress\", \"enable compression\")\n\ttransform = childfs.Bool('t', \"transform\", \"enable transformation\")\n\trefresh   = childfs.DurationLong(\"refresh\", 15*time.Second, \"refresh interval\")\n)\n\nif err := ff.Parse(childfs, os.Args[1:],\n\tff.WithEnvVarPrefix(\"MY_PROGRAM\"),\n\tff.WithConfigFileFlag(\"config\"),\n\tff.WithConfigFileParser(ff.PlainParser),\n); err != nil {\n\tfmt.Printf(\"%s\\n\", ffhelp.Flags(childfs))\n\tfmt.Printf(\"err=%v\\n\", err)\n} else {\n\tfmt.Printf(\"loglevel=%v compress=%v transform=%v refresh=%v\\n\", *loglevel, *compress, *transform, *refresh)\n}\n```\n\n```shell\n$ childcommand -h\nNAME\n  childcommand\n\nFLAGS (childcommand)\n  -c, --compress           enable compression\n  -t, --transform          enable transformation\n      --refresh DURATION   refresh interval (default: 15s)\n\nFLAGS (parentcommand)\n  -l, --log STRING         log level: debug, info, error (default: info)\n      --config STRING      config file (optional)\n\nerr=parse args: flag: help requested\n```\n\n## Parse priority\n\nCommand-line args have the highest priority, because they're explicitly provided\nto the program by the user. Think of command-line args as the \"user\"\nconfiguration.\n\nEnvironment variables have the next-highest priority, because they represent\nconfiguration in the runtime environment. Think of env vars as the \"session\"\nconfiguration.\n\nConfig files have the lowest priority, because they represent config that's\nstatic to the host. Think of config files as the \"host\" configuration.\n\n## ff.Command\n\n[ff.Command][command] is a tool for building larger CLI programs with\nsub-commands, like `docker` or `kubectl`. It's a declarative and lightweight\nalternative to more common frameworks like [spf13/cobra][cobra],\n[urfave/cli][urfave], or [alecthomas/kingpin][kingpin].\n\n[command]: https://pkg.go.dev/github.com/peterbourgon/ff/v4#Command\n[cobra]: https://github.com/spf13/cobra\n[urfave]: https://github.com/urfave/cli\n[kingpin]: https://github.com/alecthomas/kingpin\n\nCommands are concerned only with the core mechanics of defining a command tree,\nparsing flags, and selecting a command to run. They're not intended to be a\none-stop-shop for everything a command-line application may need. Features like\ntab completion, colorized output, etc. are orthogonal to command tree parsing,\nand can be easily added on top.\n\nHere's a simple example of a basic command tree.\n\n```go\n// textctl -- root command\ntextctlFlags := ff.NewFlagSet(\"textctl\")\nverbose := textctlFlags.Bool('v', \"verbose\", \"increase log verbosity\")\ntextctlCmd := \u0026ff.Command{\n\tName:  \"textctl\",\n\tUsage: \"textctl [FLAGS] SUBCOMMAND ...\",\n\tFlags: textctlFlags,\n}\n\n// textctl repeat -- subcommand\nrepeatFlags := ff.NewFlagSet(\"repeat\").SetParent(textctlFlags) // \u003c-- set parent flag set\nn := repeatFlags.IntShort('n', 3, \"how many times to repeat\")\nrepeatCmd := \u0026ff.Command{\n\tName:      \"repeat\",\n\tUsage:     \"textctl repeat [-n TIMES] ARG\",\n\tShortHelp: \"repeatedly print the first argument to stdout\",\n\tFlags:     repeatFlags,\n\tExec:      func(ctx context.Context, args []string) error { /* ... */ },\n}\ntextctlCmd.Subcommands = append(textctlCmd.Subcommands, repeatCmd) // \u003c-- append to parent subcommands\n\n// ...\n\nif err := textctlCmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil {\n\tfmt.Fprintf(os.Stderr, \"%s\\n\", ffhelp.Command(textctlCmd))\n\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\tos.Exit(1)\n}\n```\n\nMore sophisticated programs are available in [the examples directory][examples].\n\n[examples]: https://github.com/peterbourgon/ff/tree/main/examples\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterbourgon%2Fff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeterbourgon%2Fff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterbourgon%2Fff/lists"}