diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 974ce93..170ba05 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -48,6 +48,9 @@ Diff, catalogs, JSON output, consumer migrations, publication tooling, and concurrent-dispatch guarantees are deliberate deferrals, not missing initial architecture. The issues below affect behavior within the implemented structure. +- R-02 complete: inline document starts are recognized and native content is + retained, including block scalars. + ## Findings and priority P1 means a supported invocation is blocked or its report content is corrupted. diff --git a/show.go b/show.go index b272c69..3ea1f6f 100644 --- a/show.go +++ b/show.go @@ -107,7 +107,7 @@ func hasYAMLDirective(native []byte) bool { lines := bytes.Split(native, []byte("\n")) for _, line := range lines { trimmed := bytes.TrimSpace(line) - if bytes.Equal(trimmed, []byte("---")) || bytes.HasPrefix(trimmed, []byte("--- #")) { + if isYAMLDocumentStart(string(trimmed)) { return false } if bytes.HasPrefix(trimmed, []byte("%YAML ")) || bytes.HasPrefix(trimmed, []byte("%TAG ")) { @@ -135,8 +135,17 @@ func frameEffectiveYAML(scope string, native []byte) []byte { hasDirective := false for _, line := range lines { trimmed := strings.TrimSpace(string(line)) - if trimmed == "---" || strings.HasPrefix(trimmed, "--- #") { + if isYAMLDocumentStart(trimmed) { out := make([]byte, 0, len(native)+len(comment)) + // Inline content may be a block scalar; place the label before + // its marker rather than interrupting the native value. + content := strings.TrimSpace(strings.TrimPrefix(trimmed, "---")) + if content != "" && !strings.HasPrefix(content, "#") { + out = append(out, native[:position]...) + out = append(out, comment...) + out = append(out, native[position:]...) + return out + } out = append(out, native[:position+len(line)]...) if len(line) > 0 && line[len(line)-1] != '\n' { out = append(out, '\n') @@ -175,3 +184,7 @@ func showFailure(cause error, diagnostics []Diagnostic, reportStarted bool) Outc outcome.ReportIncomplete = reportStarted return outcome } + +func isYAMLDocumentStart(line string) bool { + return strings.HasPrefix(line, "---") && (len(line) == 3 || line[3] == ' ' || line[3] == '\t') +} diff --git a/show_test.go b/show_test.go new file mode 100644 index 0000000..ad5e2ed --- /dev/null +++ b/show_test.go @@ -0,0 +1,47 @@ +package commandkit + +import ( + "bytes" + "go.yaml.in/yaml/v4" + "io" + "reflect" + "strings" + "testing" +) + +func TestEffectiveYAMLFramingPreservesOneNativeDocument(t *testing.T) { + for _, native := range []string{ + "--- {setting: value}\n", "---\t{setting: value}\n", "--- value\n", + "--- |\n first\n second\n", "--- # authored\nsetting: value\n", + "setting: value\n", "%YAML 1.1\n--- {setting: value}\n", + } { + t.Run(native, func(t *testing.T) { + var want any + if err := yaml.Unmarshal([]byte(native), &want); err != nil { + t.Fatal(err) + } + framed := frameEffectiveYAML("app", []byte(native)) + withoutLabel := bytes.Replace(framed, []byte("# scope: app\n"), nil, 1) + if !strings.HasPrefix(native, "---") && !strings.HasPrefix(native, "%") { + withoutLabel = bytes.TrimPrefix(withoutLabel, []byte("---\n")) + } + if !bytes.Equal(withoutLabel, []byte(native)) { + t.Fatalf("native content changed: %q", framed) + } + if !bytes.Contains(framed, []byte("# scope: app\n")) { + t.Fatalf("missing label: %q", framed) + } + decoder := yaml.NewDecoder(bytes.NewReader(framed)) + var got any + if err := decoder.Decode(&got); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("got=%#v want=%#v", got, want) + } + if err := decoder.Decode(new(any)); err != io.EOF { + t.Fatalf("extra document: %v", err) + } + }) + } +}