Convert Figma logo to code with AI

peterbourgon logoff

Flags-first package for configuration

1,369
59
1,369
7

Top Related Projects

26,991

Go configuration with fangs

Golang library for managing configuration data from environment variables

A Go port of Ruby's dotenv library (Loads environment variables from .env files)

4,871

A simple, zero-dependencies library to parse environment variables into structs

2,736

Simple, extremely lightweight, extensible, configuration management library for Go. Support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

Quick Overview

ff is a feature flag (toggle) library for Go, designed to be simple, fast, and flexible. It provides a way to enable or disable features in your application dynamically, without requiring code changes or redeployments.

Pros

  • Simple and lightweight implementation
  • Supports multiple flag types (bool, int, float, string, time.Duration)
  • Provides both in-memory and file-based flag storage
  • Thread-safe and concurrent-access friendly

Cons

  • Limited built-in integrations with external flag management services
  • No built-in web UI for flag management
  • Lacks advanced targeting or segmentation features
  • Limited documentation and examples

Code Examples

  1. Creating and using a boolean flag:
f := ff.New()
f.Bool("new-feature", false)

if f.Bool("new-feature") {
    // New feature code
} else {
    // Old feature code
}
  1. Using a string flag with a default value:
f := ff.New()
f.String("greeting", "Hello")

message := fmt.Sprintf("%s, World!", f.String("greeting"))
fmt.Println(message)
  1. Loading flags from a JSON file:
f := ff.New()
err := f.ParseJSON([]byte(`{
    "debug": true,
    "max-connections": 100,
    "timeout": "30s"
}`))
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Debug mode: %v\n", f.Bool("debug"))
fmt.Printf("Max connections: %d\n", f.Int("max-connections"))
fmt.Printf("Timeout: %v\n", f.Duration("timeout"))

Getting Started

To use ff in your Go project, follow these steps:

  1. Install the package:

    go get github.com/peterbourgon/ff
    
  2. Import the package in your code:

    import "github.com/peterbourgon/ff"
    
  3. Create a new ff instance and define your flags:

    f := ff.New()
    f.Bool("feature-x", false)
    f.Int("max-retries", 3)
    f.String("api-url", "https://api.example.com")
    
  4. Use the flags in your application logic:

    if f.Bool("feature-x") {
        // Feature X is enabled
    }
    maxRetries := f.Int("max-retries")
    apiURL := f.String("api-url")
    

Competitor Comparisons

26,991

Go configuration with fangs

Pros of Viper

  • More comprehensive configuration management with support for multiple formats (JSON, TOML, YAML, etc.)
  • Live watching and automatic reloading of configuration files
  • Nested key support for more complex configurations

Cons of Viper

  • Larger and more complex codebase, potentially overkill for simpler projects
  • Steeper learning curve due to more features and options

Code Comparison

ff:

var (
    addr = ff.String("addr", ":8080", "listen address")
    debug = ff.Bool("debug", false, "log debug information")
)

ff.Parse(ff.DefaultConfigFlagName("config"))

Viper:

viper.SetConfigName("config")
viper.AddConfigPath(".")
viper.ReadInConfig()

addr := viper.GetString("addr")
debug := viper.GetBool("debug")

Summary

Viper offers a more feature-rich configuration management solution with support for multiple formats and advanced features like live reloading. However, this comes at the cost of increased complexity and a steeper learning curve. ff provides a simpler, more lightweight approach that may be sufficient for many projects, especially those with straightforward configuration needs. The choice between the two depends on the specific requirements of your project and the level of configuration complexity you need to handle.

Golang library for managing configuration data from environment variables

Pros of envconfig

  • Simpler API with fewer configuration options, making it easier to use for basic scenarios
  • Directly populates struct fields, reducing boilerplate code
  • Supports custom parsing functions for complex types

Cons of envconfig

  • Limited to environment variables only, less flexible for multiple config sources
  • Doesn't support command-line flags or configuration files out of the box
  • Less control over parsing behavior and error handling

