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. type Suite[I, S any] struct { // NewInput creates input storage once for the selected definition command. NewInput func() I // NewState creates shared inspection state once for that invocation. 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) }, }} }