clix

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Mar 15, 2024 License: MIT Imports: 23 Imported by: 6

README

logo

🔗 Table of Contents

✨ Features

  • go-flags wrapper, that handles parsing and decoding, with additional helpers. Uses Go 1.18's generics to allow embedding your own custom struct.
  • Auto-generated logger, and auto-provided logger flags that:
    • allows users to switch between plain-text, colored/pretty, JSON output (and quiet for no output).
    • Allows configuring the level dynamically.
    • Uses the built-in debug flag to automatically change the logging level.
  • Built-in --debug flag.
  • Built-in --version flag that provides a lot of useful features:
    • Using Go 1.18's build metadata, the ability to use that as the version info, automatically using VCS information if available.
    • Printing dependencies and build flags.
    • Embedding useful links (support, repo, homepage, etc) in both version output, and help output.
    • Colored output!
  • --generate-markdown flag (hidden) that allows generating markdown from the CLI's help information (see below!).
  • Uses godotenv to auto-load environment variables from .env files, before parsing flags.
  • Many flags to enable/disable functionality to suit your needs.

☑ TODO

  • Generate commands/sub-commands/etc (example)
  • Custom markdown formatting

⚙ Usage

go get -u github.com/lrstanley/clix@latest

Example:

package main

import (
 "github.com/apex/log"
 clix "github.com/lrstanley/clix"
)

var (
  cli = &clix.CLI[Flags]{
  Links: clix.GithubLinks("github.com/lrstanley/myproject", "master", "https://mysite.com"),
 }
 logger log.Interface
)

type Flags struct {
 EnableHTTP bool   `short:"e" long:"enable-http" description:"enable the http server"`
 File       string `env:"FILE" short:"f" long:"file" description:"some file that does something"`

 SubFlags struct {
  Username string `env:"USERNAME" short:"u" long:"username" default:"admin" description:"example username"`
  Password string `env:"PASSWORD" short:"p" long:"password" description:"example password"`
 } `group:"Example Group" namespace:"example" env-namespace:"EXAMPLE"`
}

func main() {
 // Initializes cli flags, and a pre-configured logger, based off user-provided
 // flags (e.g. --log.json, --log.level, etc). Also automatically handles
 // --version, --help, etc.
 cli.Parse()
 logger = cli.Logger

 logger.WithFields(log.Fields{
  "debug":     cli.Debug,
  "file_path": cli.Flags.File,
 }).Info("hello world")
}

Example help output

Below shows an example of a non-tagged revision of myproject. When using Git tags, the appropriate tag should be applied as the version, rather than (devel).

$ ./myproject --version
github.com/lrstanley/myproject :: (devel)
  build commit :: 2a00b14d2ff16b79ecbb2afc54f480c2c1e28172
    build date :: unknown
    go version :: go1.18.1 linux/amd64

helpful links:
      homepage :: https://myproject
        github :: https://github.com/lrstanley/myproject
        issues :: https://github.com/lrstanley/myproject/issues/new/choose
       support :: https://github.com/lrstanley/myproject/blob/master/.github/SUPPORT.md
  contributing :: https://github.com/lrstanley/myproject/blob/master/.github/CONTRIBUTING.md
      security :: https://github.com/lrstanley/myproject/security/policy

build options:
     -compiler :: gc
   CGO_ENABLED :: 1
    CGO_CFLAGS ::
  CGO_CPPFLAGS ::
  CGO_CXXFLAGS ::
   CGO_LDFLAGS ::
        GOARCH :: amd64
          GOOS :: linux
       GOAMD64 :: v1
           vcs :: git
  vcs.revision :: 2a00b14d2ff16b79ecbb2afc54f480c2c1e28172
      vcs.time :: 2022-04-26T00:08:11Z
  vcs.modified :: true

dependencies:
  h1:IVj9dxSeAC0CRxjM+AYLIKbNdCzAjUnsUjAp/td7kYo= :: ariga.io/atlas :: v0.3.8-0.20220424181913-f64001131c0e
  h1:Jlkg6X37VI/k5U02yTBB3MjKGniiBAmUGfA1TC1+dtU= :: ariga.io/entcache :: v0.0.0-20211014200019-283c566a429b
  h1:XE5df6hfIlK/YeRxY6ynRxoMKCqn2mIOcOjId8JrQN8= :: entgo.io/ent :: v0.10.2-0.20220424193633-04e0dc936be9
  h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= :: github.com/agext/levenshtein :: v1.2.3
  h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= :: github.com/apex/log :: v1.9.0
  [...]