Code Comparison

envconfig:

type Config struct {
    Port int `envconfig:"PORT"`
    Debug bool `envconfig:"DEBUG"`
}

var c Config
err := envconfig.Process("myapp", &c)

ff:

var (
    fs = flag.NewFlagSet("myapp", flag.ExitOnError)
    port = fs.Int("port", 8080, "listen port")
    debug = fs.Bool("debug", false, "debug mode")
)

ff.Parse(fs, os.Args[1:],
    ff.WithEnvVarPrefix("MYAPP"),
    ff.WithConfigFileFlag("config"),
    ff.WithConfigFileParser(ff.JSONParser),
)

Summary

envconfig is simpler and more straightforward for projects that only need to read configuration from environment variables. ff offers more flexibility with support for multiple configuration sources, including command-line flags and configuration files, but requires more setup code. Choose envconfig for simplicity in environment-only configurations, and ff for more complex scenarios requiring multiple config sources.

A Go port of Ruby's dotenv library (Loads environment variables from .env files)

Pros of godotenv

  • Simple and focused on loading environment variables from .env files
  • Lightweight with minimal dependencies
  • Supports multiple .env file formats and overriding

Cons of godotenv

  • Limited to environment variables, lacks broader configuration options
  • No built-in support for command-line flags or config files
  • Less flexibility for complex configuration scenarios

Code Comparison

godotenv:

import "github.com/joho/godotenv"

err := godotenv.Load()
if err != nil {
    log.Fatal("Error loading .env file")
}

ff:

var (
    fs     = flag.NewFlagSet("example", flag.ExitOnError)
    listen = fs.String("listen", ":8080", "listen address")
    db     = fs.String("db", "postgres://localhost:5432/db", "database URL")
)

ff.Parse(fs, os.Args[1:],
    ff.WithEnvVarPrefix("APP"),
    ff.WithConfigFileFlag("config"),
    ff.WithConfigFileParser(ff.JSONParser),
)

godotenv focuses solely on loading environment variables from .env files, making it simpler for basic use cases. ff offers a more comprehensive configuration solution, supporting command-line flags, environment variables, and config files. ff provides greater flexibility and options for complex applications, while godotenv is more straightforward for projects primarily using environment variables.

4,871

A simple, zero-dependencies library to parse environment variables into structs

Pros of env

  • Supports parsing environment variables into structs with various data types
  • Allows for custom parsing logic through Setter interface
  • Provides automatic prefix stripping for nested structs

Cons of env

  • Lacks built-in support for command-line flags
  • Does not offer hierarchical configuration management
  • Limited options for configuration file parsing

Code Comparison

env:

type Config struct {
    Home   string `env:"HOME"`
    Port   int    `env:"PORT" envDefault:"3000"`
    IsProduction bool `env:"PRODUCTION"`
}

cfg := Config{}
if err := env.Parse(&cfg); err != nil {
    fmt.Printf("%+v\n", err)
}

ff:

var (
    fs     = flag.NewFlagSet("example", flag.ExitOnError)
    listen = fs.String("listen", ":8080", "listen address")
    debug  = fs.Bool("debug", false, "log debug information")
)

ff.Parse(fs, os.Args[1:],
    ff.WithEnvVarPrefix("APP"),
    ff.WithConfigFileFlag("config"),
    ff.WithConfigFileParser(ff.JSONParser),
)

env focuses on parsing environment variables into structs, while ff provides a more comprehensive configuration management system that includes command-line flags, environment variables, and configuration files. env offers more flexibility in parsing complex data types, whereas ff excels in managing hierarchical configurations and supporting multiple configuration sources.

2,736

Simple, extremely lightweight, extensible, configuration management library for Go. Support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

Pros of koanf

  • Supports multiple configuration sources (files, environment variables, command-line flags, etc.)
  • Provides type-safe access to configuration values
  • Offers more advanced features like watching for file changes and merging configurations

