From 8d11a389c73ac777ae139f0fecea949f18c9d9da Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 11:14:44 +0200 Subject: [PATCH 01/14] Add e2e showcase tests. --- examples/showcase/README.md | 99 +++++ .../code/java/org/showcase/CommentModes.java | 14 + .../code/java/org/showcase/Greeting.java | 17 + .../java/org/showcase/MultiPartWorkflow.java | 23 ++ .../org/showcase/OverlappingFragments.java | 28 ++ .../java/org/showcase/PatternSamples.java | 22 ++ .../kotlin/org/showcase/KotlinGreeting.kt | 11 + examples/showcase/code/text/glob-patterns.txt | 5 + examples/showcase/configuration/README.md | 56 +++ .../docs/include-exclude/excluded.md | 8 + .../docs/include-exclude/included.md | 11 + .../docs/multiple/java/greeting.md | 10 + .../docs/multiple/kotlin/greeting.md | 12 + .../docs/named-sources/java-greeting.md | 11 + .../docs/named-sources/kotlin-greeting.md | 12 + .../docs/named-sources/text-line.md | 9 + .../configuration/docs/root-source/go-mod.md | 49 +++ .../configuration/docs/root-source/version.md | 8 + .../docs/single-source/greeting.md | 11 + .../configuration/include-exclude.yml | 6 + .../configuration/multiple-embeddings.yml | 13 + .../showcase/configuration/named-sources.yml | 10 + .../showcase/configuration/root-source.yml | 6 + .../showcase/configuration/single-source.yml | 4 + .../showcase/docs/01-whole-file-source.md | 20 + .../showcase/docs/02-source-line-pattern.md | 8 + examples/showcase/docs/03-named-fragment.md | 11 + .../docs/04-paired-instruction-tag.md | 36 ++ .../showcase/docs/05-named-source-root.md | 13 + .../showcase/docs/06-start-end-pattern.md | 18 + .../showcase/docs/07-multi-line-pattern.md | 18 + .../docs/08-escaped-glob-character.md | 9 + .../showcase/docs/09-escaped-newline-text.md | 11 + .../showcase/docs/10-comment-filtering.md | 19 + .../docs/11-multi-part-fragment-separator.md | 16 + .../showcase/docs/12-overlapping-fragments.md | 16 + .../docs/13-markdown-fence-shielding.md | 11 + examples/showcase/docs/html-showcase.html | 17 + examples/showcase/docs/ignored-by-exclude.md | 9 + examples/showcase/embed-code.yml | 14 + .../negative/docs/invalid-attributes.md | 8 + .../negative/docs/missing-code-fence.md | 7 + .../negative/docs/missing-fragment.md | 8 + .../showcase/negative/docs/missing-pattern.md | 8 + .../showcase/negative/docs/missing-source.md | 8 + .../showcase/negative/docs/stale-snippet.md | 11 + .../negative/docs/unclosed-code-fence.md | 9 + .../showcase/negative/processing-errors.yml | 13 + examples/showcase/negative/stale.yml | 6 + examples/showcase/showcase_test.go | 354 ++++++++++++++++++ go.mod | 2 +- 51 files changed, 1134 insertions(+), 1 deletion(-) create mode 100644 examples/showcase/README.md create mode 100644 examples/showcase/code/java/org/showcase/CommentModes.java create mode 100644 examples/showcase/code/java/org/showcase/Greeting.java create mode 100644 examples/showcase/code/java/org/showcase/MultiPartWorkflow.java create mode 100644 examples/showcase/code/java/org/showcase/OverlappingFragments.java create mode 100644 examples/showcase/code/java/org/showcase/PatternSamples.java create mode 100644 examples/showcase/code/kotlin/org/showcase/KotlinGreeting.kt create mode 100644 examples/showcase/code/text/glob-patterns.txt create mode 100644 examples/showcase/configuration/README.md create mode 100644 examples/showcase/configuration/docs/include-exclude/excluded.md create mode 100644 examples/showcase/configuration/docs/include-exclude/included.md create mode 100644 examples/showcase/configuration/docs/multiple/java/greeting.md create mode 100644 examples/showcase/configuration/docs/multiple/kotlin/greeting.md create mode 100644 examples/showcase/configuration/docs/named-sources/java-greeting.md create mode 100644 examples/showcase/configuration/docs/named-sources/kotlin-greeting.md create mode 100644 examples/showcase/configuration/docs/named-sources/text-line.md create mode 100644 examples/showcase/configuration/docs/root-source/go-mod.md create mode 100644 examples/showcase/configuration/docs/root-source/version.md create mode 100644 examples/showcase/configuration/docs/single-source/greeting.md create mode 100644 examples/showcase/configuration/include-exclude.yml create mode 100644 examples/showcase/configuration/multiple-embeddings.yml create mode 100644 examples/showcase/configuration/named-sources.yml create mode 100644 examples/showcase/configuration/root-source.yml create mode 100644 examples/showcase/configuration/single-source.yml create mode 100644 examples/showcase/docs/01-whole-file-source.md create mode 100644 examples/showcase/docs/02-source-line-pattern.md create mode 100644 examples/showcase/docs/03-named-fragment.md create mode 100644 examples/showcase/docs/04-paired-instruction-tag.md create mode 100644 examples/showcase/docs/05-named-source-root.md create mode 100644 examples/showcase/docs/06-start-end-pattern.md create mode 100644 examples/showcase/docs/07-multi-line-pattern.md create mode 100644 examples/showcase/docs/08-escaped-glob-character.md create mode 100644 examples/showcase/docs/09-escaped-newline-text.md create mode 100644 examples/showcase/docs/10-comment-filtering.md create mode 100644 examples/showcase/docs/11-multi-part-fragment-separator.md create mode 100644 examples/showcase/docs/12-overlapping-fragments.md create mode 100644 examples/showcase/docs/13-markdown-fence-shielding.md create mode 100644 examples/showcase/docs/html-showcase.html create mode 100644 examples/showcase/docs/ignored-by-exclude.md create mode 100644 examples/showcase/embed-code.yml create mode 100644 examples/showcase/negative/docs/invalid-attributes.md create mode 100644 examples/showcase/negative/docs/missing-code-fence.md create mode 100644 examples/showcase/negative/docs/missing-fragment.md create mode 100644 examples/showcase/negative/docs/missing-pattern.md create mode 100644 examples/showcase/negative/docs/missing-source.md create mode 100644 examples/showcase/negative/docs/stale-snippet.md create mode 100644 examples/showcase/negative/docs/unclosed-code-fence.md create mode 100644 examples/showcase/negative/processing-errors.yml create mode 100644 examples/showcase/negative/stale.yml create mode 100644 examples/showcase/showcase_test.go diff --git a/examples/showcase/README.md b/examples/showcase/README.md new file mode 100644 index 0000000..a26ee5b --- /dev/null +++ b/examples/showcase/README.md @@ -0,0 +1,99 @@ +# Embed Code Showcase + +This folder is an opt-in, executable guide to `embed-code-go`. It is not part of +the normal `go test ./...` flow. The Go test is guarded by the `showcase` build +tag, so run it only when you want to verify the examples end to end. + +Run commands from the repository root. + +The showcase-owned source examples live under [code](code/). Repository-root +source examples are kept in the configuration showcase only. + +## Positive Flow + +Refresh the generated snippets: + +```bash +go run ./main.go -mode embed -config-path examples/showcase/embed-code.yml +``` + +Verify that the snippets are up-to-date: + +```bash +go run ./main.go -mode check -config-path examples/showcase/embed-code.yml +``` + +Run the opt-in test: + +```bash +go test -tags showcase ./examples/showcase +``` + +The positive showcase covers: + +| Case | File | What it verifies | +|--------------------------|------------------------------------------------------------------------------|-----------------------------------------------------------------------| +| Whole file | [docs/01-whole-file-source.md](docs/01-whole-file-source.md) | Omitting selection attributes embeds the whole showcase source file. | +| Source line pattern | [docs/02-source-line-pattern.md](docs/02-source-line-pattern.md) | A showcase source file can be matched with a `line` pattern. | +| Named fragment | [docs/03-named-fragment.md](docs/03-named-fragment.md) | `fragment` uses `#docfragment` markers and omits marker lines. | +| Paired instruction tags | [docs/04-paired-instruction-tag.md](docs/04-paired-instruction-tag.md) | Paired tags are preferred; self-closing tags are still supported. | +| Named source roots | [docs/05-named-source-root.md](docs/05-named-source-root.md) | `$java/...` and `$kotlin/...` select different configured roots. | +| Start and end patterns | [docs/06-start-end-pattern.md](docs/06-start-end-pattern.md) | `start` and `end` select an inclusive source range. | +| Multi-line patterns | [docs/07-multi-line-pattern.md](docs/07-multi-line-pattern.md) | `\n` matches consecutive source lines. | +| Escaped glob characters | [docs/08-escaped-glob-character.md](docs/08-escaped-glob-character.md) | `\*` matches a literal asterisk. | +| Escaped newline text | [docs/09-escaped-newline-text.md](docs/09-escaped-newline-text.md) | `\\n` matches a literal backslash-n sequence. | +| Comment filtering | [docs/10-comment-filtering.md](docs/10-comment-filtering.md) | `comments="documentation"` keeps Java documentation comments. | +| Multi-part fragments | [docs/11-multi-part-fragment-separator.md](docs/11-multi-part-fragment-separator.md) | Repeated fragment markers are joined with the configured separator. | +| Overlapping fragments | [docs/12-overlapping-fragments.md](docs/12-overlapping-fragments.md) | Multiple fragment names may share marker lines. | +| Markdown fence shielding | [docs/13-markdown-fence-shielding.md](docs/13-markdown-fence-shielding.md) | Instruction-looking text inside a regular fence is ignored. | +| HTML documents | [docs/html-showcase.html](docs/html-showcase.html) | HTML files can be scanned when included by configuration. | +| Excludes | [docs/ignored-by-exclude.md](docs/ignored-by-exclude.md) | Excluded files are not processed even when they contain instructions. | + +## Negative Flow + +The negative examples are intentionally broken. These commands should fail. + +```bash +go run ./main.go -mode check -config-path examples/showcase/negative/processing-errors.yml +go run ./main.go -mode check -config-path examples/showcase/negative/stale.yml +``` + +The processing-error config verifies: + +| Case | File | Expected behavior | +|---------------------|------------------------------------------------------------------------------|------------------------------------------------------| +| Missing source | [negative/docs/missing-source.md](negative/docs/missing-source.md) | Reports that the code file cannot be found. | +| Missing fragment | [negative/docs/missing-fragment.md](negative/docs/missing-fragment.md) | Reports that the requested fragment cannot be found. | +| Missing pattern | [negative/docs/missing-pattern.md](negative/docs/missing-pattern.md) | Reports that no source line matches the pattern. | +| Invalid attributes | [negative/docs/invalid-attributes.md](negative/docs/invalid-attributes.md) | Rejects mutually exclusive selection attributes. | +| Missing code fence | [negative/docs/missing-code-fence.md](negative/docs/missing-code-fence.md) | Requires a fence immediately after an instruction. | +| Unclosed code fence | [negative/docs/unclosed-code-fence.md](negative/docs/unclosed-code-fence.md) | Reports an instruction fence that reaches EOF. | + +The stale config verifies: + +| Case | File | Expected behavior | +|---------------|--------------------------------------------------------------------|------------------------------------------------------------------------| +| Stale snippet | [negative/docs/stale-snippet.md](negative/docs/stale-snippet.md) | Check mode reports the file as needing an update without rewriting it. | + +## Configuration Flow + +Configuration examples live under [configuration](configuration/). They cover a +repository-root source, a single source root, named source roots, +include/exclude patterns, and the `embeddings` list for multiple documentation +roots. + +| Case | Config | Docs root | +|----------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------| +| Repository root | [configuration/root-source.yml](configuration/root-source.yml) | [configuration/docs/root-source](configuration/docs/root-source/) | +| Single source root | [configuration/single-source.yml](configuration/single-source.yml) | [configuration/docs/single-source](configuration/docs/single-source/) | +| Named source roots | [configuration/named-sources.yml](configuration/named-sources.yml) | [configuration/docs/named-sources](configuration/docs/named-sources/) | +| Include and exclude | [configuration/include-exclude.yml](configuration/include-exclude.yml) | [configuration/docs/include-exclude](configuration/docs/include-exclude/) | +| Multiple embeddings | [configuration/multiple-embeddings.yml](configuration/multiple-embeddings.yml) | [configuration/docs/multiple](configuration/docs/multiple/) | + +```bash +go run ./main.go -mode check -config-path examples/showcase/configuration/root-source.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/single-source.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/named-sources.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/include-exclude.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/multiple-embeddings.yml +``` diff --git a/examples/showcase/code/java/org/showcase/CommentModes.java b/examples/showcase/code/java/org/showcase/CommentModes.java new file mode 100644 index 0000000..3cbf65f --- /dev/null +++ b/examples/showcase/code/java/org/showcase/CommentModes.java @@ -0,0 +1,14 @@ +package org.showcase; + +/** + * Creates public greetings. + */ +public interface CommentModes { + /* + * Internal implementation note. + */ + String URL = "http://example.org/*not-comment*/"; + + // Regular inline comment. + String greet(String name); // trailing inline comment. +} diff --git a/examples/showcase/code/java/org/showcase/Greeting.java b/examples/showcase/code/java/org/showcase/Greeting.java new file mode 100644 index 0000000..60ab7c9 --- /dev/null +++ b/examples/showcase/code/java/org/showcase/Greeting.java @@ -0,0 +1,17 @@ +package org.showcase; + +// #docfragment "Greeter class" +public final class Greeting { + private Greeting() {} + + // #docfragment "main()" + public static void main(String[] args) { + System.out.println(greeting("Ada")); + } + // #enddocfragment "main()" + + public static String greeting(String name) { + return "Hello, " + name + "!"; + } +} +// #enddocfragment "Greeter class" diff --git a/examples/showcase/code/java/org/showcase/MultiPartWorkflow.java b/examples/showcase/code/java/org/showcase/MultiPartWorkflow.java new file mode 100644 index 0000000..71c4ba3 --- /dev/null +++ b/examples/showcase/code/java/org/showcase/MultiPartWorkflow.java @@ -0,0 +1,23 @@ +package org.showcase; + +// #docfragment "Workflow" +public final class MultiPartWorkflow { + // #enddocfragment "Workflow" + private MultiPartWorkflow() {} + + // #docfragment "Workflow" + public static void start() { + // #enddocfragment "Workflow" + System.out.println("Internal setup is hidden."); + // #docfragment "Workflow" + System.out.println("Start workflow"); + } + // #enddocfragment "Workflow" + + public static void finish() { + System.out.println("Finish workflow"); + } + +// #docfragment "Workflow" +} +// #enddocfragment "Workflow" diff --git a/examples/showcase/code/java/org/showcase/OverlappingFragments.java b/examples/showcase/code/java/org/showcase/OverlappingFragments.java new file mode 100644 index 0000000..602ec4a --- /dev/null +++ b/examples/showcase/code/java/org/showcase/OverlappingFragments.java @@ -0,0 +1,28 @@ +package org.showcase; + +// #docfragment "Class wrapper", "Greeting method" +public final class OverlappingFragments { + // #enddocfragment "Class wrapper", "Greeting method" + private OverlappingFragments() {} + + // #docfragment "Class wrapper" + public static void boot() { + // #enddocfragment "Class wrapper" + System.out.println("Boot details are hidden."); + // #docfragment "Class wrapper" + System.out.println("Boot complete"); + } + // #enddocfragment "Class wrapper" + + // #docfragment "Greeting method" + public static String greeting(String name) { + // #enddocfragment "Greeting method" + var normalized = name.trim(); + // #docfragment "Greeting method" + return "Hello, " + normalized + "!"; + } + // #enddocfragment "Greeting method" + +// #docfragment "Class wrapper", "Greeting method" +} +// #enddocfragment "Class wrapper", "Greeting method" diff --git a/examples/showcase/code/java/org/showcase/PatternSamples.java b/examples/showcase/code/java/org/showcase/PatternSamples.java new file mode 100644 index 0000000..8d429f8 --- /dev/null +++ b/examples/showcase/code/java/org/showcase/PatternSamples.java @@ -0,0 +1,22 @@ +package org.showcase; + +class PatternSamples { + + private static final String ESCAPED_NEWLINE = "\n"; + + @Scenario + @Name("adds two numbers") + void addsTwoNumbers() { + int total = 1 + 1; + + assertEquals(2, total); + } + + @Scenario + @Name("subtracts two numbers") + void subtractsTwoNumbers() { + int total = 2 - 1; + + assertEquals(1, total); + } +} diff --git a/examples/showcase/code/kotlin/org/showcase/KotlinGreeting.kt b/examples/showcase/code/kotlin/org/showcase/KotlinGreeting.kt new file mode 100644 index 0000000..5b06ade --- /dev/null +++ b/examples/showcase/code/kotlin/org/showcase/KotlinGreeting.kt @@ -0,0 +1,11 @@ +package org.showcase + +object KotlinGreeting { + + // #docfragment "main()" + @JvmStatic + fun main(args: Array) { + println("Hello from Kotlin") + } + // #enddocfragment "main()" +} diff --git a/examples/showcase/code/text/glob-patterns.txt b/examples/showcase/code/text/glob-patterns.txt new file mode 100644 index 0000000..d7529af --- /dev/null +++ b/examples/showcase/code/text/glob-patterns.txt @@ -0,0 +1,5 @@ + padded text +Use * to multiply +The total is $5 +The value ends with $ +^ starts with caret diff --git a/examples/showcase/configuration/README.md b/examples/showcase/configuration/README.md new file mode 100644 index 0000000..872da7a --- /dev/null +++ b/examples/showcase/configuration/README.md @@ -0,0 +1,56 @@ +# Configuration Examples + +These examples show the supported YAML configuration shapes. Run commands from +the repository root. + +## Repository Root Source + +[root-source.yml](root-source.yml) uses the repository root as a named source +root. Instructions in [docs/root-source](docs/root-source/) embed files from +the project root with the `$repo` prefix. + +```bash +go run ./main.go -mode check -config-path examples/showcase/configuration/root-source.yml +``` + +## Single Showcase Source Root + +[single-source.yml](single-source.yml) uses one unnamed `code-path`. +Instructions in [docs/single-source](docs/single-source/) use paths relative to +that root without a `$name` prefix. + +```bash +go run ./main.go -mode check -config-path examples/showcase/configuration/single-source.yml +``` + +## Named Source Roots + +[named-sources.yml](named-sources.yml) defines Java, Kotlin, and text source +roots. Instructions in [docs/named-sources](docs/named-sources/) choose a +source root with `$java`, `$kotlin`, or `$text`. + +```bash +go run ./main.go -mode check -config-path examples/showcase/configuration/named-sources.yml +``` + +## Include And Exclude Patterns + +[include-exclude.yml](include-exclude.yml) processes Markdown files in +[docs/include-exclude](docs/include-exclude/) but excludes +[excluded.md](docs/include-exclude/excluded.md). That file intentionally +references a missing source file, so the check succeeds only when +`doc-excludes` is applied. + +```bash +go run ./main.go -mode check -config-path examples/showcase/configuration/include-exclude.yml +``` + +## Multiple Embeddings + +[multiple-embeddings.yml](multiple-embeddings.yml) uses the `embeddings` list +to process two independent documentation roots in +[docs/multiple](docs/multiple/) in one run. + +```bash +go run ./main.go -mode check -config-path examples/showcase/configuration/multiple-embeddings.yml +``` diff --git a/examples/showcase/configuration/docs/include-exclude/excluded.md b/examples/showcase/configuration/docs/include-exclude/excluded.md new file mode 100644 index 0000000..a21fb47 --- /dev/null +++ b/examples/showcase/configuration/docs/include-exclude/excluded.md @@ -0,0 +1,8 @@ +# Excluded Document + +The config excludes this file. The intentionally missing source file proves +that excluded documents are not processed. + + +```java +``` diff --git a/examples/showcase/configuration/docs/include-exclude/included.md b/examples/showcase/configuration/docs/include-exclude/included.md new file mode 100644 index 0000000..7faa5e7 --- /dev/null +++ b/examples/showcase/configuration/docs/include-exclude/included.md @@ -0,0 +1,11 @@ +# Included Document + +The `doc-includes` pattern selects this Markdown file, so the instruction is +processed normally. + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` diff --git a/examples/showcase/configuration/docs/multiple/java/greeting.md b/examples/showcase/configuration/docs/multiple/java/greeting.md new file mode 100644 index 0000000..1761683 --- /dev/null +++ b/examples/showcase/configuration/docs/multiple/java/greeting.md @@ -0,0 +1,10 @@ +# Java Embedding Entry + +This document is processed by the `java-guide` entry in an `embeddings` config. + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` diff --git a/examples/showcase/configuration/docs/multiple/kotlin/greeting.md b/examples/showcase/configuration/docs/multiple/kotlin/greeting.md new file mode 100644 index 0000000..81c89d4 --- /dev/null +++ b/examples/showcase/configuration/docs/multiple/kotlin/greeting.md @@ -0,0 +1,12 @@ +# Kotlin Embedding Entry + +This document is processed by the `kotlin-guide` entry in the same +`embeddings` config. + + +```kotlin +@JvmStatic +fun main(args: Array) { + println("Hello from Kotlin") +} +``` diff --git a/examples/showcase/configuration/docs/named-sources/java-greeting.md b/examples/showcase/configuration/docs/named-sources/java-greeting.md new file mode 100644 index 0000000..6107d34 --- /dev/null +++ b/examples/showcase/configuration/docs/named-sources/java-greeting.md @@ -0,0 +1,11 @@ +# Named Java Source + +This config has multiple named source roots. The `$java` prefix chooses the Java +root before resolving the relative path. + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` diff --git a/examples/showcase/configuration/docs/named-sources/kotlin-greeting.md b/examples/showcase/configuration/docs/named-sources/kotlin-greeting.md new file mode 100644 index 0000000..f3b6160 --- /dev/null +++ b/examples/showcase/configuration/docs/named-sources/kotlin-greeting.md @@ -0,0 +1,12 @@ +# Named Kotlin Source + +The same config can embed from a different root by changing the source-root +prefix in the instruction. + + +```kotlin +@JvmStatic +fun main(args: Array) { + println("Hello from Kotlin") +} +``` diff --git a/examples/showcase/configuration/docs/named-sources/text-line.md b/examples/showcase/configuration/docs/named-sources/text-line.md new file mode 100644 index 0000000..f2e02fb --- /dev/null +++ b/examples/showcase/configuration/docs/named-sources/text-line.md @@ -0,0 +1,9 @@ +# Named Text Source + +Named roots do not need to be language-specific. This example embeds one line +from the text source root. + + +```text +The total is $5 +``` diff --git a/examples/showcase/configuration/docs/root-source/go-mod.md b/examples/showcase/configuration/docs/root-source/go-mod.md new file mode 100644 index 0000000..cbc414e --- /dev/null +++ b/examples/showcase/configuration/docs/root-source/go-mod.md @@ -0,0 +1,49 @@ +# Repository Root Source + +This configuration example uses the repository root as a named source root. The +instruction embeds `go.mod` through the `$repo` prefix. + + +```go +// Copyright 2026, TeamDev. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +module embed-code/embed-code-go + +go 1.22.1 + +require ( + github.com/bmatcuk/doublestar/v4 v4.6.1 + github.com/gobwas/glob v0.2.3 + github.com/onsi/ginkgo/v2 v2.20.2 + github.com/onsi/gomega v1.34.2 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect + github.com/stretchr/testify v1.9.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect +) +``` diff --git a/examples/showcase/configuration/docs/root-source/version.md b/examples/showcase/configuration/docs/root-source/version.md new file mode 100644 index 0000000..4e822b8 --- /dev/null +++ b/examples/showcase/configuration/docs/root-source/version.md @@ -0,0 +1,8 @@ +# Repository Root Line Pattern + +The same root-source configuration can select a single line from a root file. + + +```go +const Version = "1.2.2" +``` diff --git a/examples/showcase/configuration/docs/single-source/greeting.md b/examples/showcase/configuration/docs/single-source/greeting.md new file mode 100644 index 0000000..91bb961 --- /dev/null +++ b/examples/showcase/configuration/docs/single-source/greeting.md @@ -0,0 +1,11 @@ +# Single Source Root + +This config uses one unnamed `code-path`, so instructions refer to source files +relative to that root without a `$name` prefix. + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` diff --git a/examples/showcase/configuration/include-exclude.yml b/examples/showcase/configuration/include-exclude.yml new file mode 100644 index 0000000..d165aaf --- /dev/null +++ b/examples/showcase/configuration/include-exclude.yml @@ -0,0 +1,6 @@ +code-path: examples/showcase/code/java +docs-path: examples/showcase/configuration/docs/include-exclude +doc-includes: + - "**/*.md" +doc-excludes: + - excluded.md diff --git a/examples/showcase/configuration/multiple-embeddings.yml b/examples/showcase/configuration/multiple-embeddings.yml new file mode 100644 index 0000000..0316823 --- /dev/null +++ b/examples/showcase/configuration/multiple-embeddings.yml @@ -0,0 +1,13 @@ +embeddings: + - name: java-guide + code-path: examples/showcase/code/java + docs-path: examples/showcase/configuration/docs/multiple/java + doc-includes: + - "**/*.md" + - name: kotlin-guide + code-path: + - name: kotlin + path: examples/showcase/code/kotlin + docs-path: examples/showcase/configuration/docs/multiple/kotlin + doc-includes: + - "**/*.md" diff --git a/examples/showcase/configuration/named-sources.yml b/examples/showcase/configuration/named-sources.yml new file mode 100644 index 0000000..a624a75 --- /dev/null +++ b/examples/showcase/configuration/named-sources.yml @@ -0,0 +1,10 @@ +code-path: + - name: java + path: examples/showcase/code/java + - name: kotlin + path: examples/showcase/code/kotlin + - name: text + path: examples/showcase/code/text +docs-path: examples/showcase/configuration/docs/named-sources +doc-includes: + - "**/*.md" diff --git a/examples/showcase/configuration/root-source.yml b/examples/showcase/configuration/root-source.yml new file mode 100644 index 0000000..f7209a3 --- /dev/null +++ b/examples/showcase/configuration/root-source.yml @@ -0,0 +1,6 @@ +code-path: + - name: repo + path: . +docs-path: examples/showcase/configuration/docs/root-source +doc-includes: + - "**/*.md" diff --git a/examples/showcase/configuration/single-source.yml b/examples/showcase/configuration/single-source.yml new file mode 100644 index 0000000..45fac63 --- /dev/null +++ b/examples/showcase/configuration/single-source.yml @@ -0,0 +1,4 @@ +code-path: examples/showcase/code/java +docs-path: examples/showcase/configuration/docs/single-source +doc-includes: + - "**/*.md" diff --git a/examples/showcase/docs/01-whole-file-source.md b/examples/showcase/docs/01-whole-file-source.md new file mode 100644 index 0000000..ed7016e --- /dev/null +++ b/examples/showcase/docs/01-whole-file-source.md @@ -0,0 +1,20 @@ +# Whole File From A Showcase Source + +Omitting `fragment`, `start`, `end`, and `line` embeds the whole source file. + + +```java +package org.showcase; + +public final class Greeting { + private Greeting() {} + + public static void main(String[] args) { + System.out.println(greeting("Ada")); + } + + public static String greeting(String name) { + return "Hello, " + name + "!"; + } +} +``` diff --git a/examples/showcase/docs/02-source-line-pattern.md b/examples/showcase/docs/02-source-line-pattern.md new file mode 100644 index 0000000..93c342a --- /dev/null +++ b/examples/showcase/docs/02-source-line-pattern.md @@ -0,0 +1,8 @@ +# One Line From A Showcase Source + +The `line` attribute embeds only the first source line matching the pattern. + + +```java +return "Hello, " + name + "!"; +``` diff --git a/examples/showcase/docs/03-named-fragment.md b/examples/showcase/docs/03-named-fragment.md new file mode 100644 index 0000000..2e6e641 --- /dev/null +++ b/examples/showcase/docs/03-named-fragment.md @@ -0,0 +1,11 @@ +# Named Fragment + +The `fragment` attribute embeds lines between matching `#docfragment` and +`#enddocfragment` markers. Marker lines are not rendered. + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` diff --git a/examples/showcase/docs/04-paired-instruction-tag.md b/examples/showcase/docs/04-paired-instruction-tag.md new file mode 100644 index 0000000..bf4dcb6 --- /dev/null +++ b/examples/showcase/docs/04-paired-instruction-tag.md @@ -0,0 +1,36 @@ +# Paired Instruction Tag + +Instructions may be self-closing or paired. The self-closing form is supported, +but this showcase uses paired tags because some Markdown renderers display the +XML-style self-closing tag awkwardly. + +## Short Paired Tag + +This compact paired form is useful when all attributes fit naturally in one +line. + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` + +## Paired Tag With A Larger Fragment + +The same paired form works for larger snippets too. + + +```java +public final class Greeting { + private Greeting() {} + + public static void main(String[] args) { + System.out.println(greeting("Ada")); + } + + public static String greeting(String name) { + return "Hello, " + name + "!"; + } +} +``` diff --git a/examples/showcase/docs/05-named-source-root.md b/examples/showcase/docs/05-named-source-root.md new file mode 100644 index 0000000..f138e1c --- /dev/null +++ b/examples/showcase/docs/05-named-source-root.md @@ -0,0 +1,13 @@ +# Named Source Roots + +When a configuration has named code paths, the instruction chooses one with the +`$name/relative/path` prefix. This example reads Kotlin code from the `kotlin` +source root while the Java examples read from `java`. + + +```kotlin +@JvmStatic +fun main(args: Array) { + println("Hello from Kotlin") +} +``` diff --git a/examples/showcase/docs/06-start-end-pattern.md b/examples/showcase/docs/06-start-end-pattern.md new file mode 100644 index 0000000..416d9c2 --- /dev/null +++ b/examples/showcase/docs/06-start-end-pattern.md @@ -0,0 +1,18 @@ +# Start And End Patterns + +The `start` and `end` attributes select an inclusive source range. Patterns are +glob-like and the end search starts after the matched start. + + +```java +@Scenario +@Name("adds two numbers") +void addsTwoNumbers() { + int total = 1 + 1; + + assertEquals(2, total); +} +``` diff --git a/examples/showcase/docs/07-multi-line-pattern.md b/examples/showcase/docs/07-multi-line-pattern.md new file mode 100644 index 0000000..b30b84b --- /dev/null +++ b/examples/showcase/docs/07-multi-line-pattern.md @@ -0,0 +1,18 @@ +# Multi-Line Patterns + +Use `\n` inside a pattern when the match should span consecutive source lines. +Each pattern line still uses the same glob syntax. + + +```java +@Scenario +@Name("adds two numbers") +void addsTwoNumbers() { + int total = 1 + 1; + + assertEquals(2, total); +} +``` diff --git a/examples/showcase/docs/08-escaped-glob-character.md b/examples/showcase/docs/08-escaped-glob-character.md new file mode 100644 index 0000000..e2e78ec --- /dev/null +++ b/examples/showcase/docs/08-escaped-glob-character.md @@ -0,0 +1,9 @@ +# Escaped Glob Characters + +Backslashes match glob control characters literally. This case embeds a line +that contains a literal asterisk. + + +```text +Use * to multiply +``` diff --git a/examples/showcase/docs/09-escaped-newline-text.md b/examples/showcase/docs/09-escaped-newline-text.md new file mode 100644 index 0000000..61d7dda --- /dev/null +++ b/examples/showcase/docs/09-escaped-newline-text.md @@ -0,0 +1,11 @@ +# Escaped Newline Text + +Use `\\n` when the source line contains the characters backslash and `n`. +Quote characters may be escaped as `\"` inside instruction attributes. + + +```java +private static final String ESCAPED_NEWLINE = "\n"; +``` diff --git a/examples/showcase/docs/10-comment-filtering.md b/examples/showcase/docs/10-comment-filtering.md new file mode 100644 index 0000000..ad0aeae --- /dev/null +++ b/examples/showcase/docs/10-comment-filtering.md @@ -0,0 +1,19 @@ +# Comment Filtering + +The `comments` attribute controls which recognized comments remain in the +rendered snippet. This example keeps documentation comments and removes regular +comments from Java source. + + +```java +package org.showcase; + +/** + * Creates public greetings. + */ +public interface CommentModes { + String URL = "http://example.org/*not-comment*/"; + + String greet(String name); +} +``` diff --git a/examples/showcase/docs/11-multi-part-fragment-separator.md b/examples/showcase/docs/11-multi-part-fragment-separator.md new file mode 100644 index 0000000..9ff4c42 --- /dev/null +++ b/examples/showcase/docs/11-multi-part-fragment-separator.md @@ -0,0 +1,16 @@ +# Multi-Part Fragment Separator + +Fragments with the same name may appear in several source regions. The rendered +parts are joined in source order with the configured separator. + + +```java +public final class MultiPartWorkflow { + // ... + public static void start() { + // ... + System.out.println("Start workflow"); + } +// ... +} +``` diff --git a/examples/showcase/docs/12-overlapping-fragments.md b/examples/showcase/docs/12-overlapping-fragments.md new file mode 100644 index 0000000..51362fe --- /dev/null +++ b/examples/showcase/docs/12-overlapping-fragments.md @@ -0,0 +1,16 @@ +# Overlapping Fragments + +Several fragments can open or close on the same marker line. This example uses +an overlapping fragment that shares the class wrapper with another fragment. + + +```java +public final class OverlappingFragments { + // ... + public static String greeting(String name) { + // ... + return "Hello, " + normalized + "!"; + } +// ... +} +``` diff --git a/examples/showcase/docs/13-markdown-fence-shielding.md b/examples/showcase/docs/13-markdown-fence-shielding.md new file mode 100644 index 0000000..4f6d2c4 --- /dev/null +++ b/examples/showcase/docs/13-markdown-fence-shielding.md @@ -0,0 +1,11 @@ +# Instructions Inside Markdown Fences + +Instruction-looking text inside an ordinary Markdown code fence is preserved as +documentation content. It is not executed because the parser tracks Markdown +fence state before looking for instructions. + +````markdown + +```go +``` +```` diff --git a/examples/showcase/docs/html-showcase.html b/examples/showcase/docs/html-showcase.html new file mode 100644 index 0000000..b3a62d0 --- /dev/null +++ b/examples/showcase/docs/html-showcase.html @@ -0,0 +1,17 @@ + + + +