You can also use ./myproject --version-json for a more programmatic approach to the above information.

Generate Markdown

When using clix, you can generate markdown for your commands by passing the flag --generate-markdown, which is a hidden flag. This will print the markdown to stdout.

./<my-script> --generate-markdown > USAGE.md

Example output

Raw:

#### Application Options

| Environment vars | Flags               | Type   | Description                          |
| ---------------- | ------------------- | ------ | ------------------------------------ |
| -                | `-e, --enable-http` | bool   | enable the http server               |
| `FILE`           | `-f, --file`        | string | some file that does something        |
| -                | `-v, --version`     | bool   | prints version information and exits |
| `DEBUG`          | `-D, --debug`       | bool   | enables debug mode                   |

#### Example Group

| Environment vars   | Flags                    | Type   | Description                           |
| ------------------ | ------------------------ | ------ | ------------------------------------- |
| `EXAMPLE_USERNAME` | `-u, --example.username` | string | example username [**default: admin**] |
| `EXAMPLE_PASSWORD` | `-p, --example.password` | string | example password                      |

#### Logging Options

| Environment vars | Flags          | Type   | Description                                                                      |
| ---------------- | -------------- | ------ | -------------------------------------------------------------------------------- |
| `LOG_QUIET`      | `--log.quiet`  | bool   | disable logging to stdout (also: see levels)                                     |
| `LOG_LEVEL`      | `--log.level`  | string | logging level [**default: info**] [**choices: debug, info, warn, error, fatal**] |
| `LOG_JSON`       | `--log.json`   | bool   | output logs in JSON format                                                       |
| `LOG_PRETTY`     | `--log.pretty` | bool   | output logs in a pretty colored format (cannot be easily parsed)                 |

Generated:


Application Options
Environment vars Flags Type Description
- -e, --enable-http bool enable the http server
FILE -f, --file string some file that does something
- -v, --version bool prints version information and exits
DEBUG -D, --debug bool enables debug mode
Example Group
Environment vars Flags Type Description
EXAMPLE_USERNAME -u, --example.username string example username [default: admin]
EXAMPLE_PASSWORD -p, --example.password string example password
Logging Options
Environment vars Flags Type Description
LOG_QUIET --log.quiet bool disable logging to stdout (also: see levels)
LOG_LEVEL --log.level string logging level [default: info] [choices: debug, info, warn, error, fatal]
LOG_JSON --log.json bool output logs in JSON format
LOG_PRETTY --log.pretty bool output logs in a pretty colored format (cannot be easily parsed)

🙋♂ Support & Assistance

  • ❤ Please review the Code of Conduct for guidelines on ensuring everyone has the best experience interacting with the community.
  • 🙋♂ Take a look at the support document on guidelines for tips on how to ask the right questions.
  • 🐞 For all features/bugs/issues/questions/etc, head over here.

🤝 Contributing

  • ❤ Please review the Code of Conduct for guidelines on ensuring everyone has the best experience interacting with the community.
  • 📋 Please review the contributing doc for submitting issues/a guide on submitting pull requests and helping out.
  • 🗝 For anything security related, please review this repositories security policy.

⚖ License

MIT License

Copyright (c) 2021 Liam Stanley <me@liamstanley.io>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Also located here

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(runners ...Runner) error

Run invokes all runners concurrently, and listens for any termination signals (SIGINT, SIGTERM, SIGQUIT, etc).

If any runners return an error, all runners will terminate (assuming they listen to the provided context), and the first known error will be returned.

func RunCtx

func RunCtx(ctx context.Context, runners ...Runner) error

RunCtx is the same as Run, but with the provided context that can be used to externally cancel all runners.

Types

type BuildSetting

type BuildSetting struct {
	// Key and Value describe the build setting.
	// Key must not contain an equals sign, space, tab, or newline.
	// Value must not contain newlines ('\n').
	Key   string `json:"key"`
	Value string `json:"value"`
}

BuildSetting describes a setting that may be used to understand how the binary was built. For example, VCS commit and dirty status is stored here.