Cons of koanf

  • More complex API compared to ff's simplicity
  • Steeper learning curve for basic use cases
  • Potentially overkill for small projects or simple configuration needs

Code Comparison

ff:

var (
    addr = ff.String("addr", ":8080", "listen address")
    debug = ff.Bool("debug", false, "debug logging")
)

ff.Parse(ff.DefaultConfigFlagName("config"))

koanf:

k := koanf.New(".")
k.Load(file.Provider("config.yaml"), yaml.Parser())
k.Load(env.Provider("APP_", ".", func(s string) string {
    return strings.Replace(strings.ToLower(s), "_", ".", -1)
}), nil)

addr := k.String("server.addr")
debug := k.Bool("app.debug")

The code comparison shows that ff focuses on simplicity and ease of use, while koanf offers more flexibility and advanced features for handling complex configuration scenarios.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

ff

go.dev reference Latest Release Build Status

ff is a flags-first approach to configuration.

The basic idea is that myprogram -h should always show the complete configuration "surface area" of a program. Therefore, every config parameter should be defined as a flag. This module provides a simple and robust way to define those flags, and to parse them from command-line arguments, environment variables, and/or config files.

Building a command-line application in the style of kubectl or docker? ff.Command offers a declarative approach that may be simpler to write, and easier to maintain, than many common alternatives.

Note

This README describes the pre-release version v4 of ff. For the stable version, see ff/v3.

Usage

Parse a flag.FlagSet

Parse a flag.FlagSet from commandline args, env vars, and/or a config file, by using ff.Parse instead of flag.FlagSet.Parse. Use options to control parse behavior.

fs := flag.NewFlagSet("myprogram", flag.ContinueOnError)
var (
	listenAddr = fs.String("listen", "localhost:8080", "listen address")
	refresh    = fs.Duration("refresh", 15*time.Second, "refresh interval")
	debug      = fs.Bool("debug", false, "log debug information")
	_          = fs.String("config", "", "config file (optional)")
)

