mybase

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Sep 25, 2018 License: Apache-2.0 Imports: 18 Imported by: 0

README

mybase

build status godoc latest release

A light-weight Golang framework for building command-line applications, with MySQL-like option handling

Features

  • Options may be provided via POSIX-style CLI flags (long or short) and/or ini-style option files
  • Intentionally does not support the golang flag package's single-dash long args (e.g. "-bar" is not equivalent to "--bar")
  • Multiple option files may be used, with cascading overrides
  • Ability to determine which source provided any given option (e.g. CLI vs a specific option file vs default value)
  • Supports command suites / subcommands, including nesting
  • Extensible to other option file formats/sources via a simple one-method interface
  • Automatic help/usage flags and subcommands
  • Only uses the Go standard library -- no external dependencies

Motivation

Unlike other Go CLI packages, mybase attempts to provide MySQL-like option parsing on the command-line and in option files. In brief, this means:

  • In option names, underscores are automatically converted to dashes.
  • Boolean options may have their value omitted to mean true ("--foo" means "--foo=true"). Meanwhile, falsey values include "off", "false", and "0".
  • Boolean option names may be modified by a prefix of "skip-" or "disable-" to negate the option ("--skip-foo" is equivalent to "--foo=false")
  • If an option name is prefixed with "loose-", it isn't an error if the option doesn't exist; it will just be ignored. This allows for backwards-compatible / cross-version option files.
  • The -h short option is not mapped to help (instead help uses -? for its short option). This allows -h to be used for --host if desired.
  • String-type short options may be configured to require arg (format "-u root" with a space) or have optional arg (format "-psecret" with no space, or "-p" alone if no arg / using default value or boolean value).
  • Boolean short options may be combined ("-bar" will mean "-b -a -r" if all three are boolean options).

Full compatibility with MySQL's option semantics is not guaranteed. Please open a GitHub issue if you encounter specific incompatibilities.

MySQL is a trademark of Oracle Corp.

Status

mybase has reached v1 and now has a stable API with backwards-compatibility guarantee. However, documentation, generic examples, and more thorough test coverage still need to be written. For now, see the Skeema codebase for a canonical example using all features of mybase.

Future development

The following features are not yet implemented, but are planned for future releases:

  • Env vars as an option source
  • Additional ways to get config option values: floating-point, IP address, bool count of repeated option
  • API for runtime option overrides, which take precedence even over command-line flags
  • API for re-reading all option files that have changed
  • Command aliases

Unit test coverage of mybase is still incomplete; code coverage is currently around 40%. This will be improved in future releases.

Authors

@evanelias

License

Copyright 2018 Skeema LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NormalizeOptionName

func NormalizeOptionName(name string) string

NormalizeOptionName is a convenience function that only returns the "key" portion of NormalizeOptionToken.

func NormalizeOptionToken

func NormalizeOptionToken(arg string) (key, value string, hasValue, loose bool)

NormalizeOptionToken takes a string of form "foo=bar" or just "foo", and parses it into separate key and value. It also returns whether the arg included a value (to tell "" vs no-value) and whether it had a "loose-" prefix, meaning that the calling parser shouldn't return an error if the key does not correspond to any existing option.

Types

type Command

type Command struct {
	Name          string              // Command name, as used in CLI
	Summary       string              // Short description text. If ParentCommand is nil, represents version instead.
	Description   string              // Long (multi-line) description/help text
	SubCommands   map[string]*Command // Index of sub-commands
	ParentCommand *Command            // What command this is a sub-command of, or nil if this is the top level
	Handler       CommandHandler      // Callback for processing command. Ignored if len(SubCommands) > 0.
	// contains filtered or unexported fields
}

Command can represent either a command suite (program with subcommands), a subcommand of another command suite, a stand-alone program without subcommands, or an arbitrarily nested command suite.

func NewCommand

func NewCommand(name, summary, description string, handler CommandHandler) *Command

NewCommand creates a standalone command, ie one that does not take sub- commands of its own. If this will be a top-level command (no parent), supply a version string in place of summary.

func NewCommandSuite

func NewCommandSuite(name, summary, description string) *Command

