Migrate maintained examples to invocation reporting

This commit is contained in:
2026-10-26 03:44:19 +00:00
parent b6f22462d3
commit 6264a87183
8 changed files with 204 additions and 19 deletions

View File

@@ -4,6 +4,8 @@ Run the maintained example with:
```sh
go run . config show
go run . config show --config fixtures/before.yml --verbose
go run . config show --config fixtures/before.yml --quiet
go run . config diff --config fixtures/before.yml --against-config fixtures/after.yml
go run . greet
go run . greet --count=0 --tag=first --tag second
@@ -15,3 +17,6 @@ omitted `--count` and an explicitly supplied zero, plus ordered repeated tags.
With no `--config`, application settings use the in-memory default. A supplied
configuration and overlay are passed to ConfigKit unchanged. The comparison
command loads the two local fixtures and reports `changes.greeting: Goodbye`.
The before fixture uses schema version 1, which ConfigKit reports as a provider
warning in verbose mode. Ordinary mode hides the warning while preserving the
effective YAML report; quiet mode suppresses that report.

View File

@@ -1 +1,3 @@
schema:
version: 2
greeting: Goodbye

View File

@@ -1 +1,3 @@
schema:
version: 1
greeting: Hello

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"io"
"os"
"strconv"
@@ -12,6 +13,9 @@ import (
)
type appConfiguration struct {
Schema struct {
Version int `yaml:"version"`
} `yaml:"schema"`
Greeting string `yaml:"greeting"`
}
@@ -22,12 +26,25 @@ type greetInput struct {
func loadAppConfiguration(_ context.Context, selection ckconfig.Selection) (*configkit.Result[appConfiguration], error) {
source := configkit.Mapping("simple application settings", map[string]any{
"schema": map[string]any{"version": 2},
"greeting": "Hello",
})
if selection.Config.Set {
source = configkit.File(selection.Config.Value)
}
options := configkit.Options[appConfiguration]{}
options := configkit.Options[appConfiguration]{
Definition: configkit.Definition[appConfiguration]{
Version: &configkit.VersionRule{
Path: configkit.Keys("schema", "version"),
Check: func(version configkit.Version, validation *configkit.Validation) error {
if version.Text == "1" {
validation.Warning(configkit.Keys("schema", "version"), "schema version 1 is supported; upgrade to version 2 before the next example release")
}
return nil
},
},
},
}
if selection.Overlay.Set {
options.Overlay = selection.Overlay.Value
options.Filesystem = &configkit.FilesystemContext{Root: ".", BaseDir: "."}
@@ -50,9 +67,10 @@ func newApp() (*commandkit.App, error) {
}),
},
Run: func(invocation commandkit.Invocation, input greetInput) commandkit.Outcome {
reporter := invocation.Reporter()
configuration, err := loadAppConfiguration(invocation.Context, ckconfig.Selection{})
if err != nil {
return commandkit.Failure(err)
return commandkit.Failure("Could not load application configuration.").WithCause(err)
}
count := fmt.Sprintf("default %d (not supplied)", input.Count.Value)
if input.Count.Set {
@@ -62,9 +80,10 @@ func newApp() (*commandkit.App, error) {
if input.Tags.Set {
tags = fmt.Sprintf("explicit %v", input.Tags.Value)
}
if _, err := fmt.Fprintf(invocation.Streams.Out, "%s; count=%s; tags=%s\n", configuration.Config.Greeting, count, tags); err != nil {
return commandkit.Failure(err)
}
_ = reporter.RenderOutput(func(writer io.Writer) error {
_, err := fmt.Fprintf(writer, "%s; count=%s; tags=%s\n", configuration.Config.Greeting, count, tags)
return err
})
return commandkit.Success()
},
})

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"io"
"strings"
"testing"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
@@ -42,7 +43,7 @@ func TestSimpleAppComparesFixturesAndKeepsDefaultCommands(t *testing.T) {
output.Reset()
outcome = app.Execute(context.Background(), []string{"config", "show"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || output.String() != "greeting: Hello\n" {
if outcome.Kind != commandkit.OutcomeSuccess || output.String() != "greeting: Hello\nschema:\n version: 2\n" {
t.Fatalf("default show outcome/output = %#v / %q", outcome, output.String())
}
@@ -60,6 +61,73 @@ func TestSimpleLoaderDoesNotReplaceExplicitEmptyConfiguration(t *testing.T) {
}
}
func TestSimpleConfigurationReportingModes(t *testing.T) {
for _, test := range []struct {
name string
args []string
wantOutput string
wantDiagnostic string
quiet bool
}{
{
name: "ordinary output hides provider warning",
args: []string{"config", "show", "--config", "fixtures/before.yml"},
wantOutput: "greeting: Hello\nschema:\n version: 1\n",
},
{
name: "quiet suppresses configuration report",
args: []string{"config", "show", "--config", "fixtures/before.yml", "--quiet"},
quiet: true,
},
{
name: "verbose shows ConfigKit warning",
args: []string{"config", "show", "--config", "fixtures/before.yml", "--verbose"},
wantOutput: "greeting: Hello\nschema:\n version: 1\n",
wantDiagnostic: "schema version 1 is supported",
},
} {
t.Run(test.name, func(t *testing.T) {
outcome, output, diagnostics := executeSimple(t, test.args...)
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Cause != nil {
t.Fatalf("outcome = %#v", outcome)
}
if output != test.wantOutput {
t.Errorf("output = %q, want %q", output, test.wantOutput)
}
if test.wantDiagnostic != "" && !strings.Contains(diagnostics, test.wantDiagnostic) {
t.Errorf("diagnostics = %q, want provider warning %q", diagnostics, test.wantDiagnostic)
}
if test.wantDiagnostic == "" && diagnostics != "" {
t.Errorf("diagnostics = %q, want no warning outside verbose mode", diagnostics)
}
if strings.Contains(diagnostics, "ERROR:") {
t.Errorf("diagnostics %q unexpectedly contain a terminal failure", diagnostics)
}
if test.quiet && (outcome.Presentation.Output.State != commandkit.DeliverySuppressed || outcome.Presentation.Diagnostics.State != commandkit.DeliverySuppressed) {
t.Errorf("quiet presentation = %#v, want report and provider warning suppressed", outcome.Presentation)
}
})
}
}
func TestSimpleCustomCommandUsesSharedReportingFlags(t *testing.T) {
outcome, output, diagnostics := executeSimple(t, "greet", "--quiet")
if outcome.Kind != commandkit.OutcomeSuccess || output != "" || diagnostics != "" {
t.Fatalf("quiet greet outcome/output/diagnostics = %#v / %q / %q", outcome, output, diagnostics)
}
}
func executeSimple(t *testing.T, args ...string) (commandkit.Outcome, string, string) {
t.Helper()
app, err := newApp()
if err != nil {
t.Fatal(err)
}
var output, diagnostics bytes.Buffer
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: &diagnostics})
return outcome, output.String(), diagnostics.String()
}
func field(mapping *yaml.Node, name string) *yaml.Node {
if mapping == nil || mapping.Kind != yaml.MappingNode {
return nil