cliconf

package
v0.36.4 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

View Source
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.

View Source
const Delimiter = "."

Delimiter joins a section to the key inside it.

Variables

View Source
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.

View Source
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

func Flatten(nested map[string]any) map[string]any

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

func Parse(data []byte) (map[string]any, error)

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.

func Split

func Split(key string) (section, name string)

Split separates a key into the section it is in and the flag it names.

A key with no section is reported as one with an empty section, which Apply refuses: sections are what let one file serve several commands, so a key outside them addresses nobody in particular.

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 Register

func Register(fs *flag.FlagSet) *Flags

Register declares the configuration-file flags and returns where their values land.

func (*Flags) Discover

func (f *Flags) Discover(start string) (string, error)

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

func Apply(fs *flag.FlagSet, values map[string]any, schema Schema) (Result, error)

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

type Schema map[string]string

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.

func (Schema) Merge

func (s Schema) Merge(more Schema) (Schema, error)

Merge returns s with more added, leaving both alone.

The command's own flags arrive this way, which is also where a collision would show up: a flag cannot be addressed in two sections, so the later one silently winning is not a thing to allow.

func (Schema) Sections

func (s Schema) Sections() []string

Sections reports the sections the schema addresses, sorted.

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.

func (YAML) Marshal

func (YAML) Marshal(values map[string]any) ([]byte, error)

Marshal writes what Unmarshal reads. Present to complete koanf's parser interface.

func (YAML) Unmarshal

func (YAML) Unmarshal(data []byte) (map[string]any, error)

Unmarshal reads a configuration file into the nested map it describes.

Jump to

Keyboard shortcuts

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