ff.Parse(fs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("listen=%s refresh=%s debug=%v\n", *listen, *refresh, *debug)
$ myprogram -listen=localhost:9090
listen=localhost:9090 refresh=15s debug=false

$ env MY_PROGRAM_DEBUG=1 myprogram
listen=localhost:8080 refresh=15s debug=true

$ printf 'refresh 30s \n debug \n' > my.conf
$ myprogram -config=my.conf
listen=localhost:8080 refresh=30s debug=true

Upgrade to an ff.FlagSet

Alternatively, you can use the getopts(3)-inspired ff.FlagSet, which provides short (-f) and long (--foo) flag names, more useful flag types, and other niceities.

fs := ff.NewFlagSet("myprogram")
var (
	addrs     = fs.StringSet('a', "addr", "remote address (repeatable)")
	compress  = fs.Bool('c', "compress", "enable compression")
	transform = fs.Bool('t', "transform", "enable transformation")
	loglevel  = fs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_         = fs.StringLong("config", "", "config file (optional)")
)

ff.Parse(fs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("addrs=%v compress=%v transform=%v loglevel=%v\n", *addrs, *compress, *transform, *loglevel)
$ env MY_PROGRAM_LOG=debug myprogram -afoo -a bar --addr=baz --addr qux -ct
addrs=[foo bar baz qux] compress=true transform=true loglevel=debug

Parent flag sets

ff.FlagSet supports the notion of a parent flag set, which allows a "child" flag set to parse all "parent" flags, in addition to their own flags.

parentfs := ff.NewFlagSet("parentcommand")
var (
	loglevel = parentfs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_        = parentfs.StringLong("config", "", "config file (optional)")
)

childfs := ff.NewFlagSet("childcommand").SetParent(parentfs)
var (
	compress  = childfs.Bool('c', "compress", "enable compression")
	transform = childfs.Bool('t', "transform", "enable transformation")
	refresh   = childfs.DurationLong("refresh", 15*time.Second, "refresh interval")
)

ff.Parse(childfs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("loglevel=%v compress=%v transform=%v refresh=%v\n", *loglevel, *compress, *transform, *refresh)
$ myprogram --log=debug --refresh=1s
loglevel=debug compress=false transform=false refresh=1s

$ printf 'log error \n refresh 5s \n' > my.conf
$ myprogram --config my.conf
loglevel=error compress=false transform=false refresh=5s

Help output

Unlike flag.FlagSet, the ff.FlagSet doesn't emit help text to os.Stderr as an invisible side effect of a failed parse. When using an ff.FlagSet, callers are expected to check the error returned by parse, and to emit help text to the user as appropriate. Package ffhelp provides functions that produce help text in a standard format, and tools for creating your own help text format.

parentfs := ff.NewFlagSet("parentcommand")
var (
	loglevel  = parentfs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_         = parentfs.StringLong("config", "", "config file (optional)")
)

childfs := ff.NewFlagSet("childcommand").SetParent(parentfs)
var (
	compress  = childfs.Bool('c', "compress", "enable compression")
	transform = childfs.Bool('t', "transform", "enable transformation")
	refresh   = childfs.DurationLong("refresh", 15*time.Second, "refresh interval")
)

if err := ff.Parse(childfs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
); err != nil {
	fmt.Printf("%s\n", ffhelp.Flags(childfs))
	fmt.Printf("err=%v\n", err)
} else {
	fmt.Printf("loglevel=%v compress=%v transform=%v refresh=%v\n", *loglevel, *compress, *transform, *refresh)
}
$ childcommand -h
NAME
  childcommand

FLAGS (childcommand)
  -c, --compress           enable compression
  -t, --transform          enable transformation
      --refresh DURATION   refresh interval (default: 15s)

FLAGS (parentcommand)
  -l, --log STRING         log level: debug, info, error (default: info)
      --config STRING      config file (optional)

err=parse args: flag: help requested

Parse priority

Command-line args have the highest priority, because they're explicitly provided to the program by the user. Think of command-line args as the "user" configuration.

Environment variables have the next-highest priority, because they represent configuration in the runtime environment. Think of env vars as the "session" configuration.

Config files have the lowest priority, because they represent config that's static to the host. Think of config files as the "host" configuration.

ff.Command

ff.Command is a tool for building larger CLI programs with sub-commands, like docker or kubectl. It's a declarative and lightweight alternative to more common frameworks like spf13/cobra, urfave/cli, or alecthomas/kingpin.

Commands are concerned only with the core mechanics of defining a command tree, parsing flags, and selecting a command to run. They're not intended to be a one-stop-shop for everything a command-line application may need. Features like tab completion, colorized output, etc. are orthogonal to command tree parsing, and can be easily added on top.

Here's a simple example of a basic command tree.

// textctl -- root command
textctlFlags := ff.NewFlagSet("textctl")
verbose := textctlFlags.Bool('v', "verbose", "increase log verbosity")
textctlCmd := &ff.Command{
	Name:  "textctl",
	Usage: "textctl [FLAGS] SUBCOMMAND ...",
	Flags: textctlFlags,
}

// textctl repeat -- subcommand
repeatFlags := ff.NewFlagSet("repeat").SetParent(textctlFlags) // <-- set parent flag set
n := repeatFlags.IntShort('n', 3, "how many times to repeat")
repeatCmd := &ff.Command{
	Name:      "repeat",
	Usage:     "textctl repeat [-n TIMES] ARG",
	ShortHelp: "repeatedly print the first argument to stdout",
	Flags:     repeatFlags,
	Exec:      func(ctx context.Context, args []string) error { /* ... */ },
}
textctlCmd.Subcommands = append(textctlCmd.Subcommands, repeatCmd) // <-- append to parent subcommands

// ...

if err := textctlCmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
	fmt.Fprintf(os.Stderr, "%s\n", ffhelp.Command(textctlCmd))
	fmt.Fprintf(os.Stderr, "error: %v\n", err)
	os.Exit(1)
}

More sophisticated programs are available in the examples directory.