NewCommandSuite creates a Command that will have sub-commands. If this will be a top-level command (no parent), supply a version string in place of summary.

func (*Command) AddArg

func (cmd *Command) AddArg(name, defaultValue string, requireValue bool)

AddArg adds a positional arg to a Command. If requireValue is false, this arg is considered optional and its defaultValue will be used if omitted.

func (*Command) AddOption

func (cmd *Command) AddOption(opt *Option)

AddOption adds an Option to a Command. Options represent flags/settings which can be supplied via the command-line or an options file.

func (*Command) AddSubCommand

func (cmd *Command) AddSubCommand(subCmd *Command)

AddSubCommand adds a subcommand to a command suite.

func (*Command) OptionValue

func (cmd *Command) OptionValue(optionName string) (string, bool)

OptionValue returns the default value of the option with name optionName. This is satisfies the OptionValuer interface, and allows a Config to use a Command as the lowest-priority option provider in order to return an option's default value.

func (*Command) Options

func (cmd *Command) Options() (optMap map[string]*Option)

Options returns a map of options for this command, recursively merged with its parent command. In cases of conflicts, sub-command options override their parents / grandparents / etc. The returned map is always a copy, so modifications to the map itself will not affect the original cmd.options. This method does not include positional args in its return value.

func (*Command) Usage

func (cmd *Command) Usage()

Usage returns help instructions for a Command.

type CommandHandler

type CommandHandler func(*Config) error

CommandHandler is a function that can be associated with a Command as a callback which implements the command's logic.

type CommandLine

type CommandLine struct {
	InvokedAs    string            // How the bin was invoked; e.g. os.Args[0]
	Command      *Command          // Which command (or subcommand) is being executed
	OptionValues map[string]string // Option values parsed from the command-line
	ArgValues    []string          // Positional arg values (does not include InvokedAs or Command.Name)
}

CommandLine stores state relating to executing an application.

func (*CommandLine) OptionValue

func (cli *CommandLine) OptionValue(optionName string) (string, bool)

OptionValue returns the value for the requested option if it was specified on the command-line. This is satisfies the OptionValuer interface, allowing Config to use the command-line as the highest-priority option provider.

func (*CommandLine) String added in v1.0.1

func (cli *CommandLine) String() string

type Config

type Config struct {
	CLI    *CommandLine // Parsed command-line
	IsTest bool         // true if Config generated from test logic, false otherwise
	// contains filtered or unexported fields
}

Config represents a list of sources for option values -- the command-line plus zero or more option files, or any other source implementing the OptionValuer interface.

func NewConfig

func NewConfig(cli *CommandLine, sources ...OptionValuer) *Config

NewConfig creates a Config object, given a CommandLine and any arbitrary number of other OptionValuer option sources. The order of sources matters: in case of conflicts (multiple sources providing the same option value), later sources override earlier sources. The CommandLine always overrides other sources, and should not be supplied redundantly via sources.

func ParseCLI

func ParseCLI(cmd *Command, args []string) (*Config, error)

ParseCLI parses the command-line to generate a CommandLine, which stores which (sub)command was used, named option values, and positional arg values. The CommandLine will then be wrapped in a Config for returning.

The supplied cmd should typically be a root Command (one with nil ParentCommand), but this is not a requirement.

The supplied args should match format of os.Args; i.e. args[0] should contain the program name.

func ParseFakeCLI

func ParseFakeCLI(t *testing.T, cmd *Command, commandLine string, sources ...OptionValuer) *Config

ParseFakeCLI splits a single command-line string into a slice of arg token strings, and then calls ParseCLI using those args. It understands simple quoting and escaping rules, but does not attempt to replicate more advanced bash tokenization, wildcards, etc.

func (*Config) AddSource

func (cfg *Config) AddSource(source OptionValuer)

AddSource adds a new OptionValuer to cfg. It will override previously-added sources, with the exception of the CommandLine, which always takes precedence.

func (*Config) Changed

func (cfg *Config) Changed(name string) bool

Changed returns true if the specified option name has been set, and its set value differs from the option's default value.

func (*Config) Clone

func (cfg *Config) Clone() *Config