HTML Embedding Showcase

+

+ HTML files are scanned too when the include patterns allow them. The + instruction still needs a Markdown code fence after it. +

+ + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` + + diff --git a/examples/showcase/docs/ignored-by-exclude.md b/examples/showcase/docs/ignored-by-exclude.md new file mode 100644 index 0000000..f1c4950 --- /dev/null +++ b/examples/showcase/docs/ignored-by-exclude.md @@ -0,0 +1,9 @@ +# Excluded Showcase File + +The positive configuration excludes this file. Its intentionally broken +instruction verifies that `doc-excludes` prevents selected files from being +processed. + + +```go +``` diff --git a/examples/showcase/embed-code.yml b/examples/showcase/embed-code.yml new file mode 100644 index 0000000..b2a3bb0 --- /dev/null +++ b/examples/showcase/embed-code.yml @@ -0,0 +1,14 @@ +code-path: + - name: java + path: examples/showcase/code/java + - name: kotlin + path: examples/showcase/code/kotlin + - name: text + path: examples/showcase/code/text +docs-path: examples/showcase/docs +doc-includes: + - "**/*.md" + - "**/*.html" +doc-excludes: + - ignored-by-exclude.md +separator: "// ..." diff --git a/examples/showcase/negative/docs/invalid-attributes.md b/examples/showcase/negative/docs/invalid-attributes.md new file mode 100644 index 0000000..d8feab3 --- /dev/null +++ b/examples/showcase/negative/docs/invalid-attributes.md @@ -0,0 +1,8 @@ +# Invalid Attributes + +This scenario fails because `fragment` cannot be combined with `line`, `start`, +or `end`. + + +```java +``` diff --git a/examples/showcase/negative/docs/missing-code-fence.md b/examples/showcase/negative/docs/missing-code-fence.md new file mode 100644 index 0000000..32c060a --- /dev/null +++ b/examples/showcase/negative/docs/missing-code-fence.md @@ -0,0 +1,7 @@ +# Missing Code Fence + +This scenario fails because every active instruction must be followed by a +Markdown code fence. + + +This line is not a code fence. diff --git a/examples/showcase/negative/docs/missing-fragment.md b/examples/showcase/negative/docs/missing-fragment.md new file mode 100644 index 0000000..7a89643 --- /dev/null +++ b/examples/showcase/negative/docs/missing-fragment.md @@ -0,0 +1,8 @@ +# Missing Fragment + +This scenario fails because the source file exists, but the requested named +fragment does not. + + +```java +``` diff --git a/examples/showcase/negative/docs/missing-pattern.md b/examples/showcase/negative/docs/missing-pattern.md new file mode 100644 index 0000000..32360d6 --- /dev/null +++ b/examples/showcase/negative/docs/missing-pattern.md @@ -0,0 +1,8 @@ +# Missing Pattern + +This scenario fails because the source file exists, but no line matches the +requested line pattern. + + +```java +``` diff --git a/examples/showcase/negative/docs/missing-source.md b/examples/showcase/negative/docs/missing-source.md new file mode 100644 index 0000000..5aeabd7 --- /dev/null +++ b/examples/showcase/negative/docs/missing-source.md @@ -0,0 +1,8 @@ +# Missing Source + +This scenario fails because the instruction points to a file that cannot be +resolved from the configured source roots. + + +```java +``` diff --git a/examples/showcase/negative/docs/stale-snippet.md b/examples/showcase/negative/docs/stale-snippet.md new file mode 100644 index 0000000..b74d5c2 --- /dev/null +++ b/examples/showcase/negative/docs/stale-snippet.md @@ -0,0 +1,11 @@ +# Stale Snippet + +This scenario is syntactically valid, but check mode reports it as stale because +the rendered code fence does not match the current source fragment. + + +```java +public static void main(String[] args) { + System.out.println("Out of date"); +} +``` diff --git a/examples/showcase/negative/docs/unclosed-code-fence.md b/examples/showcase/negative/docs/unclosed-code-fence.md new file mode 100644 index 0000000..a89f5cd --- /dev/null +++ b/examples/showcase/negative/docs/unclosed-code-fence.md @@ -0,0 +1,9 @@ +# Unclosed Code Fence + +This scenario fails because the opening fence after the instruction is never +closed. + + +```java +public static void main(String[] args) { +} diff --git a/examples/showcase/negative/processing-errors.yml b/examples/showcase/negative/processing-errors.yml new file mode 100644 index 0000000..90cc680 --- /dev/null +++ b/examples/showcase/negative/processing-errors.yml @@ -0,0 +1,13 @@ +code-path: + - name: repo + path: . + - name: java + path: examples/showcase/code/java +docs-path: examples/showcase/negative/docs +doc-includes: + - missing-source.md + - missing-fragment.md + - missing-pattern.md + - invalid-attributes.md + - missing-code-fence.md + - unclosed-code-fence.md diff --git a/examples/showcase/negative/stale.yml b/examples/showcase/negative/stale.yml new file mode 100644 index 0000000..0cd2bd6 --- /dev/null +++ b/examples/showcase/negative/stale.yml @@ -0,0 +1,6 @@ +code-path: + - name: java + path: examples/showcase/code/java +docs-path: examples/showcase/negative/docs +doc-includes: + - stale-snippet.md diff --git a/examples/showcase/showcase_test.go b/examples/showcase/showcase_test.go new file mode 100644 index 0000000..46a2c55 --- /dev/null +++ b/examples/showcase/showcase_test.go @@ -0,0 +1,354 @@ +//go:build showcase + +// Copyright 2026, TeamDev. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package showcase_test + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" +) + +// TestShowcasePositiveFlow verifies the showcase docs can be checked, detected as stale, +// repaired with embed mode, and checked again without changing repository files. +func TestShowcasePositiveFlow(t *testing.T) { + repoRoot := findRepoRoot(t) + docsRoot := copyShowcaseDocs(t, repoRoot, "docs") + configPath := writeShowcaseConfig(t, repoRoot, docsRoot) + + checkOutput, err := runEmbedCode(t, repoRoot, "check", configPath) + if err != nil { + t.Fatalf("expected positive showcase check to pass:\n%s", checkOutput) + } + + staleDoc := filepath.Join(docsRoot, "01-whole-file-source.md") + replaceInFile(t, staleDoc, "package org.showcase;", "package stale.showcase;") + + staleOutput, err := runEmbedCode(t, repoRoot, "check", configPath) + if err == nil { + t.Fatalf("expected stale showcase check to fail:\n%s", staleOutput) + } + assertOutputContains(t, staleOutput, "File to update:") + assertOutputContains(t, staleOutput, "01-whole-file-source.md") + + embedOutput, err := runEmbedCode(t, repoRoot, "embed", configPath) + if err != nil { + t.Fatalf("expected positive showcase embed to repair stale doc:\n%s", embedOutput) + } + assertOutputContains(t, embedOutput, "Embedding process finished.") + + finalOutput, err := runEmbedCode(t, repoRoot, "check", configPath) + if err != nil { + t.Fatalf("expected positive showcase check to pass after embed:\n%s", finalOutput) + } +} + +// TestShowcaseNegativeScenarios verifies each negative document fails with its expected reason. +func TestShowcaseNegativeScenarios(t *testing.T) { + repoRoot := findRepoRoot(t) + + cases := []struct { + name string + doc string + sources []namedSource + expected []string + }{ + { + name: "missing source", + doc: "missing-source.md", + sources: []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + }, + expected: []string{ + "code file `$java/org/showcase/DoesNotExist.java", + "not found", + }, + }, + { + name: "missing fragment", + doc: "missing-fragment.md", + sources: []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + }, + expected: []string{ + "fragment `does not exist`", + "not found", + }, + }, + { + name: "missing pattern", + doc: "missing-pattern.md", + sources: []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + }, + expected: []string{ + "matches the line pattern", + "doesNotExistPattern", + }, + }, + { + name: "invalid attributes", + doc: "invalid-attributes.md", + sources: []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + }, + expected: []string{ + "must NOT specify both a fragment name and start/end/line patterns", + }, + }, + { + name: "missing code fence", + doc: "missing-code-fence.md", + sources: []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + }, + expected: []string{ + "expected a markdown code fence after the embedding instruction", + }, + }, + { + name: "unclosed code fence", + doc: "unclosed-code-fence.md", + sources: []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + }, + expected: []string{ + "the markdown code fence after the embedding instruction is not closed", + }, + }, + { + name: "stale snippet", + doc: "stale-snippet.md", + sources: []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + }, + expected: []string{ + "File to update:", + "stale-snippet.md", + "the documentation files are not up-to-date with code files", + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + docsRoot := copyShowcaseDocs(t, repoRoot, filepath.Join("negative", "docs")) + configPath := writeSingleDocConfig(t, docsRoot, tc.sources, tc.doc) + + output, err := runEmbedCode(t, repoRoot, "check", configPath) + if err == nil { + t.Fatalf("expected negative scenario to fail:\n%s", output) + } + for _, expected := range tc.expected { + assertOutputContains(t, output, expected) + } + }) + } +} + +// TestShowcaseConfigurationExamples verifies the runnable configuration examples. +func TestShowcaseConfigurationExamples(t *testing.T) { + repoRoot := findRepoRoot(t) + + configs := []string{ + "root-source.yml", + "single-source.yml", + "named-sources.yml", + "include-exclude.yml", + "multiple-embeddings.yml", + } + + for _, config := range configs { + t.Run(config, func(t *testing.T) { + configPath := filepath.Join("examples", "showcase", "configuration", config) + output, err := runEmbedCode(t, repoRoot, "check", configPath) + if err != nil { + t.Fatalf("expected configuration example to pass:\n%s", output) + } + }) + } +} + +type namedSource struct { + name string + path string +} + +// findRepoRoot returns the repository root by walking up from this test file. +func findRepoRoot(t *testing.T) string { + t.Helper() + + _, filePath, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("could not locate showcase test file") + } + + return filepath.Clean(filepath.Join(filepath.Dir(filePath), "..", "..")) +} + +// copyShowcaseDocs copies one showcase documentation folder to a temporary test directory. +func copyShowcaseDocs(t *testing.T, repoRoot string, relativeSource string) string { + t.Helper() + + sourceRoot := filepath.Join(repoRoot, "examples", "showcase", relativeSource) + targetRoot := filepath.Join(t.TempDir(), "docs") + copyDir(t, sourceRoot, targetRoot) + + return targetRoot +} + +// copyDir recursively copies a directory tree while preserving regular file permissions. +func copyDir(t *testing.T, sourceRoot string, targetRoot string) { + t.Helper() + + err := filepath.WalkDir(sourceRoot, func(path string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + + relativePath, err := filepath.Rel(sourceRoot, path) + if err != nil { + return err + } + targetPath := filepath.Join(targetRoot, relativePath) + if entry.IsDir() { + return os.MkdirAll(targetPath, 0o755) + } + if !entry.Type().IsRegular() { + return nil + } + + data, err := os.ReadFile(path) + if err != nil { + return err + } + info, err := entry.Info() + if err != nil { + return err + } + + return os.WriteFile(targetPath, data, info.Mode()) + }) + if err != nil { + t.Fatalf("failed to copy showcase docs: %v", err) + } +} + +// writeShowcaseConfig creates a temp config that points at copied positive docs. +func writeShowcaseConfig(t *testing.T, repoRoot string, docsRoot string) string { + t.Helper() + + return writeConfig(t, docsRoot, []namedSource{ + {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + {name: "kotlin", path: filepath.Join(repoRoot, "examples", "showcase", "code", "kotlin")}, + {name: "text", path: filepath.Join(repoRoot, "examples", "showcase", "code", "text")}, + }, []string{"**/*.md", "**/*.html"}, []string{"ignored-by-exclude.md"}) +} + +// writeSingleDocConfig creates a temp config for one negative showcase document. +func writeSingleDocConfig( + t *testing.T, + docsRoot string, + sources []namedSource, + docInclude string, +) string { + t.Helper() + + return writeConfig(t, docsRoot, sources, []string{docInclude}, nil) +} + +// writeConfig writes a YAML config with absolute source and documentation paths. +func writeConfig( + t *testing.T, + docsRoot string, + sources []namedSource, + includes []string, + excludes []string, +) string { + t.Helper() + + var builder strings.Builder + builder.WriteString("code-path:\n") + for _, source := range sources { + builder.WriteString(fmt.Sprintf(" - name: %s\n", source.name)) + builder.WriteString(fmt.Sprintf(" path: %s\n", filepath.ToSlash(source.path))) + } + builder.WriteString(fmt.Sprintf("docs-path: %s\n", filepath.ToSlash(docsRoot))) + builder.WriteString("doc-includes:\n") + for _, include := range includes { + builder.WriteString(fmt.Sprintf(" - %q\n", include)) + } + if len(excludes) > 0 { + builder.WriteString("doc-excludes:\n") + for _, exclude := range excludes { + builder.WriteString(fmt.Sprintf(" - %q\n", exclude)) + } + } + builder.WriteString("separator: \"// ...\"\n") + + configPath := filepath.Join(t.TempDir(), "embed-code.yml") + if err := os.WriteFile(configPath, []byte(builder.String()), 0o644); err != nil { + t.Fatalf("failed to write temp config: %v", err) + } + + return configPath +} + +// runEmbedCode executes the CLI through `go run` and returns combined output. +func runEmbedCode(t *testing.T, repoRoot string, mode string, configPath string) (string, error) { + t.Helper() + + cmd := exec.Command("go", "run", "./main.go", "-mode", mode, "-config-path", configPath) + cmd.Dir = repoRoot + output, err := cmd.CombinedOutput() + + return string(output), err +} + +// replaceInFile replaces one expected substring in a copied documentation file. +func replaceInFile(t *testing.T, path string, oldText string, newText string) { + t.Helper() + + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed to read %s: %v", path, err) + } + content := string(data) + if !strings.Contains(content, oldText) { + t.Fatalf("expected %s to contain %q", path, oldText) + } + content = strings.Replace(content, oldText, newText, 1) + if err = os.WriteFile(path, []byte(content), 0o644); err != nil { + t.Fatalf("failed to write %s: %v", path, err) + } +} + +// assertOutputContains fails the test when a command output does not include a substring. +func assertOutputContains(t *testing.T, output string, expected string) { + t.Helper() + + if !strings.Contains(output, expected) { + t.Fatalf("expected output to contain %q:\n%s", expected, output) + } +} diff --git a/go.mod b/go.mod index 637cf52..0f202b0 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -// Copyright 2024, TeamDev. All rights reserved. +// Copyright 2026, TeamDev. All rights reserved. // // Redistribution and use in source and/or binary forms, with or without // modification, must retain the above copyright notice and the following From 53718347044845baa4b85e8354f218bcfe3f5a3e Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 17:13:45 +0200 Subject: [PATCH 02/14] Add more descriptions. --- EMBEDDING.md | 5 ++ README.md | 5 ++ examples/showcase/README.md | 23 +++++++- examples/showcase/configuration/README.md | 22 +++++++- .../docs/include-exclude/excluded.md | 6 +++ .../docs/include-exclude/included.md | 7 +++ .../docs/multiple/java/greeting.md | 7 +++ .../docs/multiple/kotlin/greeting.md | 7 +++ .../docs/named-sources/java-greeting.md | 6 +++ .../docs/named-sources/kotlin-greeting.md | 6 +++ .../docs/named-sources/text-line.md | 7 +++ .../configuration/docs/root-source/go-mod.md | 52 ++++--------------- .../configuration/docs/root-source/version.md | 7 +++ .../docs/single-source/greeting.md | 6 +++ .../showcase/docs/01-whole-file-source.md | 11 +++- .../showcase/docs/02-source-line-pattern.md | 13 ++++- examples/showcase/docs/03-named-fragment.md | 11 +++- .../docs/04-paired-instruction-tag.md | 27 ++-------- .../showcase/docs/05-named-source-root.md | 11 ++-- .../showcase/docs/06-start-end-pattern.md | 11 +++- .../showcase/docs/07-multi-line-pattern.md | 11 +++- .../docs/08-escaped-glob-character.md | 10 +++- .../showcase/docs/09-escaped-newline-text.md | 10 +++- .../showcase/docs/10-comment-filtering.md | 12 +++-- .../docs/11-multi-part-fragment-separator.md | 11 +++- .../showcase/docs/12-overlapping-fragments.md | 11 +++- .../docs/13-markdown-fence-shielding.md | 11 ++-- examples/showcase/docs/html-showcase.html | 9 +++- examples/showcase/docs/ignored-by-exclude.md | 12 +++-- .../negative/docs/invalid-attributes.md | 10 +++- .../negative/docs/missing-code-fence.md | 11 +++- .../negative/docs/missing-fragment.md | 9 +++- .../showcase/negative/docs/missing-pattern.md | 11 +++- .../showcase/negative/docs/missing-source.md | 10 +++- .../showcase/negative/docs/stale-snippet.md | 11 +++- .../negative/docs/unclosed-code-fence.md | 10 +++- 36 files changed, 308 insertions(+), 111 deletions(-) diff --git a/EMBEDDING.md b/EMBEDDING.md index 4b091d8..d0ea407 100644 --- a/EMBEDDING.md +++ b/EMBEDDING.md @@ -2,6 +2,11 @@ The `embed-code` utility uses a custom `` tag to insert code snippets from source files into Markdown documentation. +For executable examples of the embedding features described here, see the +[embed-code showcase](examples/showcase/README.md). The showcase uses paired +instruction tags and dedicated source fixtures so it can double as a guide and +an opt-in end-to-end test. + ## Embedding options There are two ways to specify which code fragment to embed: diff --git a/README.md b/README.md index bf33995..7265c99 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ This project is the implementation of `embed-code` utility written in Go. For the details of the usage in the documentation and the code, please refer to the [EMBEDDING.md](EMBEDDING.md). +For a runnable guide with positive examples, negative examples, and YAML +configuration shapes, see the [embed-code showcase](examples/showcase/README.md). +The showcase is an opt-in end-to-end test and is not part of the normal +`go test ./...` flow. + ## Running Embed Code operates in two modes: diff --git a/examples/showcase/README.md b/examples/showcase/README.md index a26ee5b..7a1f5af 100644 --- a/examples/showcase/README.md +++ b/examples/showcase/README.md @@ -9,6 +9,16 @@ Run commands from the repository root. The showcase-owned source examples live under [code](code/). Repository-root source examples are kept in the configuration showcase only. +## How To Use This Guide + +Read the files in [docs](docs/) from `01` onward when learning the embedding +syntax for the first time. Each file owns one positive case, explains how the +instruction is resolved, and shows the rendered code fence that embed mode owns. + +Then inspect [negative/docs](negative/docs/) to see the failures that check mode +reports without rewriting files. Finally, read [configuration](configuration/) +to compare the YAML shapes that point documentation roots at source roots. + ## Positive Flow Refresh the generated snippets: @@ -29,6 +39,11 @@ Run the opt-in test: go test -tags showcase ./examples/showcase ``` +The test copies the showcase docs to a temporary directory, runs check mode, +intentionally makes one copied snippet stale, repairs it with embed mode, and +then verifies the negative and configuration cases. The build tag keeps this +larger documentation test out of the default test flow. + The positive showcase covers: | Case | File | What it verifies | @@ -51,7 +66,9 @@ The positive showcase covers: ## Negative Flow -The negative examples are intentionally broken. These commands should fail. +The negative examples are intentionally broken. They are still documentation: +each file describes the mistake, the expected failure reason, and what a user +should fix in a real document. These commands should fail. ```bash go run ./main.go -mode check -config-path examples/showcase/negative/processing-errors.yml @@ -82,6 +99,10 @@ repository-root source, a single source root, named source roots, include/exclude patterns, and the `embeddings` list for multiple documentation roots. +Each configuration has its own docs root so the examples can be run separately. +Open the YAML file first, then follow the linked docs root to see how the +instructions use that configuration. + | Case | Config | Docs root | |----------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------| | Repository root | [configuration/root-source.yml](configuration/root-source.yml) | [configuration/docs/root-source](configuration/docs/root-source/) | diff --git a/examples/showcase/configuration/README.md b/examples/showcase/configuration/README.md index 872da7a..74747e6 100644 --- a/examples/showcase/configuration/README.md +++ b/examples/showcase/configuration/README.md @@ -1,7 +1,10 @@ # Configuration Examples -These examples show the supported YAML configuration shapes. Run commands from -the repository root. +These examples show the supported YAML configuration shapes. + +Each YAML file has a matching docs root under [docs](docs/). Read the YAML file +first, then open the linked docs folder to see how instructions use that source +configuration. ## Repository Root Source @@ -9,6 +12,10 @@ the repository root. root. Instructions in [docs/root-source](docs/root-source/) embed files from the project root with the `$repo` prefix. +Use this shape only when documentation really needs files from the repository +root. The main embedding showcase avoids root sources so ordinary examples stay +independent from project metadata. + ```bash go run ./main.go -mode check -config-path examples/showcase/configuration/root-source.yml ``` @@ -19,6 +26,9 @@ go run ./main.go -mode check -config-path examples/showcase/configuration/root-s Instructions in [docs/single-source](docs/single-source/) use paths relative to that root without a `$name` prefix. +This is the simplest configuration for one source tree and one documentation +tree. + ```bash go run ./main.go -mode check -config-path examples/showcase/configuration/single-source.yml ``` @@ -29,6 +39,8 @@ go run ./main.go -mode check -config-path examples/showcase/configuration/single roots. Instructions in [docs/named-sources](docs/named-sources/) choose a source root with `$java`, `$kotlin`, or `$text`. +Use this shape when one docs tree needs snippets from several source trees. + ```bash go run ./main.go -mode check -config-path examples/showcase/configuration/named-sources.yml ``` @@ -41,6 +53,9 @@ go run ./main.go -mode check -config-path examples/showcase/configuration/named- references a missing source file, so the check succeeds only when `doc-excludes` is applied. +Use this shape to skip drafts, generated docs, deprecated pages, or any file +that should not be scanned for active instructions. + ```bash go run ./main.go -mode check -config-path examples/showcase/configuration/include-exclude.yml ``` @@ -51,6 +66,9 @@ go run ./main.go -mode check -config-path examples/showcase/configuration/includ to process two independent documentation roots in [docs/multiple](docs/multiple/) in one run. +Use this shape when one command should process several independent +documentation targets with different source roots or settings. + ```bash go run ./main.go -mode check -config-path examples/showcase/configuration/multiple-embeddings.yml ``` diff --git a/examples/showcase/configuration/docs/include-exclude/excluded.md b/examples/showcase/configuration/docs/include-exclude/excluded.md index a21fb47..991f2a3 100644 --- a/examples/showcase/configuration/docs/include-exclude/excluded.md +++ b/examples/showcase/configuration/docs/include-exclude/excluded.md @@ -3,6 +3,12 @@ The config excludes this file. The intentionally missing source file proves that excluded documents are not processed. +## How It Works + +[../../include-exclude.yml](../../include-exclude.yml) lists this file in +`doc-excludes`. The instruction points at a missing source file, but the command +still succeeds because excluded files are skipped before instruction parsing. + ```java ``` diff --git a/examples/showcase/configuration/docs/include-exclude/included.md b/examples/showcase/configuration/docs/include-exclude/included.md index 7faa5e7..a7dd199 100644 --- a/examples/showcase/configuration/docs/include-exclude/included.md +++ b/examples/showcase/configuration/docs/include-exclude/included.md @@ -3,6 +3,13 @@ The `doc-includes` pattern selects this Markdown file, so the instruction is processed normally. +## How It Works + +[../../include-exclude.yml](../../include-exclude.yml) includes Markdown files +under this docs root. Because this file is not listed in `doc-excludes`, check +mode resolves the instruction and compares the rendered fence with the Java +source fragment. + ```java public static void main(String[] args) { diff --git a/examples/showcase/configuration/docs/multiple/java/greeting.md b/examples/showcase/configuration/docs/multiple/java/greeting.md index 1761683..727e27d 100644 --- a/examples/showcase/configuration/docs/multiple/java/greeting.md +++ b/examples/showcase/configuration/docs/multiple/java/greeting.md @@ -2,6 +2,13 @@ This document is processed by the `java-guide` entry in an `embeddings` config. +## How It Works + +[../../../multiple-embeddings.yml](../../../multiple-embeddings.yml) contains a +`java-guide` entry with its own `code-path` and `docs-path`. This document lives +under that entry's docs root, so the unprefixed source path resolves against the +Java source tree. + ```java public static void main(String[] args) { diff --git a/examples/showcase/configuration/docs/multiple/kotlin/greeting.md b/examples/showcase/configuration/docs/multiple/kotlin/greeting.md index 81c89d4..6a78a25 100644 --- a/examples/showcase/configuration/docs/multiple/kotlin/greeting.md +++ b/examples/showcase/configuration/docs/multiple/kotlin/greeting.md @@ -3,6 +3,13 @@ This document is processed by the `kotlin-guide` entry in the same `embeddings` config. +## How It Works + +[../../../multiple-embeddings.yml](../../../multiple-embeddings.yml) contains a +separate `kotlin-guide` entry. That entry defines a named `kotlin` source root, +so this instruction uses `$kotlin` even though it is processed by the same +top-level command as the Java entry. + ```kotlin @JvmStatic diff --git a/examples/showcase/configuration/docs/named-sources/java-greeting.md b/examples/showcase/configuration/docs/named-sources/java-greeting.md index 6107d34..655e392 100644 --- a/examples/showcase/configuration/docs/named-sources/java-greeting.md +++ b/examples/showcase/configuration/docs/named-sources/java-greeting.md @@ -3,6 +3,12 @@ This config has multiple named source roots. The `$java` prefix chooses the Java root before resolving the relative path. +## How It Works + +[../../named-sources.yml](../../named-sources.yml) defines a source root named +`java`. The instruction must include `$java` so the resolver knows which source +tree owns `org/showcase/Greeting.java`. + ```java public static void main(String[] args) { diff --git a/examples/showcase/configuration/docs/named-sources/kotlin-greeting.md b/examples/showcase/configuration/docs/named-sources/kotlin-greeting.md index f3b6160..9cb7f17 100644 --- a/examples/showcase/configuration/docs/named-sources/kotlin-greeting.md +++ b/examples/showcase/configuration/docs/named-sources/kotlin-greeting.md @@ -3,6 +3,12 @@ The same config can embed from a different root by changing the source-root prefix in the instruction. +## How It Works + +[../../named-sources.yml](../../named-sources.yml) also defines a source root +named `kotlin`. Changing the prefix to `$kotlin` resolves the same relative +package path under the Kotlin source tree. + ```kotlin @JvmStatic diff --git a/examples/showcase/configuration/docs/named-sources/text-line.md b/examples/showcase/configuration/docs/named-sources/text-line.md index f2e02fb..38d679f 100644 --- a/examples/showcase/configuration/docs/named-sources/text-line.md +++ b/examples/showcase/configuration/docs/named-sources/text-line.md @@ -3,6 +3,13 @@ Named roots do not need to be language-specific. This example embeds one line from the text source root. +## How It Works + +[../../named-sources.yml](../../named-sources.yml) defines a source root named +`text`. The `line` pattern matches one plain-text line from +`glob-patterns.txt`, showing that source roots can point at any supported text +fixture, not only programming language files. + ```text The total is $5 diff --git a/examples/showcase/configuration/docs/root-source/go-mod.md b/examples/showcase/configuration/docs/root-source/go-mod.md index cbc414e..4026295 100644 --- a/examples/showcase/configuration/docs/root-source/go-mod.md +++ b/examples/showcase/configuration/docs/root-source/go-mod.md @@ -1,49 +1,17 @@ # Repository Root Source This configuration example uses the repository root as a named source root. The -instruction embeds `go.mod` through the `$repo` prefix. +instruction embeds the module declaration from `go.mod` through the `$repo` +prefix. - -```go -// Copyright 2026, TeamDev. All rights reserved. -// -// Redistribution and use in source and/or binary forms, with or without -// modification, must retain the above copyright notice and the following -// disclaimer. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -module embed-code/embed-code-go +## How It Works -go 1.22.1 +[../../root-source.yml](../../root-source.yml) maps the repository root to the +name `repo`. The instruction uses `$repo/go.mod` and a `line` pattern so this +configuration test proves root-source lookup without embedding the whole +project metadata file. -require ( - github.com/bmatcuk/doublestar/v4 v4.6.1 - github.com/gobwas/glob v0.2.3 - github.com/onsi/ginkgo/v2 v2.20.2 - github.com/onsi/gomega v1.34.2 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect - github.com/stretchr/testify v1.9.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.24.0 // indirect -) + +```go +module embed-code/embed-code-go ``` diff --git a/examples/showcase/configuration/docs/root-source/version.md b/examples/showcase/configuration/docs/root-source/version.md index 4e822b8..08cfef4 100644 --- a/examples/showcase/configuration/docs/root-source/version.md +++ b/examples/showcase/configuration/docs/root-source/version.md @@ -2,6 +2,13 @@ The same root-source configuration can select a single line from a root file. +## How It Works + +The `$repo` prefix points at the repository root configured in +[../../root-source.yml](../../root-source.yml). The anchored pattern selects the +`Version` constant from `main.go`, which keeps the example small while proving +that root files can be used as sources. + ```go const Version = "1.2.2" diff --git a/examples/showcase/configuration/docs/single-source/greeting.md b/examples/showcase/configuration/docs/single-source/greeting.md index 91bb961..64cc31c 100644 --- a/examples/showcase/configuration/docs/single-source/greeting.md +++ b/examples/showcase/configuration/docs/single-source/greeting.md @@ -3,6 +3,12 @@ This config uses one unnamed `code-path`, so instructions refer to source files relative to that root without a `$name` prefix. +## How It Works + +[../../single-source.yml](../../single-source.yml) points `code-path` directly +at `examples/showcase/code/java`. The instruction therefore uses +`org/showcase/Greeting.java` instead of `$java/org/showcase/Greeting.java`. + ```java public static void main(String[] args) { diff --git a/examples/showcase/docs/01-whole-file-source.md b/examples/showcase/docs/01-whole-file-source.md index ed7016e..1f27a4e 100644 --- a/examples/showcase/docs/01-whole-file-source.md +++ b/examples/showcase/docs/01-whole-file-source.md @@ -1,6 +1,15 @@ # Whole File From A Showcase Source -Omitting `fragment`, `start`, `end`, and `line` embeds the whole source file. +This is the smallest useful instruction: it names a source file and leaves the +selection attributes empty. + +## How It Works + +The `$java` prefix selects the Java source root from +[../embed-code.yml](../embed-code.yml). Because `fragment`, `start`, `end`, and +`line` are omitted, embed mode copies every line from +[../code/java/org/showcase/Greeting.java](../code/java/org/showcase/Greeting.java) +into the following code fence. ```java diff --git a/examples/showcase/docs/02-source-line-pattern.md b/examples/showcase/docs/02-source-line-pattern.md index 93c342a..d8559b4 100644 --- a/examples/showcase/docs/02-source-line-pattern.md +++ b/examples/showcase/docs/02-source-line-pattern.md @@ -1,8 +1,17 @@ # One Line From A Showcase Source -The `line` attribute embeds only the first source line matching the pattern. +Use `line` when the documentation needs one source line instead of a whole +fragment. - +## How It Works + +The pattern is matched against +[../code/java/org/showcase/Greeting.java](../code/java/org/showcase/Greeting.java). +The opening quote is escaped because instruction attributes are parsed as XML, +and the trailing `*` lets the pattern match the rest of the return expression. +Only the first matching source line is rendered into the fence. + + ```java return "Hello, " + name + "!"; ``` diff --git a/examples/showcase/docs/03-named-fragment.md b/examples/showcase/docs/03-named-fragment.md index 2e6e641..9d9d667 100644 --- a/examples/showcase/docs/03-named-fragment.md +++ b/examples/showcase/docs/03-named-fragment.md @@ -1,7 +1,14 @@ # Named Fragment -The `fragment` attribute embeds lines between matching `#docfragment` and -`#enddocfragment` markers. Marker lines are not rendered. +Use `fragment` when the source file already marks a reusable documentation +region. + +## How It Works + +[../code/java/org/showcase/Greeting.java](../code/java/org/showcase/Greeting.java) +wraps the `main()` method with matching `#docfragment` and `#enddocfragment` +comments. The instruction resolves the named region, removes the marker lines, +normalizes indentation, and replaces the following fence with the method body. ```java diff --git a/examples/showcase/docs/04-paired-instruction-tag.md b/examples/showcase/docs/04-paired-instruction-tag.md index bf4dcb6..5c20205 100644 --- a/examples/showcase/docs/04-paired-instruction-tag.md +++ b/examples/showcase/docs/04-paired-instruction-tag.md @@ -4,10 +4,12 @@ Instructions may be self-closing or paired. The self-closing form is supported, but this showcase uses paired tags because some Markdown renderers display the XML-style self-closing tag awkwardly. -## Short Paired Tag +## How It Works -This compact paired form is useful when all attributes fit naturally in one -line. +The active instruction below has an opening `` tag and a matching +closing tag. No content is required between them; the rendered snippet still +comes from the source file and the following code fence. The whole showcase uses +this paired form so Markdown previews display the instructions consistently. ```java @@ -15,22 +17,3 @@ public static void main(String[] args) { System.out.println(greeting("Ada")); } ``` - -## Paired Tag With A Larger Fragment - -The same paired form works for larger snippets too. - - -```java -public final class Greeting { - private Greeting() {} - - public static void main(String[] args) { - System.out.println(greeting("Ada")); - } - - public static String greeting(String name) { - return "Hello, " + name + "!"; - } -} -``` diff --git a/examples/showcase/docs/05-named-source-root.md b/examples/showcase/docs/05-named-source-root.md index f138e1c..f703b72 100644 --- a/examples/showcase/docs/05-named-source-root.md +++ b/examples/showcase/docs/05-named-source-root.md @@ -1,8 +1,13 @@ # Named Source Roots -When a configuration has named code paths, the instruction chooses one with the -`$name/relative/path` prefix. This example reads Kotlin code from the `kotlin` -source root while the Java examples read from `java`. +Named roots let one documentation set embed source from several directories. + +## How It Works + +[../embed-code.yml](../embed-code.yml) defines `java`, `kotlin`, and `text` +source roots. The `$kotlin` prefix chooses the Kotlin root before resolving +`org/showcase/KotlinGreeting.kt`. The same docs root can therefore mix Java, +Kotlin, and text snippets without changing the command line. ```kotlin diff --git a/examples/showcase/docs/06-start-end-pattern.md b/examples/showcase/docs/06-start-end-pattern.md index 416d9c2..6e5db41 100644 --- a/examples/showcase/docs/06-start-end-pattern.md +++ b/examples/showcase/docs/06-start-end-pattern.md @@ -1,7 +1,14 @@ # Start And End Patterns -The `start` and `end` attributes select an inclusive source range. Patterns are -glob-like and the end search starts after the matched start. +Use `start` and `end` when the source does not contain named fragment markers. + +## How It Works + +The `start` pattern finds the first line in +[../code/java/org/showcase/PatternSamples.java](../code/java/org/showcase/PatternSamples.java) +that contains `@Scenario`. The `end` pattern then searches after that start +match and stops at the first line that is exactly four spaces followed by `}`. +Both boundary lines are included in the rendered snippet. ```text diff --git a/examples/showcase/docs/09-escaped-newline-text.md b/examples/showcase/docs/09-escaped-newline-text.md index 61d7dda..ca4214c 100644 --- a/examples/showcase/docs/09-escaped-newline-text.md +++ b/examples/showcase/docs/09-escaped-newline-text.md @@ -1,7 +1,13 @@ # Escaped Newline Text -Use `\\n` when the source line contains the characters backslash and `n`. -Quote characters may be escaped as `\"` inside instruction attributes. +Pattern escaping distinguishes a real multi-line pattern from source text that +contains the characters backslash and `n`. + +## How It Works + +The pattern uses `\\n` because the source line contains a string literal with +backslash-n text. The quote characters are written as `\"` so the instruction +remains valid XML. The result is one source line, not a two-line match. ```java diff --git a/examples/showcase/docs/11-multi-part-fragment-separator.md b/examples/showcase/docs/11-multi-part-fragment-separator.md index 9ff4c42..4ccb586 100644 --- a/examples/showcase/docs/11-multi-part-fragment-separator.md +++ b/examples/showcase/docs/11-multi-part-fragment-separator.md @@ -1,7 +1,14 @@ # Multi-Part Fragment Separator -Fragments with the same name may appear in several source regions. The rendered -parts are joined in source order with the configured separator. +One named fragment can be split across several source regions. + +## How It Works + +[../code/java/org/showcase/MultiPartWorkflow.java](../code/java/org/showcase/MultiPartWorkflow.java) +opens and closes the `Workflow` fragment multiple times. Embed mode collects +each part in source order and joins the parts with the `separator` configured in +[../embed-code.yml](../embed-code.yml). This is useful when a guide needs the +shape of a class but wants to hide internal lines between selected regions. ```java diff --git a/examples/showcase/docs/12-overlapping-fragments.md b/examples/showcase/docs/12-overlapping-fragments.md index 51362fe..c6c2a6c 100644 --- a/examples/showcase/docs/12-overlapping-fragments.md +++ b/examples/showcase/docs/12-overlapping-fragments.md @@ -1,7 +1,14 @@ # Overlapping Fragments -Several fragments can open or close on the same marker line. This example uses -an overlapping fragment that shares the class wrapper with another fragment. +Several fragments can open or close on the same marker line. + +## How It Works + +[../code/java/org/showcase/OverlappingFragments.java](../code/java/org/showcase/OverlappingFragments.java) +uses marker lines that name both `Class wrapper` and `Greeting method`. The +instruction asks only for `Greeting method`, so embed mode keeps the shared +class wrapper, skips unrelated method details, and renders the selected method +inside the wrapper. ```java diff --git a/examples/showcase/docs/13-markdown-fence-shielding.md b/examples/showcase/docs/13-markdown-fence-shielding.md index 4f6d2c4..a3afb2b 100644 --- a/examples/showcase/docs/13-markdown-fence-shielding.md +++ b/examples/showcase/docs/13-markdown-fence-shielding.md @@ -1,8 +1,13 @@ # Instructions Inside Markdown Fences -Instruction-looking text inside an ordinary Markdown code fence is preserved as -documentation content. It is not executed because the parser tracks Markdown -fence state before looking for instructions. +Documentation sometimes needs to show an instruction as plain text. + +## How It Works + +The instruction-looking text below is inside an ordinary Markdown fence, so it +is preserved as documentation content. The parser tracks code-fence state before +looking for active instructions, which prevents examples from accidentally +running while they are being explained. ````markdown diff --git a/examples/showcase/docs/html-showcase.html b/examples/showcase/docs/html-showcase.html index b3a62d0..a1080cb 100644 --- a/examples/showcase/docs/html-showcase.html +++ b/examples/showcase/docs/html-showcase.html @@ -3,8 +3,13 @@