func (BuildSetting) String

func (s BuildSetting) String() string

type CLI

type CLI[T any] struct {
	// Flags are the user-provided flags.
	Flags *T

	// Parser is the flags parser, this is only set after Parse() is called.
	//
	// NOTE: the "no-flag" struct field is required, otherwise the parser will parse
	// itself recursively, maxing out the stack.
	Parser *flags.Parser `no-flag:"true"`

	// VersionInfo is the version information for the CLI. You can provide
	// a custom version of this if you already have version information.
	VersionInfo *VersionInfo[T] `json:"version_info"`

	// Links are the links to the project's website, support, issues, security,
	// etc. This will be used in help and version output if provided.
	// Links are in the format of "name=url".
	Links []Link

	// Args are the remaining arguments after parsing.
	Args []string

	// Version can be used to print the version information to console. Use
	// NO_COLOR or FORCE_COLOR to change coloring.
	Version struct {
		Enabled     bool `short:"v" long:"version" description:"prints version information and exits"`
		EnabledJSON bool `long:"version-json" description:"prints version information in JSON format and exits"`
	}

	// Debug can be used to enable/disable debugging as a global flag. Also
	// sets the log level to debug.
	Debug bool `short:"D" long:"debug" env:"DEBUG" description:"enables debug mode"`

	// GenerateMarkdown can be used to generate markdown documentation for
	// the cli. clix will intercept and output the documentation to stdout.
	GenerateMarkdown bool `long:"generate-markdown" hidden:"true" description:"generate markdown documentation and write to stdout" json:"-"`

	// Logger is the generated logger.
	Logger       *log.Logger  `json:"-"`
	LoggerConfig LoggerConfig `group:"Logging Options" namespace:"log" env-namespace:"LOG"`
	// contains filtered or unexported fields
}

CLI is the main construct for clix. Do not manually set any fields until you've called Parse(). Initialize a new CLI like so:

var (
	logger   log.Interface
	cli    = &clix.CLI[Flags]{} // Where Flags is a user-provided type (struct).
)

type Flags struct {
	SomeFlag string `long:"some-flag" description:"some flag"`
}

// [...]
cli.Parse(clix.OptDisableGlobalLogger|clix.OptDisableBuildSettings)
logger = cli.Logger

Additional notes: * Use cli.Logger as a apex/log log.Interface (as shown above). * Use cli.Args to get the remaining arguments provided to the program.

func (*CLI[T]) GetVersionInfo

func (cli *CLI[T]) GetVersionInfo() *VersionInfo[T]

GetVersionInfo returns the version information for the CLI.

func (*CLI[T]) IsSet

func (cli *CLI[T]) IsSet(options Options) bool

IsSet returns true if the given option is set.

func (*CLI[T]) Markdown

func (cli *CLI[T]) Markdown(out io.Writer)

Markdown writes generated marakdown to the provided io.Writer.

func (*CLI[T]) Parse

func (cli *CLI[T]) Parse(options ...Options)

Parse executes the go-flags parser, returns the remaining arguments, as well as initializes a new logger. If cli.Version is set, it will print the version information (unless disabled).

func (*CLI[T]) ParseWithInit

func (cli *CLI[T]) ParseWithInit(initFn func() error, options ...Options) error

ParseWithInit executes the go-flags parser with the provided init function, returns the remaining arguments, as well as initializes a new logger. If cli.Version is set, it will print the version information (unless disabled).

Prefer using Parse() unless you're using sub-commands and want to run some initialization logic before the sub-command if invoked.

func (*CLI[T]) Set

func (cli *CLI[T]) Set(options ...Options)

Set sets the given option.

type Link struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

Link allows you to define a link to be included in the version and usage output.

func GithubLinks(repo, branch, homepage string) []Link

GithubLinks return an opinonated set of links for the project, using common Github layout conventions.

type LoggerConfig

