package ckconfig import ( "context" "errors" "fmt" "io" "strconv" commandkit "gitea.maximumdirect.net/eric/CommandKit" configkit "gitea.maximumdirect.net/eric/ConfigKit" ) // FromConfigKit adapts a typed ConfigKit loader to all four native // configuration operations. The loader receives the operation's explicit // Selection, initialized input, and shared invocation state, and runs only when // an installed operation selects this scope. Construction and help do not call // it. A nil original loader is retained as a construction issue, even if callers // later narrow the scope's operation list. func FromConfigKit[I, S, T any]( name, summary string, load func(context.Context, Selection, I, S) (*configkit.Result[T], error), ) Scope[I, S] { scope := Scope[I, S]{ Name: name, Summary: summary, Operations: []Operation{Validate, Show, Sources, Explain}, } if load == nil { return WithScopeError(scope, fmt.Errorf("ConfigKit scope %q loader must not be nil", name)) } scope.Acquire = func(ctx context.Context, request Request, input I, state S) (commandkit.Acquired[View], error) { result, err := load(ctx, request.Selection, input, state) if err != nil { var loadError *configkit.LoadError if errors.As(err, &loadError) && loadError != nil { return commandkit.Acquired[View]{Diagnostics: configKitDiagnostics(name, loadError.Sources(), loadError.Diagnostics())}, err } return commandkit.Acquired[View]{}, err } if result == nil { return commandkit.Acquired[View]{}, fmt.Errorf("ConfigKit scope %q returned a nil result", name) } sources := result.Sources() return commandkit.Acquired[View]{ Value: View{Effective: result, Sources: result, Explanation: result}, Diagnostics: configKitDiagnostics(name, sources, result.Diagnostics()), }, nil } return scope } func configKitDiagnostics(scope string, sources []configkit.SourceInfo, records []configkit.Diagnostic) []commandkit.Diagnostic { if len(records) == 0 { return nil } severity := commandkit.SeverityWarning for _, record := range records { if record.Severity == configkit.SeverityError { severity = commandkit.SeverityError break } } // Capture the upstream snapshots so later calls to Write do not depend on // slices returned by the provider. sources = append([]configkit.SourceInfo(nil), sources...) records = append([]configkit.Diagnostic(nil), records...) return []commandkit.Diagnostic{{ Severity: severity, Write: func(writer io.Writer) error { if err := writeDiagnosticText(writer, "Configuration scope "+strconv.Quote(scope)+":\n"); err != nil { return err } if len(sources) > 0 { if err := writeDiagnosticText(writer, "Source IDs:\n"); err != nil { return err } for _, source := range sources { line := " " + strconv.Quote(source.ID) + ": label=" + strconv.Quote(source.Label) + ", path=" + strconv.Quote(source.Path) + "\n" if err := writeDiagnosticText(writer, line); err != nil { return err } } } return configkit.WriteDiagnostics(writer, records) }, }} } func writeDiagnosticText(writer io.Writer, value string) error { written, err := io.WriteString(writer, value) if err != nil { return err } if written != len(value) { return io.ErrShortWrite } return nil }