Files
commandkit/integrations/ckprompt/adapters.go

166 lines
6.1 KiB
Go

package ckprompt
import (
"context"
"errors"
"fmt"
"io"
"strconv"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
"gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
promptkit "gitea.maximumdirect.net/eric/promptkit"
)
// PromptRequest selects one named prompt for inspection.
type PromptRequest struct {
Selection ckconfig.Selection
ID string
Version commandkit.Supplied[string]
}
// ProfileRequest selects one named profile for inspection.
type ProfileRequest struct {
Selection ckconfig.Selection
ID string
}
// Suite describes application-owned input, shared state, and named inspection
// capabilities. Its private issues retain adapter construction failures.
type Suite[I, S any] struct {
NewInput func() I
NewState func() S
Selectors []commandkit.Flag[I]
CheckInput func(ckconfig.Selection, I) error
InspectPrompt func(context.Context, PromptRequest, I, S) (commandkit.Acquired[*promptkit.PromptInspection], error)
InspectProfile func(context.Context, ProfileRequest, I, S) (commandkit.Acquired[*promptkit.ProfileInspection], error)
issues []error
}
type engineLoader[I, S any] func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)
// ConfigScope adapts PromptKit's captured configuration inspection to the
// standard configuration commands. The engine is acquired only when an
// operation selects this scope.
func ConfigScope[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) ckconfig.Scope[I, S] {
scope := ckconfig.Scope[I, S]{
Name: "promptkit",
Summary: "PromptKit engine configuration.",
Operations: []ckconfig.Operation{ckconfig.Validate, ckconfig.Show, ckconfig.Sources, ckconfig.Explain},
}
if load == nil {
return ckconfig.WithScopeError(scope, errors.New("PromptKit configuration loader is nil"))
}
scope.Acquire = func(ctx context.Context, request ckconfig.Request, input I, state S) (commandkit.Acquired[ckconfig.View], error) {
_, inspection, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, load)
if err != nil {
return commandkit.Acquired[ckconfig.View]{Diagnostics: diagnostics}, err
}
return commandkit.Acquired[ckconfig.View]{
Value: ckconfig.View{
Effective: inspection,
Sources: inspection,
Explanation: inspection,
},
Diagnostics: diagnostics,
}, nil
}
return scope
}
// FromEngine creates named prompt and profile inspection callbacks backed by a
// PromptKit engine loader. A nil loader is retained as an adapter issue.
func FromEngine[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) Suite[I, S] {
if load == nil {
return Suite[I, S]{issues: []error{errors.New("PromptKit engine loader is nil")}}
}
return Suite[I, S]{
InspectPrompt: func(ctx context.Context, request PromptRequest, input I, state S) (commandkit.Acquired[*promptkit.PromptInspection], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, load)
if err != nil {
return commandkit.Acquired[*promptkit.PromptInspection]{Diagnostics: diagnostics}, err
}
inspection, err := engine.InspectPrompt(ctx, request.ID, request.Version.Value)
return commandkit.Acquired[*promptkit.PromptInspection]{Value: inspection, Diagnostics: diagnostics}, err
},
InspectProfile: func(ctx context.Context, request ProfileRequest, input I, state S) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, load)
if err != nil {
return commandkit.Acquired[*promptkit.ProfileInspection]{Diagnostics: diagnostics}, err
}
inspection, err := engine.InspectProfile(ctx, request.ID)
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: inspection, Diagnostics: diagnostics}, err
},
}
}
func loadPromptKitEngine[I, S any](
ctx context.Context,
selection ckconfig.Selection,
input I,
state S,
load engineLoader[I, S],
) (*promptkit.Engine, promptkit.ConfigurationInspection, []commandkit.Diagnostic, error) {
engine, err := load(ctx, selection, input, state)
if err != nil {
var loadErr *configkit.LoadError
if errors.As(err, &loadErr) {
return nil, promptkit.ConfigurationInspection{}, promptKitDiagnostics("promptkit", loadErr.Sources(), loadErr.Diagnostics()), err
}
return nil, promptkit.ConfigurationInspection{}, nil, err
}
if engine == nil {
return nil, promptkit.ConfigurationInspection{}, nil, errors.New("PromptKit configuration loader returned a nil engine")
}
if ctx != nil {
if err := ctx.Err(); err != nil {
return nil, promptkit.ConfigurationInspection{}, nil, err
}
}
inspection, err := engine.InspectConfiguration()
if err != nil {
return nil, promptkit.ConfigurationInspection{}, nil, err
}
diagnostics := promptKitDiagnostics("promptkit", inspection.Sources(), inspection.Diagnostics())
return engine, inspection, diagnostics, nil
}
func promptKitDiagnostics(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
}
}
// Snapshot provider-owned slices before deferring their report to invocation.
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 := fmt.Fprintf(writer, "Configuration scope %s:\n", strconv.Quote(scope)); err != nil {
return err
}
if len(sources) > 0 {
if _, err := io.WriteString(writer, "Source IDs:\n"); err != nil {
return err
}
for _, source := range sources {
if _, err := fmt.Fprintf(writer, " %s: label=%s, path=%s\n", strconv.Quote(source.ID), strconv.Quote(source.Label), strconv.Quote(source.Path)); err != nil {
return err
}
}
}
return configkit.WriteDiagnostics(writer, records)
},
}}
}