package output import ( "errors" "fmt" "io" "sort" "github.com/salvacybersec/keyhunter/pkg/engine" ) // ErrUnknownFormat is returned by Get when no formatter is registered for the // requested name. Callers should use errors.Is to check for it. var ErrUnknownFormat = errors.New("output: unknown format") // Options controls formatter behavior. Unmask reveals full key values. // ToolName and ToolVersion are consumed by metadata-bearing formats (SARIF). type Options struct { Unmask bool ToolName string ToolVersion string } // Formatter renders a slice of findings to an io.Writer. // Implementations must not mutate the findings slice. type Formatter interface { Format(findings []engine.Finding, w io.Writer, opts Options) error } // registry holds all formatters registered at init() time. It is not guarded // by a mutex because registration is expected to happen exclusively from // package init functions, which run sequentially before main. var registry = map[string]Formatter{} // Register adds a formatter under the given name. Intended to be called from // package init() functions. Re-registering the same name overwrites the // previous value. func Register(name string, f Formatter) { registry[name] = f } // Get returns the formatter registered under name. If no formatter is // registered, the returned error wraps ErrUnknownFormat. func Get(name string) (Formatter, error) { f, ok := registry[name] if !ok { return nil, fmt.Errorf("%w: %q", ErrUnknownFormat, name) } return f, nil } // Names returns the sorted list of registered format names. Used by the scan // command to build --output help text and error messages. func Names() []string { names := make([]string, 0, len(registry)) for k := range registry { names = append(names, k) } sort.Strings(names) return names }