Clone returns a shallow copy of a Config. The copy will point to the same CLI value and sources values, but the sources slice itself will be a new slice, meaning that a caller can add sources without impacting the original Config's source list.

func (*Config) FindOption

func (cfg *Config) FindOption(name string) *Option

FindOption returns an Option by name. It first searches the current command hierarchy, but if it fails to find the option there, it then searches all other command hierarchies as well. This makes it suitable for use in parsing option files, which may refer to options that aren't relevant to the current command but exist in some other command. Returns nil if no option with that name can be found anywhere.

func (*Config) Get

func (cfg *Config) Get(name string) string

Get returns an option's value as a string. If the entire value is wrapped in quotes (single, double, or backticks) they will be stripped, and escaped quotes or backslashes within the string will be unescaped. If the option is not set, its default value will be returned. Panics if the option does not exist, since this is indicative of programmer error, not runtime error.

func (*Config) GetBool

func (cfg *Config) GetBool(name string) bool

GetBool returns an option's value as a bool. If the option is not set, its default value will be returned. Panics if the flag does not exist.

func (*Config) GetBytes

func (cfg *Config) GetBytes(name string) (uint64, error)

GetBytes returns an option's value as a uint64 representing a number of bytes. If the value was supplied with a suffix of K, M, or G (upper or lower case) the returned value will automatically be multiplied by 1024, 1024^2, or 1024^3 respectively. Suffixes may also be expressed with a trailing 'B', e.g. 'KB' and 'K' are equivalent. A blank string will be returned as 0, with no error. Aside from that case, an error will be returned if the value cannot be parsed as a byte size. Panics if the option does not exist.

func (*Config) GetEnum

func (cfg *Config) GetEnum(name string, allowedValues ...string) (string, error)

GetEnum returns an option's value as a string if it matches one of the supplied allowed values, or its default value (which need not be supplied). Otherwise an error is returned. Matching is case-insensitive, but the returned value will always be of the same case as it was supplied in allowedValues. Panics if the option does not exist.

func (*Config) GetInt

func (cfg *Config) GetInt(name string) (int, error)

GetInt returns an option's value as an int. If an error occurs in parsing the value as an int, it is returned as the second return value. Panics if the option does not exist.

func (*Config) GetIntOrDefault

func (cfg *Config) GetIntOrDefault(name string) int

GetIntOrDefault is like GetInt, but returns the option's default value if parsing the supplied value as an int fails. Panics if the option does not exist.

func (*Config) GetRaw

func (cfg *Config) GetRaw(name string) string

GetRaw returns an option's value as-is as a string. If the option is not set, its default value will be returned. Panics if the option does not exist, since this is indicative of programmer error, not runtime error.

func (*Config) GetRegexp

func (cfg *Config) GetRegexp(name string) (*regexp.Regexp, error)

GetRegexp returns an option's value as a compiled *regexp.Regexp. If the option value isn't set (empty string), returns nil,nil. If the option value is set but cannot be compiled as a valid regular expression, returns nil and an error value. Panics if the named option does not exist.

func (*Config) GetSlice

func (cfg *Config) GetSlice(name string, delimiter rune, unwrapFullValue bool) []string

GetSlice returns an option's value as a slice of strings, splitting on the provided delimiter. Delimiters contained inside quoted values have no effect, nor do backslash-escaped delimiters. Quote-wrapped tokens will have their surrounding quotes stripped in the returned value. Leading and trailing whitespace in any token will be stripped. Empty values will be removed.

unwrapFullValue determines how an entirely-quoted-wrapped option value is treated: if true, a fully quote-wrapped option value will be unquoted before being parsed for delimiters. If false, a fully-quote-wrapped option value will be treated as a single token, resulting in a one-element slice.

func (*Config) HandleCommand

func (cfg *Config) HandleCommand() error

HandleCommand executes the CommandHandler callback associated with the Command that was parsed on the CommandLine.

func (*Config) MarkDirty

func (cfg *Config) MarkDirty()

MarkDirty causes the config to rebuild itself on next option lookup. This is only needed in situations where a source is known to have changed since the previous lookup.

func (*Config) OnCLI

func (cfg *Config) OnCLI(name string) bool

