Add native configuration display
This commit is contained in:
177
show.go
Normal file
177
show.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package commandkit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func newConfigShowCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
||||
spec := CommandSpec[configInput[C]]{
|
||||
Name: string(ConfigShow),
|
||||
Summary: "Show effective configuration as YAML.",
|
||||
Flags: configInputFlags(suite, "yaml"),
|
||||
CheckInput: func(input configInput[C]) error {
|
||||
_, err := preflightConfig(suite, input, ConfigShow)
|
||||
return err
|
||||
},
|
||||
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
||||
return runConfigShow(invocation, suite, input)
|
||||
},
|
||||
}
|
||||
return NewCommand(spec)
|
||||
}
|
||||
|
||||
type effectiveDocument struct {
|
||||
name string
|
||||
view ConfigView
|
||||
}
|
||||
|
||||
func runConfigShow[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
|
||||
scopeNames, err := selectConfigScopes(suite, input, ConfigShow)
|
||||
if err != nil {
|
||||
return Failure(err)
|
||||
}
|
||||
contexts := contextsForScopes(suite, input, scopeNames)
|
||||
documents := make([]effectiveDocument, 0, len(scopeNames))
|
||||
diagnostics := make([]Diagnostic, 0)
|
||||
for _, name := range scopeNames {
|
||||
scope, ok := findConfigScope(suite.Scopes, name)
|
||||
if !ok || scope.Acquire == nil {
|
||||
return showFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, false)
|
||||
}
|
||||
acquired, acquireErr := scope.Acquire(invocation.Context, ConfigRequest{Operation: ConfigShow, Selection: input.Selection}, contexts[name])
|
||||
diagnostics = append(diagnostics, acquired.Diagnostics...)
|
||||
if acquireErr != nil {
|
||||
return showFailure(acquireErr, diagnostics, false)
|
||||
}
|
||||
if acquired.Value.Effective == nil {
|
||||
return showFailure(fmt.Errorf("configuration scope %q did not provide an effective configuration writer", name), diagnostics, false)
|
||||
}
|
||||
documents = append(documents, effectiveDocument{name: name, view: acquired.Value})
|
||||
}
|
||||
|
||||
if len(documents) == 1 && input.Scope != "all" {
|
||||
checked := &checkedWriter{writer: invocation.Streams.Out}
|
||||
renderErr := documents[0].view.Effective.WriteEffective(checked)
|
||||
if checked.failure != nil {
|
||||
if renderErr == nil {
|
||||
renderErr = checked.failure
|
||||
} else {
|
||||
renderErr = errors.Join(renderErr, checked.failure)
|
||||
}
|
||||
}
|
||||
if renderErr != nil {
|
||||
outcome := showFailure(renderErr, diagnostics, true)
|
||||
outcome.DeliveryError = checked.failure
|
||||
return outcome
|
||||
}
|
||||
outcome := Success()
|
||||
outcome.Diagnostics = diagnostics
|
||||
return outcome
|
||||
}
|
||||
|
||||
written := false
|
||||
for index, document := range documents {
|
||||
var native bytes.Buffer
|
||||
if err := document.view.Effective.WriteEffective(&native); err != nil {
|
||||
return showFailure(err, diagnostics, written)
|
||||
}
|
||||
if index > 0 && hasYAMLDirective(native.Bytes()) {
|
||||
if err := writeReport(invocation.Streams.Out, []byte("...\n")); err != nil {
|
||||
outcome := showFailure(err, diagnostics, true)
|
||||
outcome.DeliveryError = err
|
||||
return outcome
|
||||
}
|
||||
}
|
||||
framed := frameEffectiveYAML(document.name, native.Bytes())
|
||||
if index < len(documents)-1 && len(framed) > 0 && framed[len(framed)-1] != '\n' {
|
||||
framed = append(framed, '\n')
|
||||
}
|
||||
if err := writeReport(invocation.Streams.Out, framed); err != nil {
|
||||
outcome := showFailure(err, diagnostics, true)
|
||||
outcome.DeliveryError = err
|
||||
return outcome
|
||||
}
|
||||
written = true
|
||||
}
|
||||
outcome := Success()
|
||||
outcome.Diagnostics = diagnostics
|
||||
return outcome
|
||||
}
|
||||
|
||||
func hasYAMLDirective(native []byte) bool {
|
||||
lines := bytes.Split(native, []byte("\n"))
|
||||
for _, line := range lines {
|
||||
trimmed := bytes.TrimSpace(line)
|
||||
if bytes.Equal(trimmed, []byte("---")) || bytes.HasPrefix(trimmed, []byte("--- #")) {
|
||||
return false
|
||||
}
|
||||
if bytes.HasPrefix(trimmed, []byte("%YAML ")) || bytes.HasPrefix(trimmed, []byte("%TAG ")) {
|
||||
return true
|
||||
}
|
||||
if len(trimmed) > 0 && trimmed[0] != '#' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func frameEffectiveYAML(scope string, native []byte) []byte {
|
||||
label := scope
|
||||
for _, r := range scope {
|
||||
if unicode.IsControl(r) {
|
||||
label = strconv.Quote(scope)
|
||||
break
|
||||
}
|
||||
}
|
||||
comment := []byte("# scope: " + label + "\n")
|
||||
lines := bytes.SplitAfter(native, []byte("\n"))
|
||||
position := 0
|
||||
preambleEnd := 0
|
||||
hasDirective := false
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(string(line))
|
||||
if trimmed == "---" || strings.HasPrefix(trimmed, "--- #") {
|
||||
out := make([]byte, 0, len(native)+len(comment))
|
||||
out = append(out, native[:position+len(line)]...)
|
||||
if len(line) > 0 && line[len(line)-1] != '\n' {
|
||||
out = append(out, '\n')
|
||||
}
|
||||
out = append(out, comment...)
|
||||
out = append(out, native[position+len(line):]...)
|
||||
return out
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "%YAML ") || strings.HasPrefix(trimmed, "%TAG ") {
|
||||
hasDirective = true
|
||||
}
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, "%YAML ") || strings.HasPrefix(trimmed, "%TAG ") {
|
||||
preambleEnd = position + len(line)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
position += len(line)
|
||||
}
|
||||
result := make([]byte, 0, len(native)+len(comment)+5)
|
||||
if hasDirective {
|
||||
result = append(result, native[:preambleEnd]...)
|
||||
}
|
||||
result = append(result, []byte("---\n")...)
|
||||
result = append(result, comment...)
|
||||
if hasDirective {
|
||||
result = append(result, native[preambleEnd:]...)
|
||||
} else {
|
||||
result = append(result, native...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func showFailure(cause error, diagnostics []Diagnostic, reportStarted bool) Outcome {
|
||||
outcome := Failure(cause)
|
||||
outcome.Diagnostics = diagnostics
|
||||
outcome.ReportIncomplete = reportStarted
|
||||
return outcome
|
||||
}
|
||||
Reference in New Issue
Block a user