Documentation
¶
Overview ¶
Package cliconf supplements a command line with a configuration file.
It owns the contract - where the file is, what may be in it, and how it loses to anything the caller typed - and nothing else. The values themselves arrive as a plain map, so a command is free to read them however it likes: through koanf, which brings environment variables and further formats with it, or through Parse, which needs nothing this repository does not already have. That seam is why this package can be shared with a command that must cross-compile to WebAssembly with no dependency beyond the library.
What a file looks like ¶
Keys are grouped into sections, and within a section a key is the flag it sets, spelled exactly as on the command line:
scan: workdir: ./api exclude-tags: [internal, debug] emit: scan-models: true name-from-tags: [form, json]
Sections are how one file serves several commands: a section this command does not know is skipped rather than refused, and reported through Result so it can be mentioned rather than silently dropped. A key inside a section it does know must name one of its flags - that is what makes a typo an error rather than a setting that quietly never applied.
Precedence ¶
A flag the caller typed always wins, whatever the file says. That is decided by asking the flag set which flags were actually seen, so it holds for a flag set to its own default value too: -scan-models=false means false, even where the file says true.
Everything else lands through flag.FlagSet.Set, the same path the command line takes, so a value is parsed and validated exactly once and a file cannot express something an argument could not.
Index ¶
Constants ¶
const ( // Flag names the file to read. Flag = "config" // ShortFlag is the short form of [Flag]. ShortFlag = "c" // NoFlag says to read no configuration file at all. NoFlag = "no-config" )
The flags every command registers for its configuration file.
Three spellings of two questions: which file, and whether to read one at all. -c is the short form of -config, because naming a file is the thing done often enough to be worth two keystrokes; -no-config is the way to say none, and it is a switch rather than a magic value so that it reads as what it is at a glance.
const Delimiter = "."
Delimiter joins a section to the key inside it.
Variables ¶
var ( // ErrBadConfig is a file that cannot be read or is not a mapping of sections. ErrBadConfig = errors.New("bad configuration file") // ErrUnknownKey is a key naming no flag of the command reading it. ErrUnknownKey = errors.New("unknown configuration key") // ErrBadValue is a value the flag it addresses will not take. ErrBadValue = errors.New("bad configuration value") )
What a configuration file can be wrong about.
Sentinels rather than a formatted string at each site, so that a command can decide what a bad configuration file costs it - a usage status, usually - without matching on prose.
var Names = []string{
".codescan.yaml",
".codescan.yml",
".codescan.json",
}
Names are the file names looked for, in order, when -config says nothing.
One name for every command rather than one per command: the sections tell them apart, and a project configuring a scan has configured it for all of them. JSON is in the list because the parser reads it anyway, and a generated file is as likely to be JSON as YAML.
Functions ¶
func Flatten ¶
Flatten renders nested sections as section-qualified keys.
Only maps are descended into. A list is a value - it is how a repeated flag is written - and flattening one would turn the entries of exclude-tags into keys named after their positions.
func Parse ¶
Parse reads a configuration file into the flat, section-qualified keys Apply takes.
This is the whole of the dependency-free path: a command that does not want koanf calls this, and gets exactly what koanf's own All() would have handed it.
func SampleConfigName ¶
func SampleConfigName() string
SampleConfigName returns the first default supported default config file.
Types ¶
type Flags ¶
type Flags struct {
// contains filtered or unexported fields
}
Flags holds the configuration-file flags after parsing.
A type rather than a pair of pointers, because the two questions can be answered in ways that contradict each other, and something has to hold the answer to "then what".
func (*Flags) Discover ¶
Discover reports which file to read, if any.
A named file must exist: a caller who named one meant that file, and silently searching for another - or for none - would answer a question they did not ask. Searching, on the other hand, is allowed to find nothing, which is the ordinary case.
The search walks up from start, so running a command from anywhere inside a project finds the project's own file. It stops at the first hit rather than merging what it passes: a file half-overridden by one three directories up is not something anybody can read off the page.
type Result ¶
type Result struct {
// Set names the flags the file decided, in order. A flag absent from it was either typed on
// the command line or left at its default.
Set []string
// Ignored names the keys in sections this command does not know - another command's half of a
// shared file, or a section that was misspelled. Reported rather than dropped so that the second
// case is findable at all; a caller that mentions them under a -verbose gives that a way out.
Ignored []string
}
Result reports what a configuration file did.
func Apply ¶
Apply writes a configuration file's values onto a flag set.
Flags the caller typed are left alone. Everything else goes through flag.FlagSet.Set, so the file is parsed by the same code as the command line and cannot mean anything an argument could not.
type Schema ¶
Schema says which section of a configuration file each flag is addressed in.
It is what makes a key checkable: without it every key would have to be believed, and a misspelled one would read as a setting that quietly never applied. Commands build theirs by merging what the shared flag tables declare with what they add themselves.
type YAML ¶
type YAML struct{}
YAML reads a configuration file.
The methods are shaped as koanf's parser interface, which it declares structurally, so a command using koanf can hand this straight to it - and this package still owes koanf no import. JSON needs no parser of its own: it is a subset of YAML, so a file written as JSON reads here unchanged.