HTML Embedding Showcase

- HTML files are scanned too when the include patterns allow them. The - instruction still needs a Markdown code fence after it. + HTML files are scanned when the include patterns allow them. The instruction + below uses the same paired tag form as the Markdown examples and still needs + a Markdown code fence immediately after it. +

+

+ The source root comes from embed-code.yml, and + the rendered snippet is checked the same way as a Markdown document.

diff --git a/examples/showcase/docs/ignored-by-exclude.md b/examples/showcase/docs/ignored-by-exclude.md index f1c4950..8797906 100644 --- a/examples/showcase/docs/ignored-by-exclude.md +++ b/examples/showcase/docs/ignored-by-exclude.md @@ -1,8 +1,14 @@ # Excluded Showcase File -The positive configuration excludes this file. Its intentionally broken -instruction verifies that `doc-excludes` prevents selected files from being -processed. +This file is intentionally present in the positive docs root but absent from +the positive processing flow. + +## How It Works + +[../embed-code.yml](../embed-code.yml) lists this file in `doc-excludes`, so +embed mode and check mode skip it even though it matches the include patterns. +The missing source path proves the exclude is active: processing this file would +fail immediately. ```go diff --git a/examples/showcase/negative/docs/invalid-attributes.md b/examples/showcase/negative/docs/invalid-attributes.md index d8feab3..eb7b17d 100644 --- a/examples/showcase/negative/docs/invalid-attributes.md +++ b/examples/showcase/negative/docs/invalid-attributes.md @@ -1,7 +1,13 @@ # Invalid Attributes -This scenario fails because `fragment` cannot be combined with `line`, `start`, -or `end`. +This scenario shows a structurally invalid instruction. + +## How It Fails + +`fragment` selects a named source region, while `line`, `start`, and `end` +select by pattern. The instruction combines `fragment` and `line`, so the tool +rejects it before reading the source file. In a real guide, choose one selection +style for each instruction. ```java diff --git a/examples/showcase/negative/docs/missing-code-fence.md b/examples/showcase/negative/docs/missing-code-fence.md index 32c060a..1e51cde 100644 --- a/examples/showcase/negative/docs/missing-code-fence.md +++ b/examples/showcase/negative/docs/missing-code-fence.md @@ -1,7 +1,14 @@ # Missing Code Fence -This scenario fails because every active instruction must be followed by a -Markdown code fence. +This scenario shows what happens when an active instruction has no owned code +fence. + +## How It Fails + +Every instruction must be followed immediately by a Markdown code fence. The +plain text line below is not a fence, so the parser reports the missing fence at +the instruction. In a real guide, add an opening and closing fence after the +instruction, even if the fence starts empty. This line is not a code fence. diff --git a/examples/showcase/negative/docs/missing-fragment.md b/examples/showcase/negative/docs/missing-fragment.md index 7a89643..31bbd6a 100644 --- a/examples/showcase/negative/docs/missing-fragment.md +++ b/examples/showcase/negative/docs/missing-fragment.md @@ -1,8 +1,15 @@ # Missing Fragment -This scenario fails because the source file exists, but the requested named +This scenario shows what happens when the source file exists but the named fragment does not. +## How It Fails + +[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) +contains fragments such as `main()`, but it does not contain `does not exist`. +Check mode reports the missing fragment. In a real guide, use an existing +fragment name or add matching source markers. + ```java ``` diff --git a/examples/showcase/negative/docs/missing-pattern.md b/examples/showcase/negative/docs/missing-pattern.md index 32360d6..d63b94c 100644 --- a/examples/showcase/negative/docs/missing-pattern.md +++ b/examples/showcase/negative/docs/missing-pattern.md @@ -1,7 +1,14 @@ # Missing Pattern -This scenario fails because the source file exists, but no line matches the -requested line pattern. +This scenario shows what happens when a line pattern matches nothing. + +## How It Fails + +The source file is found, but no line in +[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) +matches `doesNotExistPattern`. Check mode reports the unmatched pattern. In a +real guide, loosen the glob pattern, add anchors only where needed, or point the +instruction at the intended source file. ```java diff --git a/examples/showcase/negative/docs/missing-source.md b/examples/showcase/negative/docs/missing-source.md index 5aeabd7..26fc071 100644 --- a/examples/showcase/negative/docs/missing-source.md +++ b/examples/showcase/negative/docs/missing-source.md @@ -1,7 +1,13 @@ # Missing Source -This scenario fails because the instruction points to a file that cannot be -resolved from the configured source roots. +This scenario shows what happens when the `file` attribute cannot be resolved. + +## How It Fails + +The `$java` root exists, but `org/showcase/DoesNotExist.java` is not present +under [../../code/java](../../code/java/). Check mode reports the missing source +file and leaves the document unchanged. In a real guide, fix the path or add the +missing source file. ```java diff --git a/examples/showcase/negative/docs/stale-snippet.md b/examples/showcase/negative/docs/stale-snippet.md index b74d5c2..5b6b424 100644 --- a/examples/showcase/negative/docs/stale-snippet.md +++ b/examples/showcase/negative/docs/stale-snippet.md @@ -1,7 +1,14 @@ # Stale Snippet -This scenario is syntactically valid, but check mode reports it as stale because -the rendered code fence does not match the current source fragment. +This scenario is syntactically valid, but the rendered code is out of date. + +## How It Fails + +Check mode resolves the `main()` fragment from +[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) +and compares it with the existing fence. The fence contains different text, so +check mode reports the document as stale without rewriting it. Embed mode would +replace the fence with the current source fragment. ```java diff --git a/examples/showcase/negative/docs/unclosed-code-fence.md b/examples/showcase/negative/docs/unclosed-code-fence.md index a89f5cd..35315de 100644 --- a/examples/showcase/negative/docs/unclosed-code-fence.md +++ b/examples/showcase/negative/docs/unclosed-code-fence.md @@ -1,7 +1,13 @@ # Unclosed Code Fence -This scenario fails because the opening fence after the instruction is never -closed. +This scenario shows what happens when the instruction has an opening fence but +no closing fence. + +## How It Fails + +The parser finds the opening fence after the instruction and then reaches the +end of the file before seeing a matching closing fence. In a real guide, close +the fence with the same marker style and at least the same marker length. ```java From f7150420859315cc6458bb6bf9fa2c55f15709cf Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 17:25:13 +0200 Subject: [PATCH 03/14] Rearrange files. --- examples/showcase/README.md | 119 +----------------- .../showcase/{ => embedding}/embed-code.yml | 0 .../negative/docs/invalid-attributes.md | 0 .../negative/docs/missing-code-fence.md | 0 .../negative/docs/missing-fragment.md | 0 .../negative/docs/missing-pattern.md | 0 .../negative/docs/missing-source.md | 0 .../negative/docs/stale-snippet.md | 0 .../negative/docs/unclosed-code-fence.md | 0 .../negative/processing-errors.yml | 0 .../{ => embedding}/negative/stale.yml | 0 .../positive}/01-whole-file-source.md | 0 .../positive}/02-source-line-pattern.md | 0 .../positive}/03-named-fragment.md | 0 .../positive}/04-paired-instruction-tag.md | 0 .../positive}/05-named-source-root.md | 0 .../positive}/06-start-end-pattern.md | 0 .../positive}/07-multi-line-pattern.md | 0 .../positive}/08-escaped-glob-character.md | 0 .../positive}/09-escaped-newline-text.md | 0 .../positive}/10-comment-filtering.md | 0 .../11-multi-part-fragment-separator.md | 0 .../positive}/12-overlapping-fragments.md | 0 .../positive}/13-markdown-fence-shielding.md | 0 .../positive}/html-showcase.html | 0 .../positive}/ignored-by-exclude.md | 0 26 files changed, 6 insertions(+), 113 deletions(-) rename examples/showcase/{ => embedding}/embed-code.yml (100%) rename examples/showcase/{ => embedding}/negative/docs/invalid-attributes.md (100%) rename examples/showcase/{ => embedding}/negative/docs/missing-code-fence.md (100%) rename examples/showcase/{ => embedding}/negative/docs/missing-fragment.md (100%) rename examples/showcase/{ => embedding}/negative/docs/missing-pattern.md (100%) rename examples/showcase/{ => embedding}/negative/docs/missing-source.md (100%) rename examples/showcase/{ => embedding}/negative/docs/stale-snippet.md (100%) rename examples/showcase/{ => embedding}/negative/docs/unclosed-code-fence.md (100%) rename examples/showcase/{ => embedding}/negative/processing-errors.yml (100%) rename examples/showcase/{ => embedding}/negative/stale.yml (100%) rename examples/showcase/{docs => embedding/positive}/01-whole-file-source.md (100%) rename examples/showcase/{docs => embedding/positive}/02-source-line-pattern.md (100%) rename examples/showcase/{docs => embedding/positive}/03-named-fragment.md (100%) rename examples/showcase/{docs => embedding/positive}/04-paired-instruction-tag.md (100%) rename examples/showcase/{docs => embedding/positive}/05-named-source-root.md (100%) rename examples/showcase/{docs => embedding/positive}/06-start-end-pattern.md (100%) rename examples/showcase/{docs => embedding/positive}/07-multi-line-pattern.md (100%) rename examples/showcase/{docs => embedding/positive}/08-escaped-glob-character.md (100%) rename examples/showcase/{docs => embedding/positive}/09-escaped-newline-text.md (100%) rename examples/showcase/{docs => embedding/positive}/10-comment-filtering.md (100%) rename examples/showcase/{docs => embedding/positive}/11-multi-part-fragment-separator.md (100%) rename examples/showcase/{docs => embedding/positive}/12-overlapping-fragments.md (100%) rename examples/showcase/{docs => embedding/positive}/13-markdown-fence-shielding.md (100%) rename examples/showcase/{docs => embedding/positive}/html-showcase.html (100%) rename examples/showcase/{docs => embedding/positive}/ignored-by-exclude.md (100%) diff --git a/examples/showcase/README.md b/examples/showcase/README.md index 7a1f5af..b8574df 100644 --- a/examples/showcase/README.md +++ b/examples/showcase/README.md @@ -1,120 +1,13 @@ # Embed Code Showcase -This folder is an opt-in, executable guide to `embed-code-go`. It is not part of -the normal `go test ./...` flow. The Go test is guarded by the `showcase` build -tag, so run it only when you want to verify the examples end to end. - -Run commands from the repository root. - -The showcase-owned source examples live under [code](code/). Repository-root -source examples are kept in the configuration showcase only. +This is an executable showcase guide to `embed-code-go` and the end-to-end tests. ## How To Use This Guide -Read the files in [docs](docs/) from `01` onward when learning the embedding -syntax for the first time. Each file owns one positive case, explains how the -instruction is resolved, and shows the rendered code fence that embed mode owns. - -Then inspect [negative/docs](negative/docs/) to see the failures that check mode -reports without rewriting files. Finally, read [configuration](configuration/) -to compare the YAML shapes that point documentation roots at source roots. - -## Positive Flow - -Refresh the generated snippets: - -```bash -go run ./main.go -mode embed -config-path examples/showcase/embed-code.yml -``` - -Verify that the snippets are up-to-date: - -```bash -go run ./main.go -mode check -config-path examples/showcase/embed-code.yml -``` - -Run the opt-in test: - -```bash -go test -tags showcase ./examples/showcase -``` - -The test copies the showcase docs to a temporary directory, runs check mode, -intentionally makes one copied snippet stale, repairs it with embed mode, and -then verifies the negative and configuration cases. The build tag keeps this -larger documentation test out of the default test flow. - -The positive showcase covers: - -| Case | File | What it verifies | -|--------------------------|------------------------------------------------------------------------------|-----------------------------------------------------------------------| -| Whole file | [docs/01-whole-file-source.md](docs/01-whole-file-source.md) | Omitting selection attributes embeds the whole showcase source file. | -| Source line pattern | [docs/02-source-line-pattern.md](docs/02-source-line-pattern.md) | A showcase source file can be matched with a `line` pattern. | -| Named fragment | [docs/03-named-fragment.md](docs/03-named-fragment.md) | `fragment` uses `#docfragment` markers and omits marker lines. | -| Paired instruction tags | [docs/04-paired-instruction-tag.md](docs/04-paired-instruction-tag.md) | Paired tags are preferred; self-closing tags are still supported. | -| Named source roots | [docs/05-named-source-root.md](docs/05-named-source-root.md) | `$java/...` and `$kotlin/...` select different configured roots. | -| Start and end patterns | [docs/06-start-end-pattern.md](docs/06-start-end-pattern.md) | `start` and `end` select an inclusive source range. | -| Multi-line patterns | [docs/07-multi-line-pattern.md](docs/07-multi-line-pattern.md) | `\n` matches consecutive source lines. | -| Escaped glob characters | [docs/08-escaped-glob-character.md](docs/08-escaped-glob-character.md) | `\*` matches a literal asterisk. | -| Escaped newline text | [docs/09-escaped-newline-text.md](docs/09-escaped-newline-text.md) | `\\n` matches a literal backslash-n sequence. | -| Comment filtering | [docs/10-comment-filtering.md](docs/10-comment-filtering.md) | `comments="documentation"` keeps Java documentation comments. | -| Multi-part fragments | [docs/11-multi-part-fragment-separator.md](docs/11-multi-part-fragment-separator.md) | Repeated fragment markers are joined with the configured separator. | -| Overlapping fragments | [docs/12-overlapping-fragments.md](docs/12-overlapping-fragments.md) | Multiple fragment names may share marker lines. | -| Markdown fence shielding | [docs/13-markdown-fence-shielding.md](docs/13-markdown-fence-shielding.md) | Instruction-looking text inside a regular fence is ignored. | -| HTML documents | [docs/html-showcase.html](docs/html-showcase.html) | HTML files can be scanned when included by configuration. | -| Excludes | [docs/ignored-by-exclude.md](docs/ignored-by-exclude.md) | Excluded files are not processed even when they contain instructions. | - -## Negative Flow - -The negative examples are intentionally broken. They are still documentation: -each file describes the mistake, the expected failure reason, and what a user -should fix in a real document. These commands should fail. - -```bash -go run ./main.go -mode check -config-path examples/showcase/negative/processing-errors.yml -go run ./main.go -mode check -config-path examples/showcase/negative/stale.yml -``` - -The processing-error config verifies: - -| Case | File | Expected behavior | -|---------------------|------------------------------------------------------------------------------|------------------------------------------------------| -| Missing source | [negative/docs/missing-source.md](negative/docs/missing-source.md) | Reports that the code file cannot be found. | -| Missing fragment | [negative/docs/missing-fragment.md](negative/docs/missing-fragment.md) | Reports that the requested fragment cannot be found. | -| Missing pattern | [negative/docs/missing-pattern.md](negative/docs/missing-pattern.md) | Reports that no source line matches the pattern. | -| Invalid attributes | [negative/docs/invalid-attributes.md](negative/docs/invalid-attributes.md) | Rejects mutually exclusive selection attributes. | -| Missing code fence | [negative/docs/missing-code-fence.md](negative/docs/missing-code-fence.md) | Requires a fence immediately after an instruction. | -| Unclosed code fence | [negative/docs/unclosed-code-fence.md](negative/docs/unclosed-code-fence.md) | Reports an instruction fence that reaches EOF. | - -The stale config verifies: - -| Case | File | Expected behavior | -|---------------|--------------------------------------------------------------------|------------------------------------------------------------------------| -| Stale snippet | [negative/docs/stale-snippet.md](negative/docs/stale-snippet.md) | Check mode reports the file as needing an update without rewriting it. | - -## Configuration Flow - -Configuration examples live under [configuration](configuration/). They cover a -repository-root source, a single source root, named source roots, -include/exclude patterns, and the `embeddings` list for multiple documentation -roots. - -Each configuration has its own docs root so the examples can be run separately. -Open the YAML file first, then follow the linked docs root to see how the -instructions use that configuration. +Guide is divided on two categories: -| Case | Config | Docs root | -|----------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------| -| Repository root | [configuration/root-source.yml](configuration/root-source.yml) | [configuration/docs/root-source](configuration/docs/root-source/) | -| Single source root | [configuration/single-source.yml](configuration/single-source.yml) | [configuration/docs/single-source](configuration/docs/single-source/) | -| Named source roots | [configuration/named-sources.yml](configuration/named-sources.yml) | [configuration/docs/named-sources](configuration/docs/named-sources/) | -| Include and exclude | [configuration/include-exclude.yml](configuration/include-exclude.yml) | [configuration/docs/include-exclude](configuration/docs/include-exclude/) | -| Multiple embeddings | [configuration/multiple-embeddings.yml](configuration/multiple-embeddings.yml) | [configuration/docs/multiple](configuration/docs/multiple/) | +1. [Configuration](configuration/README.md) - describes how to configure the whole embed-code application. +2. [Embedding](embedding/) - describes how to work with the embedding instructions. -```bash -go run ./main.go -mode check -config-path examples/showcase/configuration/root-source.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/single-source.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/named-sources.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/include-exclude.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/multiple-embeddings.yml -``` +## How To Run Tests +... diff --git a/examples/showcase/embed-code.yml b/examples/showcase/embedding/embed-code.yml similarity index 100% rename from examples/showcase/embed-code.yml rename to examples/showcase/embedding/embed-code.yml diff --git a/examples/showcase/negative/docs/invalid-attributes.md b/examples/showcase/embedding/negative/docs/invalid-attributes.md similarity index 100% rename from examples/showcase/negative/docs/invalid-attributes.md rename to examples/showcase/embedding/negative/docs/invalid-attributes.md diff --git a/examples/showcase/negative/docs/missing-code-fence.md b/examples/showcase/embedding/negative/docs/missing-code-fence.md similarity index 100% rename from examples/showcase/negative/docs/missing-code-fence.md rename to examples/showcase/embedding/negative/docs/missing-code-fence.md diff --git a/examples/showcase/negative/docs/missing-fragment.md b/examples/showcase/embedding/negative/docs/missing-fragment.md similarity index 100% rename from examples/showcase/negative/docs/missing-fragment.md rename to examples/showcase/embedding/negative/docs/missing-fragment.md diff --git a/examples/showcase/negative/docs/missing-pattern.md b/examples/showcase/embedding/negative/docs/missing-pattern.md similarity index 100% rename from examples/showcase/negative/docs/missing-pattern.md rename to examples/showcase/embedding/negative/docs/missing-pattern.md diff --git a/examples/showcase/negative/docs/missing-source.md b/examples/showcase/embedding/negative/docs/missing-source.md similarity index 100% rename from examples/showcase/negative/docs/missing-source.md rename to examples/showcase/embedding/negative/docs/missing-source.md diff --git a/examples/showcase/negative/docs/stale-snippet.md b/examples/showcase/embedding/negative/docs/stale-snippet.md similarity index 100% rename from examples/showcase/negative/docs/stale-snippet.md rename to examples/showcase/embedding/negative/docs/stale-snippet.md diff --git a/examples/showcase/negative/docs/unclosed-code-fence.md b/examples/showcase/embedding/negative/docs/unclosed-code-fence.md similarity index 100% rename from examples/showcase/negative/docs/unclosed-code-fence.md rename to examples/showcase/embedding/negative/docs/unclosed-code-fence.md diff --git a/examples/showcase/negative/processing-errors.yml b/examples/showcase/embedding/negative/processing-errors.yml similarity index 100% rename from examples/showcase/negative/processing-errors.yml rename to examples/showcase/embedding/negative/processing-errors.yml diff --git a/examples/showcase/negative/stale.yml b/examples/showcase/embedding/negative/stale.yml similarity index 100% rename from examples/showcase/negative/stale.yml rename to examples/showcase/embedding/negative/stale.yml diff --git a/examples/showcase/docs/01-whole-file-source.md b/examples/showcase/embedding/positive/01-whole-file-source.md similarity index 100% rename from examples/showcase/docs/01-whole-file-source.md rename to examples/showcase/embedding/positive/01-whole-file-source.md diff --git a/examples/showcase/docs/02-source-line-pattern.md b/examples/showcase/embedding/positive/02-source-line-pattern.md similarity index 100% rename from examples/showcase/docs/02-source-line-pattern.md rename to examples/showcase/embedding/positive/02-source-line-pattern.md diff --git a/examples/showcase/docs/03-named-fragment.md b/examples/showcase/embedding/positive/03-named-fragment.md similarity index 100% rename from examples/showcase/docs/03-named-fragment.md rename to examples/showcase/embedding/positive/03-named-fragment.md diff --git a/examples/showcase/docs/04-paired-instruction-tag.md b/examples/showcase/embedding/positive/04-paired-instruction-tag.md similarity index 100% rename from examples/showcase/docs/04-paired-instruction-tag.md rename to examples/showcase/embedding/positive/04-paired-instruction-tag.md diff --git a/examples/showcase/docs/05-named-source-root.md b/examples/showcase/embedding/positive/05-named-source-root.md similarity index 100% rename from examples/showcase/docs/05-named-source-root.md rename to examples/showcase/embedding/positive/05-named-source-root.md diff --git a/examples/showcase/docs/06-start-end-pattern.md b/examples/showcase/embedding/positive/06-start-end-pattern.md similarity index 100% rename from examples/showcase/docs/06-start-end-pattern.md rename to examples/showcase/embedding/positive/06-start-end-pattern.md diff --git a/examples/showcase/docs/07-multi-line-pattern.md b/examples/showcase/embedding/positive/07-multi-line-pattern.md similarity index 100% rename from examples/showcase/docs/07-multi-line-pattern.md rename to examples/showcase/embedding/positive/07-multi-line-pattern.md diff --git a/examples/showcase/docs/08-escaped-glob-character.md b/examples/showcase/embedding/positive/08-escaped-glob-character.md similarity index 100% rename from examples/showcase/docs/08-escaped-glob-character.md rename to examples/showcase/embedding/positive/08-escaped-glob-character.md diff --git a/examples/showcase/docs/09-escaped-newline-text.md b/examples/showcase/embedding/positive/09-escaped-newline-text.md similarity index 100% rename from examples/showcase/docs/09-escaped-newline-text.md rename to examples/showcase/embedding/positive/09-escaped-newline-text.md diff --git a/examples/showcase/docs/10-comment-filtering.md b/examples/showcase/embedding/positive/10-comment-filtering.md similarity index 100% rename from examples/showcase/docs/10-comment-filtering.md rename to examples/showcase/embedding/positive/10-comment-filtering.md diff --git a/examples/showcase/docs/11-multi-part-fragment-separator.md b/examples/showcase/embedding/positive/11-multi-part-fragment-separator.md similarity index 100% rename from examples/showcase/docs/11-multi-part-fragment-separator.md rename to examples/showcase/embedding/positive/11-multi-part-fragment-separator.md diff --git a/examples/showcase/docs/12-overlapping-fragments.md b/examples/showcase/embedding/positive/12-overlapping-fragments.md similarity index 100% rename from examples/showcase/docs/12-overlapping-fragments.md rename to examples/showcase/embedding/positive/12-overlapping-fragments.md diff --git a/examples/showcase/docs/13-markdown-fence-shielding.md b/examples/showcase/embedding/positive/13-markdown-fence-shielding.md similarity index 100% rename from examples/showcase/docs/13-markdown-fence-shielding.md rename to examples/showcase/embedding/positive/13-markdown-fence-shielding.md diff --git a/examples/showcase/docs/html-showcase.html b/examples/showcase/embedding/positive/html-showcase.html similarity index 100% rename from examples/showcase/docs/html-showcase.html rename to examples/showcase/embedding/positive/html-showcase.html diff --git a/examples/showcase/docs/ignored-by-exclude.md b/examples/showcase/embedding/positive/ignored-by-exclude.md similarity index 100% rename from examples/showcase/docs/ignored-by-exclude.md rename to examples/showcase/embedding/positive/ignored-by-exclude.md From ae386027559df997d2d97e18df3324789cdb25d2 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 17:40:42 +0200 Subject: [PATCH 04/14] Fix paths. --- examples/showcase/README.md | 34 +++++++++++++++++-- examples/showcase/embedding/embed-code.yml | 2 +- .../negative/docs/missing-fragment.md | 2 +- .../negative/docs/missing-pattern.md | 2 +- .../embedding/negative/docs/missing-source.md | 2 +- .../embedding/negative/docs/stale-snippet.md | 2 +- .../embedding/negative/processing-errors.yml | 2 +- .../showcase/embedding/negative/stale.yml | 2 +- .../positive/01-whole-file-source.md | 2 +- .../positive/02-source-line-pattern.md | 2 +- .../embedding/positive/03-named-fragment.md | 2 +- .../positive/06-start-end-pattern.md | 2 +- .../positive/08-escaped-glob-character.md | 2 +- .../11-multi-part-fragment-separator.md | 2 +- .../positive/12-overlapping-fragments.md | 2 +- examples/showcase/showcase_test.go | 4 +-- 16 files changed, 48 insertions(+), 18 deletions(-) diff --git a/examples/showcase/README.md b/examples/showcase/README.md index b8574df..847e403 100644 --- a/examples/showcase/README.md +++ b/examples/showcase/README.md @@ -7,7 +7,37 @@ This is an executable showcase guide to `embed-code-go` and the end-to-end tests Guide is divided on two categories: 1. [Configuration](configuration/README.md) - describes how to configure the whole embed-code application. -2. [Embedding](embedding/) - describes how to work with the embedding instructions. +2. [Embedding](embedding/positive/) - describes how to work with the embedding instructions. ## How To Run Tests -... + +Run commands from the repository root. + +Run the opt-in end-to-end test with the `showcase` build tag: + +```bash +go test -tags showcase ./examples/showcase +``` + +Verify the positive embedding examples: + +```bash +go run ./main.go -mode check -config-path examples/showcase/embedding/embed-code.yml +``` + +Verify the configuration examples: + +```bash +go run ./main.go -mode check -config-path examples/showcase/configuration/root-source.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/single-source.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/named-sources.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/include-exclude.yml +go run ./main.go -mode check -config-path examples/showcase/configuration/multiple-embeddings.yml +``` + +The negative examples are intentionally broken, so these commands should fail: + +```bash +go run ./main.go -mode check -config-path examples/showcase/embedding/negative/processing-errors.yml +go run ./main.go -mode check -config-path examples/showcase/embedding/negative/stale.yml +``` diff --git a/examples/showcase/embedding/embed-code.yml b/examples/showcase/embedding/embed-code.yml index b2a3bb0..981ced6 100644 --- a/examples/showcase/embedding/embed-code.yml +++ b/examples/showcase/embedding/embed-code.yml @@ -5,7 +5,7 @@ code-path: path: examples/showcase/code/kotlin - name: text path: examples/showcase/code/text -docs-path: examples/showcase/docs +docs-path: examples/showcase/embedding/positive doc-includes: - "**/*.md" - "**/*.html" diff --git a/examples/showcase/embedding/negative/docs/missing-fragment.md b/examples/showcase/embedding/negative/docs/missing-fragment.md index 31bbd6a..63ea22a 100644 --- a/examples/showcase/embedding/negative/docs/missing-fragment.md +++ b/examples/showcase/embedding/negative/docs/missing-fragment.md @@ -5,7 +5,7 @@ fragment does not. ## How It Fails -[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) +[../../../code/java/org/showcase/Greeting.java](../../../code/java/org/showcase/Greeting.java) contains fragments such as `main()`, but it does not contain `does not exist`. Check mode reports the missing fragment. In a real guide, use an existing fragment name or add matching source markers. diff --git a/examples/showcase/embedding/negative/docs/missing-pattern.md b/examples/showcase/embedding/negative/docs/missing-pattern.md index d63b94c..a107ef8 100644 --- a/examples/showcase/embedding/negative/docs/missing-pattern.md +++ b/examples/showcase/embedding/negative/docs/missing-pattern.md @@ -5,7 +5,7 @@ This scenario shows what happens when a line pattern matches nothing. ## How It Fails The source file is found, but no line in -[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) +[../../../code/java/org/showcase/Greeting.java](../../../code/java/org/showcase/Greeting.java) matches `doesNotExistPattern`. Check mode reports the unmatched pattern. In a real guide, loosen the glob pattern, add anchors only where needed, or point the instruction at the intended source file. diff --git a/examples/showcase/embedding/negative/docs/missing-source.md b/examples/showcase/embedding/negative/docs/missing-source.md index 26fc071..a2ac946 100644 --- a/examples/showcase/embedding/negative/docs/missing-source.md +++ b/examples/showcase/embedding/negative/docs/missing-source.md @@ -5,7 +5,7 @@ This scenario shows what happens when the `file` attribute cannot be resolved. ## How It Fails The `$java` root exists, but `org/showcase/DoesNotExist.java` is not present -under [../../code/java](../../code/java/). Check mode reports the missing source +under [../../../code/java](../../../code/java/). Check mode reports the missing source file and leaves the document unchanged. In a real guide, fix the path or add the missing source file. diff --git a/examples/showcase/embedding/negative/docs/stale-snippet.md b/examples/showcase/embedding/negative/docs/stale-snippet.md index 5b6b424..f4b28cc 100644 --- a/examples/showcase/embedding/negative/docs/stale-snippet.md +++ b/examples/showcase/embedding/negative/docs/stale-snippet.md @@ -5,7 +5,7 @@ This scenario is syntactically valid, but the rendered code is out of date. ## How It Fails Check mode resolves the `main()` fragment from -[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) +[../../../code/java/org/showcase/Greeting.java](../../../code/java/org/showcase/Greeting.java) and compares it with the existing fence. The fence contains different text, so check mode reports the document as stale without rewriting it. Embed mode would replace the fence with the current source fragment. diff --git a/examples/showcase/embedding/negative/processing-errors.yml b/examples/showcase/embedding/negative/processing-errors.yml index 90cc680..e04d3a5 100644 --- a/examples/showcase/embedding/negative/processing-errors.yml +++ b/examples/showcase/embedding/negative/processing-errors.yml @@ -3,7 +3,7 @@ code-path: path: . - name: java path: examples/showcase/code/java -docs-path: examples/showcase/negative/docs +docs-path: examples/showcase/embedding/negative/docs doc-includes: - missing-source.md - missing-fragment.md diff --git a/examples/showcase/embedding/negative/stale.yml b/examples/showcase/embedding/negative/stale.yml index 0cd2bd6..1347787 100644 --- a/examples/showcase/embedding/negative/stale.yml +++ b/examples/showcase/embedding/negative/stale.yml @@ -1,6 +1,6 @@ code-path: - name: java path: examples/showcase/code/java -docs-path: examples/showcase/negative/docs +docs-path: examples/showcase/embedding/negative/docs doc-includes: - stale-snippet.md diff --git a/examples/showcase/embedding/positive/01-whole-file-source.md b/examples/showcase/embedding/positive/01-whole-file-source.md index 1f27a4e..6a1f6ba 100644 --- a/examples/showcase/embedding/positive/01-whole-file-source.md +++ b/examples/showcase/embedding/positive/01-whole-file-source.md @@ -8,7 +8,7 @@ selection attributes empty. The `$java` prefix selects the Java source root from [../embed-code.yml](../embed-code.yml). Because `fragment`, `start`, `end`, and `line` are omitted, embed mode copies every line from -[../code/java/org/showcase/Greeting.java](../code/java/org/showcase/Greeting.java) +[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) into the following code fence. diff --git a/examples/showcase/embedding/positive/02-source-line-pattern.md b/examples/showcase/embedding/positive/02-source-line-pattern.md index d8559b4..53abf67 100644 --- a/examples/showcase/embedding/positive/02-source-line-pattern.md +++ b/examples/showcase/embedding/positive/02-source-line-pattern.md @@ -6,7 +6,7 @@ fragment. ## How It Works The pattern is matched against -[../code/java/org/showcase/Greeting.java](../code/java/org/showcase/Greeting.java). +[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java). The opening quote is escaped because instruction attributes are parsed as XML, and the trailing `*` lets the pattern match the rest of the return expression. Only the first matching source line is rendered into the fence. diff --git a/examples/showcase/embedding/positive/03-named-fragment.md b/examples/showcase/embedding/positive/03-named-fragment.md index 9d9d667..0dba0b9 100644 --- a/examples/showcase/embedding/positive/03-named-fragment.md +++ b/examples/showcase/embedding/positive/03-named-fragment.md @@ -5,7 +5,7 @@ region. ## How It Works -[../code/java/org/showcase/Greeting.java](../code/java/org/showcase/Greeting.java) +[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) wraps the `main()` method with matching `#docfragment` and `#enddocfragment` comments. The instruction resolves the named region, removes the marker lines, normalizes indentation, and replaces the following fence with the method body. diff --git a/examples/showcase/embedding/positive/06-start-end-pattern.md b/examples/showcase/embedding/positive/06-start-end-pattern.md index 6e5db41..ed316c7 100644 --- a/examples/showcase/embedding/positive/06-start-end-pattern.md +++ b/examples/showcase/embedding/positive/06-start-end-pattern.md @@ -5,7 +5,7 @@ Use `start` and `end` when the source does not contain named fragment markers. ## How It Works The `start` pattern finds the first line in -[../code/java/org/showcase/PatternSamples.java](../code/java/org/showcase/PatternSamples.java) +[../../code/java/org/showcase/PatternSamples.java](../../code/java/org/showcase/PatternSamples.java) that contains `@Scenario`. The `end` pattern then searches after that start match and stops at the first line that is exactly four spaces followed by `}`. Both boundary lines are included in the rendered snippet. diff --git a/examples/showcase/embedding/positive/08-escaped-glob-character.md b/examples/showcase/embedding/positive/08-escaped-glob-character.md index a0fce7e..f64aad5 100644 --- a/examples/showcase/embedding/positive/08-escaped-glob-character.md +++ b/examples/showcase/embedding/positive/08-escaped-glob-character.md @@ -6,7 +6,7 @@ one literally. ## How It Works The pattern `Use \* to multiply` treats `*` as source text instead of a wildcard. -It matches the line in [../code/text/glob-patterns.txt](../code/text/glob-patterns.txt) +It matches the line in [../../code/text/glob-patterns.txt](../../code/text/glob-patterns.txt) that contains a literal asterisk and embeds only that line. diff --git a/examples/showcase/embedding/positive/11-multi-part-fragment-separator.md b/examples/showcase/embedding/positive/11-multi-part-fragment-separator.md index 4ccb586..6894cad 100644 --- a/examples/showcase/embedding/positive/11-multi-part-fragment-separator.md +++ b/examples/showcase/embedding/positive/11-multi-part-fragment-separator.md @@ -4,7 +4,7 @@ One named fragment can be split across several source regions. ## How It Works -[../code/java/org/showcase/MultiPartWorkflow.java](../code/java/org/showcase/MultiPartWorkflow.java) +[../../code/java/org/showcase/MultiPartWorkflow.java](../../code/java/org/showcase/MultiPartWorkflow.java) opens and closes the `Workflow` fragment multiple times. Embed mode collects each part in source order and joins the parts with the `separator` configured in [../embed-code.yml](../embed-code.yml). This is useful when a guide needs the diff --git a/examples/showcase/embedding/positive/12-overlapping-fragments.md b/examples/showcase/embedding/positive/12-overlapping-fragments.md index c6c2a6c..a5e0ebe 100644 --- a/examples/showcase/embedding/positive/12-overlapping-fragments.md +++ b/examples/showcase/embedding/positive/12-overlapping-fragments.md @@ -4,7 +4,7 @@ Several fragments can open or close on the same marker line. ## How It Works -[../code/java/org/showcase/OverlappingFragments.java](../code/java/org/showcase/OverlappingFragments.java) +[../../code/java/org/showcase/OverlappingFragments.java](../../code/java/org/showcase/OverlappingFragments.java) uses marker lines that name both `Class wrapper` and `Greeting method`. The instruction asks only for `Greeting method`, so embed mode keeps the shared class wrapper, skips unrelated method details, and renders the selected method diff --git a/examples/showcase/showcase_test.go b/examples/showcase/showcase_test.go index 46a2c55..6f400f1 100644 --- a/examples/showcase/showcase_test.go +++ b/examples/showcase/showcase_test.go @@ -34,7 +34,7 @@ import ( // repaired with embed mode, and checked again without changing repository files. func TestShowcasePositiveFlow(t *testing.T) { repoRoot := findRepoRoot(t) - docsRoot := copyShowcaseDocs(t, repoRoot, "docs") + docsRoot := copyShowcaseDocs(t, repoRoot, filepath.Join("embedding", "positive")) configPath := writeShowcaseConfig(t, repoRoot, docsRoot) checkOutput, err := runEmbedCode(t, repoRoot, "check", configPath) @@ -153,7 +153,7 @@ func TestShowcaseNegativeScenarios(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - docsRoot := copyShowcaseDocs(t, repoRoot, filepath.Join("negative", "docs")) + docsRoot := copyShowcaseDocs(t, repoRoot, filepath.Join("embedding", "negative", "docs")) configPath := writeSingleDocConfig(t, docsRoot, tc.sources, tc.doc) output, err := runEmbedCode(t, repoRoot, "check", configPath) From c0db8e7c185f60b2e9dfecff4771c831736f865c Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 18:36:49 +0200 Subject: [PATCH 05/14] Add embedding README. --- examples/showcase/README.md | 2 +- examples/showcase/embedding/README.md | 65 +++++++++++++++++++ .../positive/08-escaped-glob-character.md | 15 ----- .../positive/09-escaped-newline-text.md | 17 ----- ...ment-filtering.md => comment-filtering.md} | 0 ...ielding.md => markdown-fence-shielding.md} | 0 ...-line-pattern.md => multi-line-pattern.md} | 0 ...or.md => multi-part-fragment-separator.md} | 0 ...03-named-fragment.md => named-fragment.md} | 0 ...ed-source-root.md => named-source-root.md} | 0 ...-fragments.md => overlapping-fragments.md} | 0 ...ction-tag.md => paired-instruction-tag.md} | 0 .../embedding/positive/pattern-escaping.md | 47 ++++++++++++++ ...line-pattern.md => source-line-pattern.md} | 0 ...rt-end-pattern.md => start-end-pattern.md} | 0 ...le-file-source.md => whole-file-source.md} | 0 examples/showcase/showcase_test.go | 4 +- 17 files changed, 115 insertions(+), 35 deletions(-) create mode 100644 examples/showcase/embedding/README.md delete mode 100644 examples/showcase/embedding/positive/08-escaped-glob-character.md delete mode 100644 examples/showcase/embedding/positive/09-escaped-newline-text.md rename examples/showcase/embedding/positive/{10-comment-filtering.md => comment-filtering.md} (100%) rename examples/showcase/embedding/positive/{13-markdown-fence-shielding.md => markdown-fence-shielding.md} (100%) rename examples/showcase/embedding/positive/{07-multi-line-pattern.md => multi-line-pattern.md} (100%) rename examples/showcase/embedding/positive/{11-multi-part-fragment-separator.md => multi-part-fragment-separator.md} (100%) rename examples/showcase/embedding/positive/{03-named-fragment.md => named-fragment.md} (100%) rename examples/showcase/embedding/positive/{05-named-source-root.md => named-source-root.md} (100%) rename examples/showcase/embedding/positive/{12-overlapping-fragments.md => overlapping-fragments.md} (100%) rename examples/showcase/embedding/positive/{04-paired-instruction-tag.md => paired-instruction-tag.md} (100%) create mode 100644 examples/showcase/embedding/positive/pattern-escaping.md rename examples/showcase/embedding/positive/{02-source-line-pattern.md => source-line-pattern.md} (100%) rename examples/showcase/embedding/positive/{06-start-end-pattern.md => start-end-pattern.md} (100%) rename examples/showcase/embedding/positive/{01-whole-file-source.md => whole-file-source.md} (100%) diff --git a/examples/showcase/README.md b/examples/showcase/README.md index 847e403..1ac8e03 100644 --- a/examples/showcase/README.md +++ b/examples/showcase/README.md @@ -7,7 +7,7 @@ This is an executable showcase guide to `embed-code-go` and the end-to-end tests Guide is divided on two categories: 1. [Configuration](configuration/README.md) - describes how to configure the whole embed-code application. -2. [Embedding](embedding/positive/) - describes how to work with the embedding instructions. +2. [Embedding](embedding/README.md) - describes how to work with the embedding instructions. ## How To Run Tests diff --git a/examples/showcase/embedding/README.md b/examples/showcase/embedding/README.md new file mode 100644 index 0000000..e65b3a1 --- /dev/null +++ b/examples/showcase/embedding/README.md @@ -0,0 +1,65 @@ +# Embedding Examples + +This folder is a runnable guide to embedding instructions. The positive +examples show supported features, and the negative examples show the failures a +user should expect when an instruction is malformed or stale. + +Run the positive examples from the repository root: + +```bash +go run ./main.go -mode check -config-path examples/showcase/embedding/embed-code.yml +``` + +## Feature Examples + +### Simple Embedding + +- [Whole file source](positive/whole-file-source.md) + shows how to embed the whole source file. +- [Paired instruction tag](positive/paired-instruction-tag.md) + shows the preferred paired tag form used throughout the showcase. +- [Named source root](positive/named-source-root.md) + shows how to use different configured source trees. + +### Line, Range, And Glob Matching + +- [Source line pattern](positive/source-line-pattern.md) + embeds the first source line that matches a `line` pattern. +- [Start and end patterns](positive/start-end-pattern.md) + embeds an inclusive source range selected by `start` and `end`. +- [Multi-line patterns](positive/multi-line-pattern.md) + uses `\n` to match consecutive source lines. +- [Pattern escaping](positive/pattern-escaping.md) + shows literal `*`, trailing `$`, leading `^`, and backslash-n text matches. + +### Fragments + +- [Named fragment](positive/named-fragment.md) + embeds a region wrapped with `#docfragment` and `#enddocfragment` markers. +- [Multi-part fragment separator](positive/multi-part-fragment-separator.md) + joins repeated fragment parts with the configured separator. +- [Overlapping fragments](positive/overlapping-fragments.md) + shows fragment markers that share source lines. + +### Rendered Content And Documents + +- [Comment filtering](positive/comment-filtering.md) + keeps documentation comments while removing regular comments. +- [Markdown fence shielding](positive/markdown-fence-shielding.md) + shows that instruction-looking text inside ordinary code fences is inert. +- [HTML showcase](positive/html-showcase.html) + shows that HTML documents can be processed when the include patterns allow them. +- [Excluded document](positive/ignored-by-exclude.md) + shows that `doc-excludes` prevents a matching file from being processed. + +## Negative Examples + +The negative examples are intentionally broken and should fail in check mode. +Use them to recognize common diagnostics: + +```bash +go run ./main.go -mode check -config-path examples/showcase/embedding/negative/processing-errors.yml +go run ./main.go -mode check -config-path examples/showcase/embedding/negative/stale.yml +``` + +The cases live in [negative/docs](negative/docs/). diff --git a/examples/showcase/embedding/positive/08-escaped-glob-character.md b/examples/showcase/embedding/positive/08-escaped-glob-character.md deleted file mode 100644 index f64aad5..0000000 --- a/examples/showcase/embedding/positive/08-escaped-glob-character.md +++ /dev/null @@ -1,15 +0,0 @@ -# Escaped Glob Characters - -Glob characters are useful in patterns, but sometimes the source text contains -one literally. - -## How It Works - -The pattern `Use \* to multiply` treats `*` as source text instead of a wildcard. -It matches the line in [../../code/text/glob-patterns.txt](../../code/text/glob-patterns.txt) -that contains a literal asterisk and embeds only that line. - - -```text -Use * to multiply -``` diff --git a/examples/showcase/embedding/positive/09-escaped-newline-text.md b/examples/showcase/embedding/positive/09-escaped-newline-text.md deleted file mode 100644 index ca4214c..0000000 --- a/examples/showcase/embedding/positive/09-escaped-newline-text.md +++ /dev/null @@ -1,17 +0,0 @@ -# Escaped Newline Text - -Pattern escaping distinguishes a real multi-line pattern from source text that -contains the characters backslash and `n`. - -## How It Works - -The pattern uses `\\n` because the source line contains a string literal with -backslash-n text. The quote characters are written as `\"` so the instruction -remains valid XML. The result is one source line, not a two-line match. - - -```java -private static final String ESCAPED_NEWLINE = "\n"; -``` diff --git a/examples/showcase/embedding/positive/10-comment-filtering.md b/examples/showcase/embedding/positive/comment-filtering.md similarity index 100% rename from examples/showcase/embedding/positive/10-comment-filtering.md rename to examples/showcase/embedding/positive/comment-filtering.md diff --git a/examples/showcase/embedding/positive/13-markdown-fence-shielding.md b/examples/showcase/embedding/positive/markdown-fence-shielding.md similarity index 100% rename from examples/showcase/embedding/positive/13-markdown-fence-shielding.md rename to examples/showcase/embedding/positive/markdown-fence-shielding.md diff --git a/examples/showcase/embedding/positive/07-multi-line-pattern.md b/examples/showcase/embedding/positive/multi-line-pattern.md similarity index 100% rename from examples/showcase/embedding/positive/07-multi-line-pattern.md rename to examples/showcase/embedding/positive/multi-line-pattern.md diff --git a/examples/showcase/embedding/positive/11-multi-part-fragment-separator.md b/examples/showcase/embedding/positive/multi-part-fragment-separator.md similarity index 100% rename from examples/showcase/embedding/positive/11-multi-part-fragment-separator.md rename to examples/showcase/embedding/positive/multi-part-fragment-separator.md diff --git a/examples/showcase/embedding/positive/03-named-fragment.md b/examples/showcase/embedding/positive/named-fragment.md similarity index 100% rename from examples/showcase/embedding/positive/03-named-fragment.md rename to examples/showcase/embedding/positive/named-fragment.md diff --git a/examples/showcase/embedding/positive/05-named-source-root.md b/examples/showcase/embedding/positive/named-source-root.md similarity index 100% rename from examples/showcase/embedding/positive/05-named-source-root.md rename to examples/showcase/embedding/positive/named-source-root.md diff --git a/examples/showcase/embedding/positive/12-overlapping-fragments.md b/examples/showcase/embedding/positive/overlapping-fragments.md similarity index 100% rename from examples/showcase/embedding/positive/12-overlapping-fragments.md rename to examples/showcase/embedding/positive/overlapping-fragments.md diff --git a/examples/showcase/embedding/positive/04-paired-instruction-tag.md b/examples/showcase/embedding/positive/paired-instruction-tag.md similarity index 100% rename from examples/showcase/embedding/positive/04-paired-instruction-tag.md rename to examples/showcase/embedding/positive/paired-instruction-tag.md diff --git a/examples/showcase/embedding/positive/pattern-escaping.md b/examples/showcase/embedding/positive/pattern-escaping.md new file mode 100644 index 0000000..8c54a4f --- /dev/null +++ b/examples/showcase/embedding/positive/pattern-escaping.md @@ -0,0 +1,47 @@ +# Pattern Escaping + +Pattern escaping distinguishes glob syntax from source text that happens to use +the same characters. + +## Literal Asterisk + +The pattern `Use \* to multiply` treats `*` as source text instead of a +wildcard. It matches a line in +[../../code/text/glob-patterns.txt](../../code/text/glob-patterns.txt). + + +```text +Use * to multiply +``` + +## Literal Dollar At The End + +`$` is an end anchor only at the end of a pattern. Use `$$` there when the +source line itself ends with a dollar sign. + + +```text +The value ends with $ +``` + +## Literal Caret At The Start + +`^` is a start anchor only at the start of a pattern. Use `^^` there when the +source line itself starts with a caret. + + +```text +^ starts with caret +``` + +## Literal Backslash-N Text + +Use `\\n` when the source line contains the characters backslash and `n`. The +quote characters are written as `\"` so the instruction remains valid XML. + + +```java +private static final String ESCAPED_NEWLINE = "\n"; +``` diff --git a/examples/showcase/embedding/positive/02-source-line-pattern.md b/examples/showcase/embedding/positive/source-line-pattern.md similarity index 100% rename from examples/showcase/embedding/positive/02-source-line-pattern.md rename to examples/showcase/embedding/positive/source-line-pattern.md diff --git a/examples/showcase/embedding/positive/06-start-end-pattern.md b/examples/showcase/embedding/positive/start-end-pattern.md similarity index 100% rename from examples/showcase/embedding/positive/06-start-end-pattern.md rename to examples/showcase/embedding/positive/start-end-pattern.md diff --git a/examples/showcase/embedding/positive/01-whole-file-source.md b/examples/showcase/embedding/positive/whole-file-source.md similarity index 100% rename from examples/showcase/embedding/positive/01-whole-file-source.md rename to examples/showcase/embedding/positive/whole-file-source.md diff --git a/examples/showcase/showcase_test.go b/examples/showcase/showcase_test.go index 6f400f1..2ea535d 100644 --- a/examples/showcase/showcase_test.go +++ b/examples/showcase/showcase_test.go @@ -42,7 +42,7 @@ func TestShowcasePositiveFlow(t *testing.T) { t.Fatalf("expected positive showcase check to pass:\n%s", checkOutput) } - staleDoc := filepath.Join(docsRoot, "01-whole-file-source.md") + staleDoc := filepath.Join(docsRoot, "whole-file-source.md") replaceInFile(t, staleDoc, "package org.showcase;", "package stale.showcase;") staleOutput, err := runEmbedCode(t, repoRoot, "check", configPath) @@ -50,7 +50,7 @@ func TestShowcasePositiveFlow(t *testing.T) { t.Fatalf("expected stale showcase check to fail:\n%s", staleOutput) } assertOutputContains(t, staleOutput, "File to update:") - assertOutputContains(t, staleOutput, "01-whole-file-source.md") + assertOutputContains(t, staleOutput, "whole-file-source.md") embedOutput, err := runEmbedCode(t, repoRoot, "embed", configPath) if err != nil { From c321c61fbeb0132aefb4791def64fa347b5cf2e1 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 20:08:40 +0200 Subject: [PATCH 06/14] Improve showcase embedding readme. --- examples/showcase/embedding/README.md | 24 +++++++++---------- examples/showcase/embedding/embed-code.yml | 2 -- .../embedding/positive/ignored-by-exclude.md | 15 ------------ ...-instruction-tag.md => instruction-tag.md} | 0 examples/showcase/showcase_test.go | 11 ++------- 5 files changed, 13 insertions(+), 39 deletions(-) delete mode 100644 examples/showcase/embedding/positive/ignored-by-exclude.md rename examples/showcase/embedding/positive/{paired-instruction-tag.md => instruction-tag.md} (100%) diff --git a/examples/showcase/embedding/README.md b/examples/showcase/embedding/README.md index e65b3a1..625968f 100644 --- a/examples/showcase/embedding/README.md +++ b/examples/showcase/embedding/README.md @@ -14,10 +14,10 @@ go run ./main.go -mode check -config-path examples/showcase/embedding/embed-code ### Simple Embedding -- [Whole file source](positive/whole-file-source.md) +- [Whole file source](positive/whole-file-source.md) shows how to embed the whole source file. -- [Paired instruction tag](positive/paired-instruction-tag.md) - shows the preferred paired tag form used throughout the showcase. +- [Instruction tag](positive/instruction-tag.md) + shows the preferred way to use `` tag. - [Named source root](positive/named-source-root.md) shows how to use different configured source trees. @@ -30,31 +30,29 @@ go run ./main.go -mode check -config-path examples/showcase/embedding/embed-code - [Multi-line patterns](positive/multi-line-pattern.md) uses `\n` to match consecutive source lines. - [Pattern escaping](positive/pattern-escaping.md) - shows literal `*`, trailing `$`, leading `^`, and backslash-n text matches. + shows how to escape special characters. ### Fragments -- [Named fragment](positive/named-fragment.md) +- [Named fragment](positive/named-fragment.md) embeds a region wrapped with `#docfragment` and `#enddocfragment` markers. - [Multi-part fragment separator](positive/multi-part-fragment-separator.md) joins repeated fragment parts with the configured separator. -- [Overlapping fragments](positive/overlapping-fragments.md) +- [Overlapping fragments](positive/overlapping-fragments.md) shows fragment markers that share source lines. ### Rendered Content And Documents -- [Comment filtering](positive/comment-filtering.md) - keeps documentation comments while removing regular comments. -- [Markdown fence shielding](positive/markdown-fence-shielding.md) +- [Comment filtering](positive/comment-filtering.md) + shows how to omit comments in the source code. +- [Markdown fence shielding](positive/markdown-fence-shielding.md) shows that instruction-looking text inside ordinary code fences is inert. -- [HTML showcase](positive/html-showcase.html) +- [HTML showcase](positive/html-showcase.html) shows that HTML documents can be processed when the include patterns allow them. -- [Excluded document](positive/ignored-by-exclude.md) - shows that `doc-excludes` prevents a matching file from being processed. ## Negative Examples -The negative examples are intentionally broken and should fail in check mode. +The negative examples are intentionally broken and should fail. Use them to recognize common diagnostics: ```bash diff --git a/examples/showcase/embedding/embed-code.yml b/examples/showcase/embedding/embed-code.yml index 981ced6..1f88c8a 100644 --- a/examples/showcase/embedding/embed-code.yml +++ b/examples/showcase/embedding/embed-code.yml @@ -9,6 +9,4 @@ docs-path: examples/showcase/embedding/positive doc-includes: - "**/*.md" - "**/*.html" -doc-excludes: - - ignored-by-exclude.md separator: "// ..." diff --git a/examples/showcase/embedding/positive/ignored-by-exclude.md b/examples/showcase/embedding/positive/ignored-by-exclude.md deleted file mode 100644 index 8797906..0000000 --- a/examples/showcase/embedding/positive/ignored-by-exclude.md +++ /dev/null @@ -1,15 +0,0 @@ -# Excluded Showcase File - -This file is intentionally present in the positive docs root but absent from -the positive processing flow. - -## How It Works - -[../embed-code.yml](../embed-code.yml) lists this file in `doc-excludes`, so -embed mode and check mode skip it even though it matches the include patterns. -The missing source path proves the exclude is active: processing this file would -fail immediately. - - -```go -``` diff --git a/examples/showcase/embedding/positive/paired-instruction-tag.md b/examples/showcase/embedding/positive/instruction-tag.md similarity index 100% rename from examples/showcase/embedding/positive/paired-instruction-tag.md rename to examples/showcase/embedding/positive/instruction-tag.md diff --git a/examples/showcase/showcase_test.go b/examples/showcase/showcase_test.go index 2ea535d..78ff77e 100644 --- a/examples/showcase/showcase_test.go +++ b/examples/showcase/showcase_test.go @@ -263,7 +263,7 @@ func writeShowcaseConfig(t *testing.T, repoRoot string, docsRoot string) string {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, {name: "kotlin", path: filepath.Join(repoRoot, "examples", "showcase", "code", "kotlin")}, {name: "text", path: filepath.Join(repoRoot, "examples", "showcase", "code", "text")}, - }, []string{"**/*.md", "**/*.html"}, []string{"ignored-by-exclude.md"}) + }, []string{"**/*.md", "**/*.html"}) } // writeSingleDocConfig creates a temp config for one negative showcase document. @@ -275,7 +275,7 @@ func writeSingleDocConfig( ) string { t.Helper() - return writeConfig(t, docsRoot, sources, []string{docInclude}, nil) + return writeConfig(t, docsRoot, sources, []string{docInclude}) } // writeConfig writes a YAML config with absolute source and documentation paths. @@ -284,7 +284,6 @@ func writeConfig( docsRoot string, sources []namedSource, includes []string, - excludes []string, ) string { t.Helper() @@ -299,12 +298,6 @@ func writeConfig( for _, include := range includes { builder.WriteString(fmt.Sprintf(" - %q\n", include)) } - if len(excludes) > 0 { - builder.WriteString("doc-excludes:\n") - for _, exclude := range excludes { - builder.WriteString(fmt.Sprintf(" - %q\n", exclude)) - } - } builder.WriteString("separator: \"// ...\"\n") configPath := filepath.Join(t.TempDir(), "embed-code.yml") From d4838043714544687de96eff5ad6717fc704f628 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 20:36:18 +0200 Subject: [PATCH 07/14] Remove redundant example. --- examples/showcase/README.md | 15 +- examples/showcase/configuration/README.md | 147 +++++++++++++----- .../configuration/docs/root-source/go-mod.md | 17 -- .../configuration/docs/root-source/version.md | 15 -- .../showcase/configuration/root-source.yml | 6 - examples/showcase/embedding/README.md | 6 +- examples/showcase/showcase_test.go | 1 - 7 files changed, 114 insertions(+), 93 deletions(-) delete mode 100644 examples/showcase/configuration/docs/root-source/go-mod.md delete mode 100644 examples/showcase/configuration/docs/root-source/version.md delete mode 100644 examples/showcase/configuration/root-source.yml diff --git a/examples/showcase/README.md b/examples/showcase/README.md index 1ac8e03..843e943 100644 --- a/examples/showcase/README.md +++ b/examples/showcase/README.md @@ -22,22 +22,21 @@ go test -tags showcase ./examples/showcase Verify the positive embedding examples: ```bash -go run ./main.go -mode check -config-path examples/showcase/embedding/embed-code.yml +go run ./main.go -mode=check -config-path=examples/showcase/embedding/embed-code.yml ``` Verify the configuration examples: ```bash -go run ./main.go -mode check -config-path examples/showcase/configuration/root-source.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/single-source.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/named-sources.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/include-exclude.yml -go run ./main.go -mode check -config-path examples/showcase/configuration/multiple-embeddings.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/single-source.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/named-sources.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/include-exclude.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/multiple-embeddings.yml ``` The negative examples are intentionally broken, so these commands should fail: ```bash -go run ./main.go -mode check -config-path examples/showcase/embedding/negative/processing-errors.yml -go run ./main.go -mode check -config-path examples/showcase/embedding/negative/stale.yml +go run ./main.go -mode=check -config-path=examples/showcase/embedding/negative/processing-errors.yml +go run ./main.go -mode=check -config-path=examples/showcase/embedding/negative/stale.yml ``` diff --git a/examples/showcase/configuration/README.md b/examples/showcase/configuration/README.md index 74747e6..5594038 100644 --- a/examples/showcase/configuration/README.md +++ b/examples/showcase/configuration/README.md @@ -1,74 +1,135 @@ # Configuration Examples -These examples show the supported YAML configuration shapes. +This folder is a runnable guide to YAML configuration. Start with the smallest +working config, then add only the options your documentation needs. -Each YAML file has a matching docs root under [docs](docs/). Read the YAML file -first, then open the linked docs folder to see how instructions use that source -configuration. +## Minimal Config -## Repository Root Source +A configuration needs one source root and one documentation root: -[root-source.yml](root-source.yml) uses the repository root as a named source -root. Instructions in [docs/root-source](docs/root-source/) embed files from -the project root with the `$repo` prefix. +```yaml +code-path: examples/showcase/code/java +docs-path: examples/showcase/configuration/docs/single-source +``` + +This shape is shown by [single-source.yml](single-source.yml). + +The command scans files under `docs-path`, finds `` instructions, +and resolves each instruction's `file` path from `code-path`. For example, see +instruction in [docs/single-source/greeting.md](docs/single-source/greeting.md). -Use this shape only when documentation really needs files from the repository -root. The main embedding showcase avoids root sources so ordinary examples stay -independent from project metadata. +Run this example (from the project root): ```bash -go run ./main.go -mode check -config-path examples/showcase/configuration/root-source.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/single-source.yml ``` -## Single Showcase Source Root +## How Paths Are Selected -[single-source.yml](single-source.yml) uses one unnamed `code-path`. -Instructions in [docs/single-source](docs/single-source/) use paths relative to -that root without a `$name` prefix. +The showcase commands are meant to run from the repository root. Relative paths +in `code-path` and `docs-path` are resolved from the command's current working +directory. -This is the simplest configuration for one source tree and one documentation -tree. +`docs-path` selects the documentation root to scan. `doc-includes` and +`doc-excludes` are then matched relative to that documentation root. -```bash -go run ./main.go -mode check -config-path examples/showcase/configuration/single-source.yml -``` +`code-path` selects where source files come from: -## Named Source Roots +- With one unnamed `code-path`, an instruction such as + `file="org/showcase/Greeting.java"` is resolved relative to that source root. +- With named source roots, an instruction such as + `file="$kotlin/org/showcase/KotlinGreeting.kt"` first selects the `kotlin` + source root, then resolves the remaining path inside that root. -[named-sources.yml](named-sources.yml) defines Java, Kotlin, and text source -roots. Instructions in [docs/named-sources](docs/named-sources/) choose a -source root with `$java`, `$kotlin`, or `$text`. +## Add Document Selection -Use this shape when one docs tree needs snippets from several source trees. +Add `doc-includes` when only some files under `docs-path` should be scanned. +Add `doc-excludes` when selected files should be skipped: + +```yaml +code-path: examples/showcase/code/java +docs-path: examples/showcase/configuration/docs/include-exclude +doc-includes: + - "**/*.md" +doc-excludes: + - excluded.md +``` + +This shape is shown by [include-exclude.yml](include-exclude.yml). It processes +[docs/include-exclude/included.md](docs/include-exclude/included.md) and skips +[docs/include-exclude/excluded.md](docs/include-exclude/excluded.md). + +Use include and exclude patterns to skip drafts, generated docs, deprecated +pages, or any file that should not be scanned for active instructions. ```bash -go run ./main.go -mode check -config-path examples/showcase/configuration/named-sources.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/include-exclude.yml +``` + +## Add Named Source Roots + +Use named source roots when one documentation tree embeds snippets from several +source trees: + +```yaml +code-path: + - name: java + path: examples/showcase/code/java + - name: kotlin + path: examples/showcase/code/kotlin + - name: text + path: examples/showcase/code/text +docs-path: examples/showcase/configuration/docs/named-sources ``` -## Include And Exclude Patterns +This shape is shown by [named-sources.yml](named-sources.yml). Its docs live in +[docs/named-sources](docs/named-sources/). -[include-exclude.yml](include-exclude.yml) processes Markdown files in -[docs/include-exclude](docs/include-exclude/) but excludes -[excluded.md](docs/include-exclude/excluded.md). That file intentionally -references a missing source file, so the check succeeds only when -`doc-excludes` is applied. +Instructions choose a source root with the `$name` prefix: + +```markdown + +``` -Use this shape to skip drafts, generated docs, deprecated pages, or any file -that should not be scanned for active instructions. +Run the named-source example: ```bash -go run ./main.go -mode check -config-path examples/showcase/configuration/include-exclude.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/named-sources.yml ``` -## Multiple Embeddings +## Add Multiple Documentation Targets + +Use `embeddings` when one command should process several independent +documentation targets. Each entry has its own `name`, `code-path`, `docs-path`, +and optional settings: + +```yaml +embeddings: + - name: java-guide + code-path: examples/showcase/code/java + docs-path: examples/showcase/configuration/docs/multiple/java + - name: kotlin-guide + code-path: + - name: kotlin + path: examples/showcase/code/kotlin + docs-path: examples/showcase/configuration/docs/multiple/kotlin +``` + +This shape is shown by [multiple-embeddings.yml](multiple-embeddings.yml). It +processes [docs/multiple/java](docs/multiple/java/) and +[docs/multiple/kotlin](docs/multiple/kotlin/) in one run. + +```bash +go run ./main.go -mode=check -config-path=examples/showcase/configuration/multiple-embeddings.yml +``` -[multiple-embeddings.yml](multiple-embeddings.yml) uses the `embeddings` list -to process two independent documentation roots in -[docs/multiple](docs/multiple/) in one run. +## All Configuration Checks -Use this shape when one command should process several independent -documentation targets with different source roots or settings. +Run commands from the project root. ```bash -go run ./main.go -mode check -config-path examples/showcase/configuration/multiple-embeddings.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/single-source.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/named-sources.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/include-exclude.yml +go run ./main.go -mode=check -config-path=examples/showcase/configuration/multiple-embeddings.yml ``` diff --git a/examples/showcase/configuration/docs/root-source/go-mod.md b/examples/showcase/configuration/docs/root-source/go-mod.md deleted file mode 100644 index 4026295..0000000 --- a/examples/showcase/configuration/docs/root-source/go-mod.md +++ /dev/null @@ -1,17 +0,0 @@ -# Repository Root Source - -This configuration example uses the repository root as a named source root. The -instruction embeds the module declaration from `go.mod` through the `$repo` -prefix. - -## How It Works - -[../../root-source.yml](../../root-source.yml) maps the repository root to the -name `repo`. The instruction uses `$repo/go.mod` and a `line` pattern so this -configuration test proves root-source lookup without embedding the whole -project metadata file. - - -```go -module embed-code/embed-code-go -``` diff --git a/examples/showcase/configuration/docs/root-source/version.md b/examples/showcase/configuration/docs/root-source/version.md deleted file mode 100644 index 08cfef4..0000000 --- a/examples/showcase/configuration/docs/root-source/version.md +++ /dev/null @@ -1,15 +0,0 @@ -# Repository Root Line Pattern - -The same root-source configuration can select a single line from a root file. - -## How It Works - -The `$repo` prefix points at the repository root configured in -[../../root-source.yml](../../root-source.yml). The anchored pattern selects the -`Version` constant from `main.go`, which keeps the example small while proving -that root files can be used as sources. - - -```go -const Version = "1.2.2" -``` diff --git a/examples/showcase/configuration/root-source.yml b/examples/showcase/configuration/root-source.yml deleted file mode 100644 index f7209a3..0000000 --- a/examples/showcase/configuration/root-source.yml +++ /dev/null @@ -1,6 +0,0 @@ -code-path: - - name: repo - path: . -docs-path: examples/showcase/configuration/docs/root-source -doc-includes: - - "**/*.md" diff --git a/examples/showcase/embedding/README.md b/examples/showcase/embedding/README.md index 625968f..5085494 100644 --- a/examples/showcase/embedding/README.md +++ b/examples/showcase/embedding/README.md @@ -7,7 +7,7 @@ user should expect when an instruction is malformed or stale. Run the positive examples from the repository root: ```bash -go run ./main.go -mode check -config-path examples/showcase/embedding/embed-code.yml +go run ./main.go -mode=check -config-path=examples/showcase/embedding/embed-code.yml ``` ## Feature Examples @@ -56,8 +56,8 @@ The negative examples are intentionally broken and should fail. Use them to recognize common diagnostics: ```bash -go run ./main.go -mode check -config-path examples/showcase/embedding/negative/processing-errors.yml -go run ./main.go -mode check -config-path examples/showcase/embedding/negative/stale.yml +go run ./main.go -mode=check -config-path=examples/showcase/embedding/negative/processing-errors.yml +go run ./main.go -mode=check -config-path=examples/showcase/embedding/negative/stale.yml ``` The cases live in [negative/docs](negative/docs/). diff --git a/examples/showcase/showcase_test.go b/examples/showcase/showcase_test.go index 78ff77e..742caa8 100644 --- a/examples/showcase/showcase_test.go +++ b/examples/showcase/showcase_test.go @@ -172,7 +172,6 @@ func TestShowcaseConfigurationExamples(t *testing.T) { repoRoot := findRepoRoot(t) configs := []string{ - "root-source.yml", "single-source.yml", "named-sources.yml", "include-exclude.yml", From 3f0d78e71d9908cd2d13334fc04fb97fde770e8f Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 20:47:21 +0200 Subject: [PATCH 08/14] Improve readability. --- examples/showcase/configuration/README.md | 24 ++++--------------- .../docs/single-source/greeting.md | 3 +-- .../showcase/configuration/single-source.yml | 2 -- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/examples/showcase/configuration/README.md b/examples/showcase/configuration/README.md index 5594038..6aabe27 100644 --- a/examples/showcase/configuration/README.md +++ b/examples/showcase/configuration/README.md @@ -12,35 +12,21 @@ code-path: examples/showcase/code/java docs-path: examples/showcase/configuration/docs/single-source ``` -This shape is shown by [single-source.yml](single-source.yml). +This config is shown by [single-source.yml](single-source.yml). -The command scans files under `docs-path`, finds `` instructions, +The application scans files under `docs-path`, finds `` instructions, and resolves each instruction's `file` path from `code-path`. For example, see instruction in [docs/single-source/greeting.md](docs/single-source/greeting.md). +Relative paths in `code-path` and `docs-path` are resolved +from the command's current working directory. + Run this example (from the project root): ```bash go run ./main.go -mode=check -config-path=examples/showcase/configuration/single-source.yml ``` -## How Paths Are Selected - -The showcase commands are meant to run from the repository root. Relative paths -in `code-path` and `docs-path` are resolved from the command's current working -directory. - -`docs-path` selects the documentation root to scan. `doc-includes` and -`doc-excludes` are then matched relative to that documentation root. - -`code-path` selects where source files come from: - -- With one unnamed `code-path`, an instruction such as - `file="org/showcase/Greeting.java"` is resolved relative to that source root. -- With named source roots, an instruction such as - `file="$kotlin/org/showcase/KotlinGreeting.kt"` first selects the `kotlin` - source root, then resolves the remaining path inside that root. - ## Add Document Selection Add `doc-includes` when only some files under `docs-path` should be scanned. diff --git a/examples/showcase/configuration/docs/single-source/greeting.md b/examples/showcase/configuration/docs/single-source/greeting.md index 64cc31c..0fbb97f 100644 --- a/examples/showcase/configuration/docs/single-source/greeting.md +++ b/examples/showcase/configuration/docs/single-source/greeting.md @@ -1,7 +1,6 @@ # Single Source Root -This config uses one unnamed `code-path`, so instructions refer to source files -relative to that root without a `$name` prefix. +This config uses one unnamed `code-path`. ## How It Works diff --git a/examples/showcase/configuration/single-source.yml b/examples/showcase/configuration/single-source.yml index 45fac63..bf2e503 100644 --- a/examples/showcase/configuration/single-source.yml +++ b/examples/showcase/configuration/single-source.yml @@ -1,4 +1,2 @@ code-path: examples/showcase/code/java docs-path: examples/showcase/configuration/docs/single-source -doc-includes: - - "**/*.md" From 4ee5be60ea7a59f60330aaae4fbaf0a239ef87fa Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 21:02:11 +0200 Subject: [PATCH 09/14] Improve comments filtering doc. --- EMBEDDING.md | 22 +-- .../embedding/positive/comment-filtering.md | 125 +++++++++++++++++- 2 files changed, 132 insertions(+), 15 deletions(-) diff --git a/EMBEDDING.md b/EMBEDDING.md index d0ea407..f4d7edc 100644 --- a/EMBEDDING.md +++ b/EMBEDDING.md @@ -214,18 +214,18 @@ Not all languages distinguish documentation from regular comments or inline from The table below lists the supported languages and supported `comments` modes for them: -| Language | Extensions | Supported `comments` modes | -|------------------------|---------------------------------------------------------|--------------------------------------------------------------| -| Java, Kotlin, Groovy | `.java`, `.kt`, `.kts`, `.groovy` | `all`, `none`, `documentation`, `regular`, `inline`, `block` | -| C# | `.cs` | `all`, `none`, `documentation`, `regular`, `inline`, `block` | +| Language | Extensions | Supported `comments` modes | +|------------------------|----------------------------------------------------------|--------------------------------------------------------------| +| Java, Kotlin, Groovy | `.java`, `.kt`, `.kts`, `.groovy` | `all`, `none`, `documentation`, `regular`, `inline`, `block` | +| C# | `.cs` | `all`, `none`, `documentation`, `regular`, `inline`, `block` | | C, C++ | `.c`, `.h`, `.cc`, `.cpp`, `.cxx`, `.hh`, `.hpp`, `.hxx` | `all`, `none`, `inline`, `block` | -| JavaScript, TypeScript | `.js`, `.jsx`, `.ts`, `.tsx` | `all`, `none`, `documentation`, `regular`, `inline`, `block` | -| Go | `.go` | `all`, `none`, `inline`, `block` | -| Protobuf | `.proto` | `all`, `none`, `inline`, `block` | -| Python | `.py`, `.pyi`, `.pyw` | `all`, `none` | -| YAML | `.yml`, `.yaml` | `all`, `none` | -| XML, HTML | `.xml`, `.html`, `.htm` | `all`, `none` | -| Visual Basic | `.vb`, `.bas`, `.vbs`, `.vbscript` | `all`, `none`, `documentation`, `regular` | +| JavaScript, TypeScript | `.js`, `.jsx`, `.ts`, `.tsx` | `all`, `none`, `documentation`, `regular`, `inline`, `block` | +| Go | `.go` | `all`, `none`, `inline`, `block` | +| Protobuf | `.proto` | `all`, `none`, `inline`, `block` | +| Python | `.py`, `.pyi`, `.pyw` | `all`, `none` | +| YAML | `.yml`, `.yaml` | `all`, `none` | +| XML, HTML | `.xml`, `.html`, `.htm` | `all`, `none` | +| Visual Basic | `.vb`, `.bas`, `.vbs`, `.vbscript` | `all`, `none`, `documentation`, `regular` | ## Advanced use cases diff --git a/examples/showcase/embedding/positive/comment-filtering.md b/examples/showcase/embedding/positive/comment-filtering.md index 7f273bb..1b9f1cd 100644 --- a/examples/showcase/embedding/positive/comment-filtering.md +++ b/examples/showcase/embedding/positive/comment-filtering.md @@ -5,10 +5,71 @@ implementation notes. ## How It Works -The instruction embeds the whole Java file and applies -`comments="documentation"`. Javadoc is retained, regular block comments and -inline comments are removed, and comment-like text inside string literals stays -unchanged because it is not a real comment. +The instruction first resolves the source content, then applies comment +filtering before rendering the code fence. If `comments` is omitted, the default +is `all`, so every recognized comment remains in the snippet. + +Supported modes are: + +- `all` keeps every comment. +- `none` removes every recognized comment. +- `documentation` keeps documentation comments, such as Javadoc. +- `regular` keeps non-documentation line and block comments. +- `inline` keeps non-documentation line comments, such as `//`. +- `block` keeps non-documentation block comments, such as `/* */`. + +Comment support depends on the source file extension. Unknown extensions are +embedded unchanged, and not every supported language distinguishes +documentation, regular, inline, and block comments. See +[Comment filtering](../../../../EMBEDDING.md#comment-filtering) for the full +language matrix. + +The examples below embed the same Java file with different `comments` modes so +the rendered output can be compared directly. + +## All Comments + +`comments="all"` keeps every recognized comment. Omitting `comments` would +produce the same result because `all` is the default. + + +```java +package org.showcase; + +/** + * Creates public greetings. + */ +public interface CommentModes { + /* + * Internal implementation note. + */ + String URL = "http://example.org/*not-comment*/"; + + // Regular inline comment. + String greet(String name); // trailing inline comment. +} +``` + +## No Comments + +`comments="none"` removes every recognized comment. + + +```java +package org.showcase; + +public interface CommentModes { + String URL = "http://example.org/*not-comment*/"; + + String greet(String name); +} +``` + +## Documentation Comments + +`comments="documentation"` keeps Javadoc and removes regular block and inline +comments. Comment-like text inside string literals stays unchanged because it is +not a real comment. ```java @@ -23,3 +84,59 @@ public interface CommentModes { String greet(String name); } ``` + +## Regular Comments + +`comments="regular"` keeps non-documentation line and block comments and removes +documentation comments. + + +```java +package org.showcase; + +public interface CommentModes { + /* + * Internal implementation note. + */ + String URL = "http://example.org/*not-comment*/"; + + // Regular inline comment. + String greet(String name); // trailing inline comment. +} +``` + +## Inline Comments + +`comments="inline"` keeps non-documentation line comments and removes block and +documentation comments. + + +```java +package org.showcase; + +public interface CommentModes { + String URL = "http://example.org/*not-comment*/"; + + // Regular inline comment. + String greet(String name); // trailing inline comment. +} +``` + +## Block Comments + +`comments="block"` keeps non-documentation block comments and removes inline and +documentation comments. + + +```java +package org.showcase; + +public interface CommentModes { + /* + * Internal implementation note. + */ + String URL = "http://example.org/*not-comment*/"; + + String greet(String name); +} +``` From 9d21addfcbea62f2974934c47214aea480f28098 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 21:08:26 +0200 Subject: [PATCH 10/14] Improve 'Instruction Tag' doc. --- .../embedding/positive/html-showcase.html | 2 +- .../embedding/positive/instruction-tag.md | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/examples/showcase/embedding/positive/html-showcase.html b/examples/showcase/embedding/positive/html-showcase.html index a1080cb..0650e80 100644 --- a/examples/showcase/embedding/positive/html-showcase.html +++ b/examples/showcase/embedding/positive/html-showcase.html @@ -4,7 +4,7 @@