type LoggerConfig struct {
	// Quiet disables all logging.
	Quiet bool `env:"QUIET" long:"quiet" description:"disable logging to stdout (also: see levels)"`

	// Level is the minimum level of log messages to output, must be one of info|warn|error|debug|fatal.
	Level string `` /* 140-byte string literal not displayed */

	// JSON enables JSON logging.
	JSON bool `env:"JSON" long:"json" description:"output logs in JSON format"`

	// Github enables GitHub Actions logging.
	Github bool `env:"GITHUB" long:"github" description:"output logs in GitHub Actions format"`

	// Pretty enables cli-friendly logging.
	Pretty bool `env:"PRETTY" long:"pretty" description:"output logs in a pretty colored format (cannot be easily parsed)"`

	// Path is the path to the log file.
	Path string `env:"PATH" long:"path" description:"path to log file (disables stdout logging)"`
}

LoggerConfig are the flags that define how log entries are processed/returned. If using github.com/jessevdk/go-flags, you can defined a LoggerConfig in your struct, and then call LoggerConfig.New(isDebug), directly, so you don't have to define additional flags. See the struct tags for what it will default to in terms of environment variables.

Example (where you can set LOG_LEVEL as an environment variable, for example):

type Flags struct {
	Debug    bool               `long:"debug" env:"DEBUG" description:"enable debugging"`
	Log      *chix.LoggerConfig `group:"Logging Options" namespace:"log" env-namespace:"LOG"`
}
[...]
cli.Log.New(cli.Debug)

type Module

type Module struct {
	Path    string  `json:"path,omitempty"`     // module path
	Version string  `json:"version,omitempty"`  // module version
	Sum     string  `json:"sum,omitempty"`      // checksum
	Replace *Module `json:"replaces,omitempty"` // replaced by this module
}

Module represents a module.

func (Module) String

func (m Module) String() string

type NonSensitiveVersion

type NonSensitiveVersion struct {
	Name    string `json:"name"`          // Name of cli tool.
	Version string `json:"build_version"` // Build version.
	Commit  string `json:"build_commit"`  // VCS commit SHA.
	Date    string `json:"build_date"`    // VCS commit date.

	Command   string `json:"command"`    // Executable name where the command was called from.
	GoVersion string `json:"go_version"` // Version of Go that produced this binary.
	OS        string `json:"os"`         // Operating system for this build.
	Arch      string `json:"arch"`       // CPU Architecture for build build.

	// Items hoisted from the parent CLI. Do not change this.
	Links []Link `json:"links,omitempty"`
}

NonSensitiveVersion represents the version information for the CLI.

type Options

type Options int

Options allows overriding default logic.

const (
	OptDisableLogging       Options = 1 << iota // Disable logging initialization.
	OptDisableVersion                           // Disable version printing (must handle manually).
	OptDisableDeps                              // Disable dependency printing in version output.
	OptDisableBuildSettings                     // Disable build info printing in version output.
	OptDisableGlobalLogger                      // Disable setting the global logger for apex/log.
	OptSubcommandsOptional                      // Subcommands are optional.
)

type Runner

type Runner func(ctx context.Context) error

func (Runner) Invoke

func (r Runner) Invoke(ctx context.Context) func() error

type VersionInfo

type VersionInfo[T any] struct {
	Name         string         `json:"name"`                     // Name of cli tool.
	Version      string         `json:"build_version"`            // Build version.
	Commit       string         `json:"build_commit"`             // VCS commit SHA.
	Date         string         `json:"build_date"`               // VCS commit date.
	Settings     []BuildSetting `json:"build_settings,omitempty"` // Other information about the build.
	Dependencies []Module       `json:"dependencies,omitempty"`   // Module dependencies.

	Command   string `json:"command"`    // Executable name where the command was called from.
	GoVersion string `json:"go_version"` // Version of Go that produced this binary.
	OS        string `json:"os"`         // Operating system for this build.
	Arch      string `json:"arch"`       // CPU Architecture for build build.

	// Items hoisted from the parent CLI. Do not change this.
	Links []Link `json:"links,omitempty"`
	// contains filtered or unexported fields
}

VersionInfo represents the version information for the CLI.

func (*VersionInfo[T]) GetSetting

func (v *VersionInfo[T]) GetSetting(key, defaultValue string) string

GetSetting returns the value of the setting with the given key, otherwise defaults to defaultValue.

func (*VersionInfo[T]) NonSensitive

func (v *VersionInfo[T]) NonSensitive() *NonSensitiveVersion

NonSensitive returns a copy of VersionInfo with sensitive information removed.

func (*VersionInfo[T]) String

func (v *VersionInfo[T]) String() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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