OnCLI returns true if the specified option name was set on the command-line, or false otherwise. If the option does not exist, panics to indicate programmer error.

func (*Config) Source

func (cfg *Config) Source(name string) OptionValuer

Source returns the OptionValuer that provided the specified option. If the option does not exist, panics to indicate programmer error.

func (*Config) Supplied

func (cfg *Config) Supplied(name string) bool

Supplied returns true if the specified option name has been set by some configuration source, or false if not.

Note that Supplied returns true even if some source has set the option to a value *equal to its default value*. If you want to check if an option *differs* from its default value (the more common situation), use Changed. As an example, imagine that one source sets an option to a non-default value, but some other higher-priority source explicitly sets it back to its default value. In this case, Supplied returns true but Changed returns false.

type File

type File struct {
	Dir                  string
	Name                 string
	IgnoreUnknownOptions bool
	// contains filtered or unexported fields
}

File represents a form of ini-style option file. Lines can contain [sections], option=value, option without value (usually for bools), or comments.

func NewFile

func NewFile(paths ...string) *File

NewFile returns a value representing an option file. The arg(s) will be joined to create a single path, so it does not matter if the path is provided in a way that separates the dir from the base filename or not.

func (*File) Exists

func (f *File) Exists() bool

Exists returns true if the file exists and is visible to the current user.

func (*File) HasSection

func (f *File) HasSection(name string) bool

HasSection returns true if the file has a section with the supplied name.

func (*File) OptionValue

func (f *File) OptionValue(optionName string) (string, bool)

OptionValue returns the value for the requested option from the option file. Only the previously-selected section(s) of the file will be used, or the default section "" if no section has been selected via UseSection. Panics if the file has not yet been parsed, as this would indicate a bug. This is satisfies the OptionValuer interface, allowing Files to be used as an option source in Config.

func (*File) Parse

func (f *File) Parse(cfg *Config) error

Parse parses the file contents into a series of Sections. A Config object must be supplied so that the list of valid Options is known.

func (*File) Path

func (f *File) Path() string

Path returns the file's full absolute path with filename.

func (*File) Read

func (f *File) Read() error

Read loads the contents of the option file, but does not parse it.

func (*File) SameContents

func (f *File) SameContents(other *File) bool

SameContents returns true if f and other have the same sections and values. Ordering, formatting, comments, filename, and directory do not affect the results of this comparison. Both files must be parsed by the caller prior to calling this method, otherwise this method panics to indicate programmer error. This method is primarily intended for unit testing purposes.

func (*File) SectionsWithOption

func (f *File) SectionsWithOption(optionName string) []string

SectionsWithOption returns a list of section names that set the supplied option name.

func (*File) SetOptionValue

func (f *File) SetOptionValue(sectionName, optionName, value string)

SetOptionValue sets an option value in the named section. This is not persisted to the file until Write is called on the File. If the caller plans to subsequently read configuration values from this same File object, it is the caller's responsibility to normalize the optionName and value prior to calling this method, and call MarkDirty() on any relevant Configs. These shortcomings will be fixed in a future release.

func (*File) SomeSectionHasOption

func (f *File) SomeSectionHasOption(optionName string) bool

SomeSectionHasOption returns true if at least one section sets the supplied option name.

func (*File) String added in v1.0.1

func (f *File) String() string

func (*File) UnsetOptionValue

func (f *File) UnsetOptionValue(sectionName, optionName string)

UnsetOptionValue removes an option value in the named section. This is not persisted to the file until Write is called on the File. If the caller plans to subsequently read configuration values from this same File object, it is the caller's responsibility to normalize the optionName and value prior to calling this method, and call MarkDirty() on any relevant Configs. These shortcomings will be fixed in a future release.

func (*File) UseSection

func (f *File) UseSection(names ...string) error

UseSection changes which section(s) of the file are used when calling OptionValue. If multiple section names are supplied, multiple sections will be checked by OptionValue, with sections listed first taking precedence over subsequent ones. Note that the default nameless section "" (i.e. lines at the top of the file prior to a section header) is automatically appended to the end of the list. So this section is always checked, at lowest priority, need not be passed to this function.