HTML Embedding Showcase

HTML files are scanned when the include patterns allow them. The instruction - below uses the same paired tag form as the Markdown examples and still needs + below uses the same tag form as the Markdown examples and still needs a Markdown code fence immediately after it.

diff --git a/examples/showcase/embedding/positive/instruction-tag.md b/examples/showcase/embedding/positive/instruction-tag.md index 5c20205..ca75d49 100644 --- a/examples/showcase/embedding/positive/instruction-tag.md +++ b/examples/showcase/embedding/positive/instruction-tag.md @@ -1,15 +1,10 @@ -# Paired Instruction Tag +# Instruction Tag Instructions may be self-closing or paired. The self-closing form is supported, -but this showcase uses paired tags because some Markdown renderers display the +but it is preferred to use paired tags because some Markdown renderers display the XML-style self-closing tag awkwardly. -## How It Works - -The active instruction below has an opening `` tag and a matching -closing tag. No content is required between them; the rendered snippet still -comes from the source file and the following code fence. The whole showcase uses -this paired form so Markdown previews display the instructions consistently. +### Paired tag version ```java @@ -17,3 +12,12 @@ public static void main(String[] args) { System.out.println(greeting("Ada")); } ``` + +### Self-closing tag version + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` From 825644aa8c9c9fbf9c6c9d743b7e16e30b07adad Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 21:17:05 +0200 Subject: [PATCH 11/14] Improve 'Multi-Line Patterns' doc. --- .../embedding/positive/multi-line-pattern.md | 28 +++++++++++++++---- .../positive/multi-part-fragment-separator.md | 8 ++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/examples/showcase/embedding/positive/multi-line-pattern.md b/examples/showcase/embedding/positive/multi-line-pattern.md index 3e38232..bb8ef89 100644 --- a/examples/showcase/embedding/positive/multi-line-pattern.md +++ b/examples/showcase/embedding/positive/multi-line-pattern.md @@ -4,11 +4,16 @@ Use `\n` inside a pattern when one source line is not specific enough. ## How It Works -The `start` value is split into two consecutive line patterns: one that matches -the `@Scenario` line and one that matches the display-name line. The `end` -value works the same way for the assertion and closing brace. Each pattern line -still uses the normal glob rules, so anchors are optional unless you need exact -line boundaries. +A multi-line pattern is a sequence of ordinary line patterns separated by `\n`. +The match succeeds only when those patterns match neighboring source lines in +the same order. This works for `start`, `end`, and `line` patterns. + +Each part keeps the same glob behavior as a one-line pattern. When a part does +not start with `^`, it may begin anywhere in the source line. When it does not +end with `$`, it may stop before the source line ends. Add `^`, `$`, or both to +the individual part that needs a stricter boundary. + +## Start And End Pattern +```java +@Scenario +@Name("adds two numbers") +``` diff --git a/examples/showcase/embedding/positive/multi-part-fragment-separator.md b/examples/showcase/embedding/positive/multi-part-fragment-separator.md index 6894cad..99e44e4 100644 --- a/examples/showcase/embedding/positive/multi-part-fragment-separator.md +++ b/examples/showcase/embedding/positive/multi-part-fragment-separator.md @@ -1,14 +1,16 @@ # Multi-Part Fragment Separator -One named fragment can be split across several source regions. +Fragments can be multipart, if fragment with the same name +is started and ended in the file several times. ## How It Works [../../code/java/org/showcase/MultiPartWorkflow.java](../../code/java/org/showcase/MultiPartWorkflow.java) opens and closes the `Workflow` fragment multiple times. Embed mode collects each part in source order and joins the parts with the `separator` configured in -[../embed-code.yml](../embed-code.yml). This is useful when a guide needs the -shape of a class but wants to hide internal lines between selected regions. +[../embed-code.yml](../embed-code.yml). Separator is `...` by default. +This is useful when a guide needs the shape of a class +but wants to hide internal lines between selected fragment parts. ```java From 836517fc5eff0db3f0499d3455c4adab3ea2dc33 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 21:23:23 +0200 Subject: [PATCH 12/14] Improve 'Named Fragment' doc. --- .../positive/multi-part-fragment-separator.md | 10 ++--- .../embedding/positive/named-fragment.md | 37 ++++++++++++++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/examples/showcase/embedding/positive/multi-part-fragment-separator.md b/examples/showcase/embedding/positive/multi-part-fragment-separator.md index 99e44e4..61b4b36 100644 --- a/examples/showcase/embedding/positive/multi-part-fragment-separator.md +++ b/examples/showcase/embedding/positive/multi-part-fragment-separator.md @@ -6,11 +6,11 @@ is started and ended in the file several times. ## How It Works [../../code/java/org/showcase/MultiPartWorkflow.java](../../code/java/org/showcase/MultiPartWorkflow.java) -opens and closes the `Workflow` fragment multiple times. Embed mode collects -each part in source order and joins the parts with the `separator` configured in -[../embed-code.yml](../embed-code.yml). Separator is `...` by default. -This is useful when a guide needs the shape of a class -but wants to hide internal lines between selected fragment parts. +opens and closes the `Workflow` fragment multiple times. +Embed mode collects each part in source order and joins the parts +with the `separator` configured in [../embed-code.yml](../embed-code.yml). + +Separator is `...` by default. ```java diff --git a/examples/showcase/embedding/positive/named-fragment.md b/examples/showcase/embedding/positive/named-fragment.md index 0dba0b9..47534a8 100644 --- a/examples/showcase/embedding/positive/named-fragment.md +++ b/examples/showcase/embedding/positive/named-fragment.md @@ -1,14 +1,41 @@ # Named Fragment -Use `fragment` when the source file already marks a reusable documentation -region. +Use `fragment` when the source file can mark a stable region that documentation +may reuse. Named fragments are usually easier to maintain than line patterns +when the example has a clear semantic boundary, such as a method, class, or +configuration block. ## How It Works +A named fragment is declared in the source file with `#docfragment "name"` +before the first line to include and `#enddocfragment "name"` after the last +line to include. The marker text can sit inside the comment syntax of the source +language, so Java uses `//`, Kotlin uses `//`, and HTML can use ``. + +The `fragment` value in the embedding instruction must match the source marker +name exactly. During embed mode or check mode, embed-code resolves the named +region, removes the marker lines, normalizes common indentation, and compares or +updates the following code fence. If the fragment name is not present in the +source file, the run reports the missing fragment. + +## Source Markers + [../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) -wraps the `main()` method with matching `#docfragment` and `#enddocfragment` -comments. The instruction resolves the named region, removes the marker lines, -normalizes indentation, and replaces the following fence with the method body. +declares the `main()` fragment like this: + +```java +// #docfragment "main()" +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +// #enddocfragment "main()" +``` + +## Embedding Instruction + +The `file` attribute points to the source file, and `fragment` selects the +named region inside that file. A named fragment cannot be combined with +`start`, `end`, or `line`; use one source-selection method per instruction. ```java From f3378e1302766bf5ce7a648a67557a83e25bd0f9 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 21:48:41 +0200 Subject: [PATCH 13/14] Improve all embedding docs. --- .../embedding/positive/comment-filtering.md | 12 +++++---- .../embedding/positive/html-showcase.html | 7 +++--- .../embedding/positive/instruction-tag.md | 20 +++++++++++---- .../positive/markdown-fence-shielding.md | 17 +++++++++---- .../embedding/positive/multi-line-pattern.md | 19 +++++++++----- .../positive/multi-part-fragment-separator.md | 25 +++++++++++++------ .../embedding/positive/named-fragment.md | 2 +- .../embedding/positive/named-source-root.md | 15 ++++++++--- .../positive/overlapping-fragments.md | 15 ++++++++--- .../embedding/positive/pattern-escaping.md | 11 ++++++++ .../embedding/positive/source-line-pattern.md | 22 ++++++++++------ .../embedding/positive/start-end-pattern.md | 23 ++++++++++++----- .../embedding/positive/whole-file-source.md | 20 +++++++++------ 13 files changed, 148 insertions(+), 60 deletions(-) diff --git a/examples/showcase/embedding/positive/comment-filtering.md b/examples/showcase/embedding/positive/comment-filtering.md index 1b9f1cd..e1b43bc 100644 --- a/examples/showcase/embedding/positive/comment-filtering.md +++ b/examples/showcase/embedding/positive/comment-filtering.md @@ -5,9 +5,10 @@ implementation notes. ## How It Works -The instruction first resolves the source content, then applies comment -filtering before rendering the code fence. If `comments` is omitted, the default -is `all`, so every recognized comment remains in the snippet. +The instruction first resolves the requested source content using `file` plus +any `fragment`, `start`, `end`, or `line` selection. It then applies comment +filtering before comparing or rendering the code fence. If `comments` is +omitted, the default is `all`, so every recognized comment remains in the snippet. Supported modes are: @@ -19,8 +20,9 @@ Supported modes are: - `block` keeps non-documentation block comments, such as `/* */`. Comment support depends on the source file extension. Unknown extensions are -embedded unchanged, and not every supported language distinguishes -documentation, regular, inline, and block comments. See +embedded unchanged. Not every supported language distinguishes documentation, +regular, inline, and block comments, so unsupported categories simply have no +comments to keep. See [Comment filtering](../../../../EMBEDDING.md#comment-filtering) for the full language matrix. diff --git a/examples/showcase/embedding/positive/html-showcase.html b/examples/showcase/embedding/positive/html-showcase.html index 0650e80..af867f9 100644 --- a/examples/showcase/embedding/positive/html-showcase.html +++ b/examples/showcase/embedding/positive/html-showcase.html @@ -4,12 +4,13 @@

