197 lines
8.0 KiB
Go
197 lines
8.0 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 carries the configuration and overlay values supplied to the command.
|
|
Selection ckconfig.Selection
|
|
// ID is the selected named prompt.
|
|
ID string
|
|
// Version carries the explicitly supplied prompt version, if present.
|
|
Version commandkit.Supplied[string]
|
|
}
|
|
|
|
// ProfileRequest selects one named profile for inspection.
|
|
type ProfileRequest struct {
|
|
// Selection carries the configuration and overlay values supplied to the command.
|
|
Selection ckconfig.Selection
|
|
// ID is the selected named profile.
|
|
ID string
|
|
}
|
|
|
|
// Suite describes application-owned input, shared state, and named inspection
|
|
// capabilities. Its private issues retain adapter construction failures.
|
|
// Factory results and callback effects remain caller-owned: Command does not
|
|
// deep-copy arbitrary objects or repair aliases returned by factories.
|
|
// Factories are not invoked during construction, installation, help, or version,
|
|
// and need not run after failure.
|
|
type Suite[I, S any] struct {
|
|
// NewInput is called once when definition input preparation is reached,
|
|
// before applying selectors and calling CheckInput.
|
|
// If nil, input is the zero value of I, including nil for pointer types.
|
|
NewInput func() I
|
|
// NewState is called once when inspection is reached, after input checks
|
|
// pass. If nil, state is the zero value of S, including nil for pointer types.
|
|
NewState func() S
|
|
// Selectors declares the original typed flags accepted by definition commands.
|
|
Selectors []commandkit.Flag[I]
|
|
// CheckInput validates the selected definition inputs before state creation.
|
|
CheckInput func(ckconfig.Selection, I) error
|
|
// InspectPrompt inspects the selected prompt after input and state preparation.
|
|
InspectPrompt func(context.Context, PromptRequest, I, S) (commandkit.Acquired[*promptkit.PromptInspection], error)
|
|
// InspectProfile inspects the selected profile after input and state preparation.
|
|
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 loader is not called during declaration
|
|
// construction or installation; it is called only when a config operation
|
|
// selects the promptkit scope, with that operation's input and shared state.
|
|
// PromptKit diagnostics and error identity are returned through ckconfig's
|
|
// acquisition contract.
|
|
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. It does not call the loader during construction. Each
|
|
// selected inspection passes its context, supplied selection, initialized input,
|
|
// and shared invocation state to the loader before calling PromptKit's public
|
|
// inspection method. 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...)
|
|
for index := range records {
|
|
if records[index].Primary != nil {
|
|
primary := *records[index].Primary
|
|
records[index].Primary = &primary
|
|
}
|
|
records[index].Related = append([]configkit.Location(nil), records[index].Related...)
|
|
}
|
|
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)
|
|
},
|
|
}}
|
|
}
|