func (*File) Write

func (f *File) Write(overwrite bool) error

Write writes out the file's contents to disk. If overwrite=false and the file already exists, an error will be returned. Note that if overwrite=true and the file already exists, any comments and extra whitespace in the file will be lost upon re-writing. All option names and values will be normalized in the rewritten file. Any "loose-" prefix option names that did not exist will not be written, and any that did exist will have their "loose-" prefix stripped. These shortcomings will be fixed in a future release.

type Option

type Option struct {
	Name         string
	Shorthand    rune
	Type         OptionType
	Default      string
	Description  string
	RequireValue bool
	HiddenOnCLI  bool
}

Option represents a flag/setting for a Command. Any Option present for a parent Command will automatically be available to all of its descendent subcommands, although subcommands may choose to override the exact semantics by providing another conflicting Option of same Name.

func BoolOption

func BoolOption(long string, short rune, defaultValue bool, description string) *Option

BoolOption creates a boolean-type Option. By default, boolean options do not require a value, though this can be overridden via ValueRequired().

func StringOption

func StringOption(long string, short rune, defaultValue string, description string) *Option

StringOption creates a string-type Option. By default, string options require a value, though this can be overridden via ValueOptional().

func (*Option) HasNonzeroDefault

func (opt *Option) HasNonzeroDefault() bool

HasNonzeroDefault returns true if the Option's default value differs from its type's zero/empty value.

func (*Option) Hidden

func (opt *Option) Hidden() *Option

Hidden prevents an Option from being displayed in a Command's help/usage text.

func (*Option) PrintableDefault

func (opt *Option) PrintableDefault() string

PrintableDefault returns a human-friendly version of the Option's default value.

func (*Option) Usage

func (opt *Option) Usage(maxNameLength int) string

Usage displays one-line help information on the Option.

func (*Option) ValueOptional

func (opt *Option) ValueOptional() *Option

ValueOptional marks an Option as not needing a value, allowing the Option to appear without any value associated.

func (*Option) ValueRequired

func (opt *Option) ValueRequired() *Option

ValueRequired marks an Option as needing a value, so it will be an error if the option is supplied alone without any corresponding value.

type OptionMissingValueError

type OptionMissingValueError struct {
	Name   string
	Source string
}

OptionMissingValueError is an error returned when an Option requires a value, but no value was supplied.

func (OptionMissingValueError) Error

func (omv OptionMissingValueError) Error() string

Error satisfies golang's error interface.

type OptionNotDefinedError

type OptionNotDefinedError struct {
	Name   string
	Source string
}

OptionNotDefinedError is an error returned when an unknown Option is used.

func (OptionNotDefinedError) Error

func (ond OptionNotDefinedError) Error() string

Error satisfies golang's error interface.

type OptionType

type OptionType int

OptionType is an enum for representing the type of an option.

const (
	OptionTypeString OptionType = iota // String-valued option
	OptionTypeBool                     // Boolean-valued option
)

Constants representing different OptionType enumerated values. Note that there intentionally aren't separate types for int, comma-separated list, regex, etc. From the perspective of the CLI or an option file, these are all strings; callers may *process* a string value as a different Golang type at runtime using Config.GetInt, Config.GetSlice, etc.

type OptionValuer

type OptionValuer interface {
	OptionValue(optionName string) (value string, ok bool)
}

OptionValuer should be implemented by anything that can parse and return user-supplied values for options. If the struct has a value corresponding to the given optionName, it should return the value along with a true value for ok. If the struct does not have a value for the given optionName, it should return "", false.

type Section

type Section struct {
	Name   string
	Values map[string]string
}

Section represents a labeled section of an option file. Option values that precede any named section are still associated with a Section object, but with a Name of "".

type SimpleSource

type SimpleSource map[string]string

SimpleSource is the most trivial possible implementation of the OptionValuer interface: it just maps option name strings to option value strings.

func (SimpleSource) OptionValue

func (source SimpleSource) OptionValue(optionName string) (string, bool)

OptionValue satisfies the OptionValuer interface, allowing SimpleSource to be an option source for Config methods.

Jump to

Keyboard shortcuts

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