HTML Embedding Showcase

HTML files are scanned when the include patterns allow them. The instruction - below uses the same tag form as the Markdown examples and still needs - a Markdown code fence immediately after it. + below uses the same tag form as the Markdown examples and still needs a + Markdown code fence immediately after it.

The source root comes from embed-code.yml, and - the rendered snippet is checked the same way as a Markdown document. + the rendered snippet is checked the same way as a Markdown document. Embed + mode can update the fenced snippet in an HTML file.

diff --git a/examples/showcase/embedding/positive/instruction-tag.md b/examples/showcase/embedding/positive/instruction-tag.md index ca75d49..71b09a4 100644 --- a/examples/showcase/embedding/positive/instruction-tag.md +++ b/examples/showcase/embedding/positive/instruction-tag.md @@ -1,10 +1,16 @@ # Instruction Tag -Instructions may be self-closing or paired. The self-closing form is supported, -but it is preferred to use paired tags because some Markdown renderers display the -XML-style self-closing tag awkwardly. +An embedding instruction is an XML-like tag placed immediately before the code +fence that embed-code should manage. The tag contains source-selection +attributes such as `file`, `fragment`, `line`, etc. -### Paired tag version +The following Markdown fences keeps the language label used by renderers for +syntax highlighting. + +## Paired Tag + +The paired form is preferred in Markdown because it is displayed consistently +by most renderers. ```java @@ -13,7 +19,11 @@ public static void main(String[] args) { } ``` -### Self-closing tag version +## Self-Closing Tag + +The self-closing form is supported and resolves the same source content, +but it is preferred to use paired tags elsewhere because they tend to look better +in Markdown previews. ```java diff --git a/examples/showcase/embedding/positive/markdown-fence-shielding.md b/examples/showcase/embedding/positive/markdown-fence-shielding.md index a3afb2b..4638674 100644 --- a/examples/showcase/embedding/positive/markdown-fence-shielding.md +++ b/examples/showcase/embedding/positive/markdown-fence-shielding.md @@ -1,13 +1,20 @@ # Instructions Inside Markdown Fences -Documentation sometimes needs to show an instruction as plain text. +Use a normal Markdown fence when documentation needs to show an embedding +instruction as plain text. This lets guides explain the syntax without causing +the example instruction to run. ## How It Works -The instruction-looking text below is inside an ordinary Markdown fence, so it -is preserved as documentation content. The parser tracks code-fence state before -looking for active instructions, which prevents examples from accidentally -running while they are being explained. +The parser tracks ordinary code fences before it looks for active +`` instructions. Instruction-looking text inside a fence is +therefore preserved as documentation content, not treated as a real instruction. + +An active instruction must appear outside a fence and must be followed by its +own managed code fence. The nested fence in this example is only part of the +displayed Markdown snippet. + +## Shielded Example ````markdown diff --git a/examples/showcase/embedding/positive/multi-line-pattern.md b/examples/showcase/embedding/positive/multi-line-pattern.md index bb8ef89..eab3dfb 100644 --- a/examples/showcase/embedding/positive/multi-line-pattern.md +++ b/examples/showcase/embedding/positive/multi-line-pattern.md @@ -1,6 +1,7 @@ # Multi-Line Patterns -Use `\n` inside a pattern when one source line is not specific enough. +Use `\n` inside a pattern when one source line is not specific enough to select +the right source range. ## How It Works @@ -8,13 +9,19 @@ A multi-line pattern is a sequence of ordinary line patterns separated by `\n`. The match succeeds only when those patterns match neighboring source lines in the same order. This works for `start`, `end`, and `line` patterns. -Each part keeps the same glob behavior as a one-line pattern. When a part does -not start with `^`, it may begin anywhere in the source line. When it does not -end with `$`, it may stop before the source line ends. Add `^`, `$`, or both to -the individual part that needs a stricter boundary. +Spaces around `\n` are ignored, so `Scenario \n adds two numbers` is treated as +two line patterns: `Scenario` and `adds two numbers`. Each part keeps the same +glob behavior as a one-line pattern. When a part does not start with `^`, it may +begin anywhere in the source line. When it does not end with `$`, it may stop +before the source line ends. Add `^`, `$`, or both to the individual part that +needs a stricter boundary. ## Start And End Pattern +Here the `start` value matches the `@Scenario` line followed immediately by the +display-name line. The `end` value does the same for the assertion line and the +closing brace. + ```java diff --git a/examples/showcase/embedding/positive/named-fragment.md b/examples/showcase/embedding/positive/named-fragment.md index 47534a8..dbcc4e4 100644 --- a/examples/showcase/embedding/positive/named-fragment.md +++ b/examples/showcase/embedding/positive/named-fragment.md @@ -1,7 +1,7 @@ # Named Fragment Use `fragment` when the source file can mark a stable region that documentation -may reuse. Named fragments are usually easier to maintain than line patterns +should reuse. Named fragments are usually easier to maintain than line patterns when the example has a clear semantic boundary, such as a method, class, or configuration block. diff --git a/examples/showcase/embedding/positive/named-source-root.md b/examples/showcase/embedding/positive/named-source-root.md index f703b72..febeaee 100644 --- a/examples/showcase/embedding/positive/named-source-root.md +++ b/examples/showcase/embedding/positive/named-source-root.md @@ -1,13 +1,20 @@ # Named Source Roots -Named roots let one documentation set embed source from several directories. +Use named source roots when one documentation set needs snippets from several +source trees. Names keep instructions explicit and avoid relying on whichever +configured root happens to contain a matching relative path. ## How It Works +In config file, each entry in `code-path` can have a `name` and a `path`. +When an instruction starts its `file` value with `$name/`, embed-code selects +only that named root and then resolves the remaining relative path inside it. + [../embed-code.yml](../embed-code.yml) defines `java`, `kotlin`, and `text` -source roots. The `$kotlin` prefix chooses the Kotlin root before resolving -`org/showcase/KotlinGreeting.kt`. The same docs root can therefore mix Java, -Kotlin, and text snippets without changing the command line. +source roots. The `$kotlin` prefix below chooses the Kotlin root before +resolving `org/showcase/KotlinGreeting.kt`. + +## Embedding Instruction ```kotlin diff --git a/examples/showcase/embedding/positive/overlapping-fragments.md b/examples/showcase/embedding/positive/overlapping-fragments.md index a5e0ebe..6276541 100644 --- a/examples/showcase/embedding/positive/overlapping-fragments.md +++ b/examples/showcase/embedding/positive/overlapping-fragments.md @@ -1,14 +1,21 @@ # Overlapping Fragments -Several fragments can open or close on the same marker line. +Use overlapping fragments when different documentation examples need to share +some source lines but hide different details. One marker line can name several +fragments, and each named fragment is resolved independently. ## How It Works +A marker can open or close multiple fragments by listing several quoted names: +`#docfragment "Class wrapper", "Greeting method"`. + +## Embedding Instruction + [../../code/java/org/showcase/OverlappingFragments.java](../../code/java/org/showcase/OverlappingFragments.java) uses marker lines that name both `Class wrapper` and `Greeting method`. The -instruction asks only for `Greeting method`, so embed mode keeps the shared -class wrapper, skips unrelated method details, and renders the selected method -inside the wrapper. +instruction asks only for `Greeting method`, so the rendered snippet keeps the +shared class wrapper and the greeting method while replacing skipped details +with the configured separator. ```java diff --git a/examples/showcase/embedding/positive/pattern-escaping.md b/examples/showcase/embedding/positive/pattern-escaping.md index 8c54a4f..adf9e06 100644 --- a/examples/showcase/embedding/positive/pattern-escaping.md +++ b/examples/showcase/embedding/positive/pattern-escaping.md @@ -3,6 +3,17 @@ Pattern escaping distinguishes glob syntax from source text that happens to use the same characters. +## How It Works + +Patterns use glob control characters, so `*`, `?`, and character classes have +special meaning unless they are escaped with a backslash. The anchors `^` and +`$` are special only at the beginning and end of a pattern part. Use `^^` at the +beginning to match a literal caret and `$$` at the end to match a literal dollar +sign. + +The sequence `\n` separates consecutive pattern lines. Use `\\n` when the source +line contains the literal characters `\n`. + ## Literal Asterisk The pattern `Use \* to multiply` treats `*` as source text instead of a diff --git a/examples/showcase/embedding/positive/source-line-pattern.md b/examples/showcase/embedding/positive/source-line-pattern.md index 53abf67..21e4ca0 100644 --- a/examples/showcase/embedding/positive/source-line-pattern.md +++ b/examples/showcase/embedding/positive/source-line-pattern.md @@ -1,15 +1,23 @@ -# One Line From A Showcase Source +# Source Line Pattern Use `line` when the documentation needs one source line instead of a whole -fragment. +fragment or range. ## How It Works -The pattern is matched against -[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java). -The opening quote is escaped because instruction attributes are parsed as XML, -and the trailing `*` lets the pattern match the rest of the return expression. -Only the first matching source line is rendered into the fence. +The `line` attribute uses the same glob-style pattern syntax as `start` and`end`. +By default, embed-code behaves as if `*` exists at the beginning and end +of the pattern, so `Hello` can match any source line that contains `Hello`. +Use `^` or `$` when the match must start or end at a line boundary. + +Only the first matching source line is rendered into the fence. A `line` pattern +cannot be combined with `fragment`, `start`, or `end`. + +## Embedding Instruction + +The instruction below searches +[../../code/java/org/showcase/Greeting.java](../../code/java/org/showcase/Greeting.java) +and renders the first line that contains `Hello`. ```java diff --git a/examples/showcase/embedding/positive/start-end-pattern.md b/examples/showcase/embedding/positive/start-end-pattern.md index ed316c7..0468243 100644 --- a/examples/showcase/embedding/positive/start-end-pattern.md +++ b/examples/showcase/embedding/positive/start-end-pattern.md @@ -1,14 +1,25 @@ # Start And End Patterns -Use `start` and `end` when the source does not contain named fragment markers. +Use `start` and `end` patterns to select the code snippet. ## How It Works -The `start` pattern finds the first line in -[../../code/java/org/showcase/PatternSamples.java](../../code/java/org/showcase/PatternSamples.java) -that contains `@Scenario`. The `end` pattern then searches after that start -match and stops at the first line that is exactly four spaces followed by `}`. -Both boundary lines are included in the rendered snippet. +`start` and `end` select an inclusive source range. Embed-code first searches +for the `start` pattern, then searches for the `end` pattern after that match. +Both matched boundary lines are included in the rendered snippet. + +By default, embed-code behaves as if `*` exists at the beginning and end +of the pattern, so `Hello` can match any source line that contains `Hello`. +Use `^` or `$` when the match must start or end at a line boundary. + +If `start` is omitted, the range starts at the beginning of the file. +If `end` is omitted, it continues to the end of the file. + +## Embedding Instruction + +The instruction below finds the first `@Scenario` in +[../../code/java/org/showcase/PatternSamples.java](../../code/java/org/showcase/PatternSamples.java). +It then stops at the next line that is exactly four spaces followed by `}`. ```java From 24f5f2dd6d5c85907336cc6f48f86ebc586dc183 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 10 Jun 2026 21:56:10 +0200 Subject: [PATCH 14/14] Improve showcase test. --- examples/showcase/showcase_test.go | 277 ++++++++++++++--------------- 1 file changed, 131 insertions(+), 146 deletions(-) diff --git a/examples/showcase/showcase_test.go b/examples/showcase/showcase_test.go index 742caa8..2133368 100644 --- a/examples/showcase/showcase_test.go +++ b/examples/showcase/showcase_test.go @@ -28,58 +28,108 @@ import ( "runtime" "strings" "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -// TestShowcasePositiveFlow verifies the showcase docs can be checked, detected as stale, -// repaired with embed mode, and checked again without changing repository files. -func TestShowcasePositiveFlow(t *testing.T) { - repoRoot := findRepoRoot(t) - docsRoot := copyShowcaseDocs(t, repoRoot, filepath.Join("embedding", "positive")) - configPath := writeShowcaseConfig(t, repoRoot, docsRoot) +// TestShowcase runs the showcase example suite. +func TestShowcase(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Showcase Suite") +} + +var _ = Describe("Showcase", func() { + var repoRoot string - checkOutput, err := runEmbedCode(t, repoRoot, "check", configPath) - if err != nil { - t.Fatalf("expected positive showcase check to pass:\n%s", checkOutput) - } + BeforeEach(func() { + repoRoot = findRepoRoot() + }) - staleDoc := filepath.Join(docsRoot, "whole-file-source.md") - replaceInFile(t, staleDoc, "package org.showcase;", "package stale.showcase;") + Describe("embedding examples", func() { + It("should check, detect staleness, embed, and recheck positive examples", func() { + docsRoot := copyShowcaseDocs(repoRoot, filepath.Join("embedding", "positive")) + configPath := writeShowcaseConfig(repoRoot, docsRoot) - staleOutput, err := runEmbedCode(t, repoRoot, "check", configPath) - if err == nil { - t.Fatalf("expected stale showcase check to fail:\n%s", staleOutput) - } - assertOutputContains(t, staleOutput, "File to update:") - assertOutputContains(t, staleOutput, "whole-file-source.md") + checkOutput, err := runEmbedCode(repoRoot, "check", configPath) + Expect(err).ShouldNot(HaveOccurred(), "expected positive showcase check to pass:\n%s", checkOutput) - embedOutput, err := runEmbedCode(t, repoRoot, "embed", configPath) - if err != nil { - t.Fatalf("expected positive showcase embed to repair stale doc:\n%s", embedOutput) - } - assertOutputContains(t, embedOutput, "Embedding process finished.") + staleDoc := filepath.Join(docsRoot, "whole-file-source.md") + replaceInFile(staleDoc, "package org.showcase;", "package stale.showcase;") - finalOutput, err := runEmbedCode(t, repoRoot, "check", configPath) - if err != nil { - t.Fatalf("expected positive showcase check to pass after embed:\n%s", finalOutput) - } + staleOutput, err := runEmbedCode(repoRoot, "check", configPath) + Expect(err).Should(HaveOccurred(), "expected stale showcase check to fail:\n%s", staleOutput) + Expect(staleOutput).Should(ContainSubstring("File to update:")) + Expect(staleOutput).Should(ContainSubstring("whole-file-source.md")) + + embedOutput, err := runEmbedCode(repoRoot, "embed", configPath) + Expect(err).ShouldNot(HaveOccurred(), "expected positive showcase embed to repair stale doc:\n%s", embedOutput) + Expect(embedOutput).Should(ContainSubstring("Embedding process finished.")) + + finalOutput, err := runEmbedCode(repoRoot, "check", configPath) + Expect(err).ShouldNot(HaveOccurred(), "expected positive showcase check to pass after embed:\n%s", finalOutput) + }) + + Describe("negative examples", func() { + for _, tc := range negativeShowcaseCases() { + tc := tc + + It("should report "+tc.name, func() { + docsRoot := copyShowcaseDocs(repoRoot, filepath.Join("embedding", "negative", "docs")) + configPath := writeSingleDocConfig( + docsRoot, + []namedSource{javaSource(repoRoot)}, + tc.doc, + ) + + output, err := runEmbedCode(repoRoot, "check", configPath) + Expect(err).Should(HaveOccurred(), "expected negative scenario to fail:\n%s", output) + for _, expected := range tc.expected { + Expect(output).Should(ContainSubstring(expected)) + } + }) + } + }) + }) + + Describe("configuration examples", func() { + for _, config := range []string{ + "single-source.yml", + "named-sources.yml", + "include-exclude.yml", + "multiple-embeddings.yml", + } { + config := config + + It("should check "+config, func() { + configPath := filepath.Join("examples", "showcase", "configuration", config) + + output, err := runEmbedCode(repoRoot, "check", configPath) + Expect(err).ShouldNot(HaveOccurred(), "expected configuration example to pass:\n%s", output) + }) + } + }) +}) + +// negativeShowcaseCase describes one intentionally broken showcase document. +type negativeShowcaseCase struct { + name string + doc string + expected []string } -// TestShowcaseNegativeScenarios verifies each negative document fails with its expected reason. -func TestShowcaseNegativeScenarios(t *testing.T) { - repoRoot := findRepoRoot(t) +// namedSource is the named code source path. +type namedSource struct { + name string + path string +} - cases := []struct { - name string - doc string - sources []namedSource - expected []string - }{ +// negativeShowcaseCases returns the expected failures for the broken embedding examples. +func negativeShowcaseCases() []negativeShowcaseCase { + return []negativeShowcaseCase{ { name: "missing source", doc: "missing-source.md", - sources: []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, - }, expected: []string{ "code file `$java/org/showcase/DoesNotExist.java", "not found", @@ -88,9 +138,6 @@ func TestShowcaseNegativeScenarios(t *testing.T) { { name: "missing fragment", doc: "missing-fragment.md", - sources: []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, - }, expected: []string{ "fragment `does not exist`", "not found", @@ -99,9 +146,6 @@ func TestShowcaseNegativeScenarios(t *testing.T) { { name: "missing pattern", doc: "missing-pattern.md", - sources: []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, - }, expected: []string{ "matches the line pattern", "doesNotExistPattern", @@ -110,9 +154,6 @@ func TestShowcaseNegativeScenarios(t *testing.T) { { name: "invalid attributes", doc: "invalid-attributes.md", - sources: []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, - }, expected: []string{ "must NOT specify both a fragment name and start/end/line patterns", }, @@ -120,9 +161,6 @@ func TestShowcaseNegativeScenarios(t *testing.T) { { name: "missing code fence", doc: "missing-code-fence.md", - sources: []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, - }, expected: []string{ "expected a markdown code fence after the embedding instruction", }, @@ -130,9 +168,6 @@ func TestShowcaseNegativeScenarios(t *testing.T) { { name: "unclosed code fence", doc: "unclosed-code-fence.md", - sources: []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, - }, expected: []string{ "the markdown code fence after the embedding instruction is not closed", }, @@ -140,9 +175,6 @@ func TestShowcaseNegativeScenarios(t *testing.T) { { name: "stale snippet", doc: "stale-snippet.md", - sources: []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, - }, expected: []string{ "File to update:", "stale-snippet.md", @@ -150,76 +182,44 @@ func TestShowcaseNegativeScenarios(t *testing.T) { }, }, } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - docsRoot := copyShowcaseDocs(t, repoRoot, filepath.Join("embedding", "negative", "docs")) - configPath := writeSingleDocConfig(t, docsRoot, tc.sources, tc.doc) - - output, err := runEmbedCode(t, repoRoot, "check", configPath) - if err == nil { - t.Fatalf("expected negative scenario to fail:\n%s", output) - } - for _, expected := range tc.expected { - assertOutputContains(t, output, expected) - } - }) - } } -// TestShowcaseConfigurationExamples verifies the runnable configuration examples. -func TestShowcaseConfigurationExamples(t *testing.T) { - repoRoot := findRepoRoot(t) - - configs := []string{ - "single-source.yml", - "named-sources.yml", - "include-exclude.yml", - "multiple-embeddings.yml", - } - - for _, config := range configs { - t.Run(config, func(t *testing.T) { - configPath := filepath.Join("examples", "showcase", "configuration", config) - output, err := runEmbedCode(t, repoRoot, "check", configPath) - if err != nil { - t.Fatalf("expected configuration example to pass:\n%s", output) - } - }) +// javaSource returns the Java showcase source root. +func javaSource(repoRoot string) namedSource { + return namedSource{ + name: "java", + path: filepath.Join(repoRoot, "examples", "showcase", "code", "java"), } } -type namedSource struct { - name string - path string -} - // findRepoRoot returns the repository root by walking up from this test file. -func findRepoRoot(t *testing.T) string { - t.Helper() +func findRepoRoot() string { + GinkgoHelper() _, filePath, _, ok := runtime.Caller(0) - if !ok { - t.Fatal("could not locate showcase test file") - } + Expect(ok).Should(BeTrue(), "could not locate showcase test file") return filepath.Clean(filepath.Join(filepath.Dir(filePath), "..", "..")) } // copyShowcaseDocs copies one showcase documentation folder to a temporary test directory. -func copyShowcaseDocs(t *testing.T, repoRoot string, relativeSource string) string { - t.Helper() +func copyShowcaseDocs(repoRoot string, relativeSource string) string { + GinkgoHelper() sourceRoot := filepath.Join(repoRoot, "examples", "showcase", relativeSource) - targetRoot := filepath.Join(t.TempDir(), "docs") - copyDir(t, sourceRoot, targetRoot) + tempRoot, err := os.MkdirTemp("", "embed-code-showcase-docs-*") + Expect(err).ShouldNot(HaveOccurred()) + DeferCleanup(os.RemoveAll, tempRoot) + + targetRoot := filepath.Join(tempRoot, "docs") + copyDir(sourceRoot, targetRoot) return targetRoot } // copyDir recursively copies a directory tree while preserving regular file permissions. -func copyDir(t *testing.T, sourceRoot string, targetRoot string) { - t.Helper() +func copyDir(sourceRoot string, targetRoot string) { + GinkgoHelper() err := filepath.WalkDir(sourceRoot, func(path string, entry os.DirEntry, walkErr error) error { if walkErr != nil { @@ -249,17 +249,15 @@ func copyDir(t *testing.T, sourceRoot string, targetRoot string) { return os.WriteFile(targetPath, data, info.Mode()) }) - if err != nil { - t.Fatalf("failed to copy showcase docs: %v", err) - } + Expect(err).ShouldNot(HaveOccurred(), "failed to copy showcase docs") } // writeShowcaseConfig creates a temp config that points at copied positive docs. -func writeShowcaseConfig(t *testing.T, repoRoot string, docsRoot string) string { - t.Helper() +func writeShowcaseConfig(repoRoot string, docsRoot string) string { + GinkgoHelper() - return writeConfig(t, docsRoot, []namedSource{ - {name: "java", path: filepath.Join(repoRoot, "examples", "showcase", "code", "java")}, + return writeConfig(docsRoot, []namedSource{ + javaSource(repoRoot), {name: "kotlin", path: filepath.Join(repoRoot, "examples", "showcase", "code", "kotlin")}, {name: "text", path: filepath.Join(repoRoot, "examples", "showcase", "code", "text")}, }, []string{"**/*.md", "**/*.html"}) @@ -267,24 +265,22 @@ func writeShowcaseConfig(t *testing.T, repoRoot string, docsRoot string) string // writeSingleDocConfig creates a temp config for one negative showcase document. func writeSingleDocConfig( - t *testing.T, docsRoot string, sources []namedSource, docInclude string, ) string { - t.Helper() + GinkgoHelper() - return writeConfig(t, docsRoot, sources, []string{docInclude}) + return writeConfig(docsRoot, sources, []string{docInclude}) } // writeConfig writes a YAML config with absolute source and documentation paths. func writeConfig( - t *testing.T, docsRoot string, sources []namedSource, includes []string, ) string { - t.Helper() + GinkgoHelper() var builder strings.Builder builder.WriteString("code-path:\n") @@ -299,19 +295,22 @@ func writeConfig( } builder.WriteString("separator: \"// ...\"\n") - configPath := filepath.Join(t.TempDir(), "embed-code.yml") - if err := os.WriteFile(configPath, []byte(builder.String()), 0o644); err != nil { - t.Fatalf("failed to write temp config: %v", err) - } + tempRoot, err := os.MkdirTemp("", "embed-code-showcase-config-*") + Expect(err).ShouldNot(HaveOccurred()) + DeferCleanup(os.RemoveAll, tempRoot) + + configPath := filepath.Join(tempRoot, "embed-code.yml") + Expect(os.WriteFile(configPath, []byte(builder.String()), 0o644)). + Should(Succeed(), "failed to write temp config") return configPath } // runEmbedCode executes the CLI through `go run` and returns combined output. -func runEmbedCode(t *testing.T, repoRoot string, mode string, configPath string) (string, error) { - t.Helper() +func runEmbedCode(repoRoot string, mode string, configPath string) (string, error) { + GinkgoHelper() - cmd := exec.Command("go", "run", "./main.go", "-mode", mode, "-config-path", configPath) + cmd := exec.Command("go", "run", "./main.go", "-mode="+mode, "-config-path="+configPath) cmd.Dir = repoRoot output, err := cmd.CombinedOutput() @@ -319,28 +318,14 @@ func runEmbedCode(t *testing.T, repoRoot string, mode string, configPath string) } // replaceInFile replaces one expected substring in a copied documentation file. -func replaceInFile(t *testing.T, path string, oldText string, newText string) { - t.Helper() +func replaceInFile(path string, oldText string, newText string) { + GinkgoHelper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("failed to read %s: %v", path, err) - } + Expect(err).ShouldNot(HaveOccurred(), "failed to read %s", path) content := string(data) - if !strings.Contains(content, oldText) { - t.Fatalf("expected %s to contain %q", path, oldText) - } + Expect(content).Should(ContainSubstring(oldText), "expected %s to contain %q", path, oldText) content = strings.Replace(content, oldText, newText, 1) - if err = os.WriteFile(path, []byte(content), 0o644); err != nil { - t.Fatalf("failed to write %s: %v", path, err) - } -} - -// assertOutputContains fails the test when a command output does not include a substring. -func assertOutputContains(t *testing.T, output string, expected string) { - t.Helper() - - if !strings.Contains(output, expected) { - t.Fatalf("expected output to contain %q:\n%s", expected, output) - } + Expect(os.WriteFile(path, []byte(content), 0o644)). + Should(Succeed(), "failed to write %s", path) }