diff --git a/.agents/skills/review-docs/SKILL.md b/.agents/skills/review-docs/SKILL.md index 3f565412..d020f45d 100644 --- a/.agents/skills/review-docs/SKILL.md +++ b/.agents/skills/review-docs/SKILL.md @@ -2,8 +2,8 @@ name: review-docs description: > Reviews documentation changes in embed-code-go: Go doc comments, inline - comments, README.md, EMBEDDING.md, PROJECT.md, AGENTS.md, skills, Markdown - fixtures, and user-facing examples. Use when a diff touches prose, + comments, README.md, showcase guides, PROJECT.md, AGENTS.md, skills, + Markdown fixtures, and user-facing examples. Use when a diff touches prose, documentation comments, embedding syntax docs, command examples, architecture/package maps, or agent instructions. Read-only; does not run builds unless explicitly asked. @@ -61,10 +61,10 @@ and repository guidance. Do not duplicate `writer` for authoring strategy, root, check mode, embed mode. - **No orphans.** A paragraph, list item, or table cell must not end with a final line containing only one word. Flag it and propose a reflow or rewrite. -- **Project docs keep their ownership.** `README.md` is user-facing, - `EMBEDDING.md` owns embedding syntax, `PROJECT.md` owns project description, - map, and compact doc pointers, skills own implementation details, and - `AGENTS.md` owns agent policy. +- **Project docs keep their ownership.** `README.md` is the short user-facing + entry point, showcase docs own complete usage details, `PROJECT.md` owns + project description, map, and compact doc pointers, skills own implementation + details, and `AGENTS.md` owns agent policy. ### C. Embedding-Specific Documentation diff --git a/.agents/skills/writer/SKILL.md b/.agents/skills/writer/SKILL.md index 9795f191..d8ef5523 100644 --- a/.agents/skills/writer/SKILL.md +++ b/.agents/skills/writer/SKILL.md @@ -2,10 +2,10 @@ name: writer description: > Writes, edits, and restructures embed-code-go documentation. Use when asked - to create or update README.md, EMBEDDING.md, PROJECT.md, AGENTS.md, skills, - Markdown fixtures, contributor notes, examples, command snippets, Go doc - comments, or inline explanatory comments. Verifies claims against current Go - code, tests, fixtures, and project flows. + to create or update README.md, showcase guides, PROJECT.md, AGENTS.md, + skills, Markdown fixtures, contributor notes, examples, command snippets, + Go doc comments, or inline explanatory comments. Verifies claims against + current Go code, tests, fixtures, and project flows. --- # Write documentation @@ -22,9 +22,13 @@ description: > Prefer updating an existing document over creating a new one. -- `README.md`: user-facing overview, setup, configuration, modes, and flags. -- `EMBEDDING.md`: embedding syntax, source markers, patterns, fences, - separators, and comment modes. +- `README.md`: user-facing overview, short run/build instructions, and links + to the complete guide. +- `showcase/README.md`: complete user guide entry point and runnable workflow. +- `showcase/configuration/README.md`: command-line flags, YAML configuration, + source roots, include/exclude patterns, and multiple embedding targets. +- `showcase/embedding/README.md`: embedding instruction map; detailed syntax + belongs in the related `showcase/embedding/positive/*.md` feature page. - `PROJECT.md`: project overview, project map, and compact doc pointers. - `AGENTS.md`: agent operating policy and repository-wide rules. - `.agents/skills//SKILL.md`: language, testing, writing, review, project @@ -39,7 +43,7 @@ Prefer updating an existing document over creating a new one. - For YAML configuration, check `cli/`, `configuration/`, and config fixtures under `test/resources/config_files/`. - For embedding syntax, check `embedding/parsing/`, `embedding/processor.go`, - `embedding/parsing/instruction.go`, and `EMBEDDING.md`. + `embedding/parsing/instruction.go`, and `showcase/embedding/`. - For source fragments and patterns, check `fragmentation/`, `embedding/parsing/pattern.go`, and relevant source fixtures. - For comments modes, check `embedding/commentfilter/`. @@ -60,7 +64,7 @@ Prefer updating an existing document over creating a new one. code fence, fragment, source root, docs root, include pattern, exclude pattern. - Do not leave orphans in prose: no paragraph, list item, or table cell should end with a final line containing only one word. Reflow or rewrite the text. -- Do not duplicate long explanations between `README.md`, `EMBEDDING.md`, and +- Do not duplicate long explanations between `README.md`, the showcase, and `AGENTS.md`; link to the owning document instead. ## Go Doc Comment Guidance diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0a40bf3c..39c2a6f5 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -31,3 +31,6 @@ jobs: # Tests must be run sequentially because they create temporary files that can cause issues. # Therefore, the "-p 1" argument is required. run: go test -v ./... -p 1 + + - name: Run E2E Tests + run: go test -v -tags showcase ./showcase -p 1 diff --git a/EMBEDDING.md b/EMBEDDING.md deleted file mode 100644 index 4b091d83..00000000 --- a/EMBEDDING.md +++ /dev/null @@ -1,330 +0,0 @@ -# Setting Up Code Embedding - -The `embed-code` utility uses a custom `` tag to insert code snippets from source files into Markdown documentation. - -## Embedding options - -There are two ways to specify which code fragment to embed: - -### Option 1: Named fragments - -Use a named fragment defined within the source file. -```markdown - -``` - -### Option 2: Line patterns - -Use glob-style patterns to match the start and end lines of the fragment. -```markdown - -``` - -## Embedding instruction format - -An `` instruction must always be followed by a Markdown code fence -(opening and closing triple backticks). - -````markdown - -```java -// The utility will automatically overwrite this content. -``` -```` - -The content inside the code fence is irrelevant as it is automatically updated by the tool. -However, you should specify the language for syntax highlighting (e.g., ` ```java `). - -This is true even when embedding into HTML. - -## Named fragments - -### Marking up source code - -To define a named fragment in your source code, wrap the desired lines with -`#docfragment` and `#enddocfragment` comments: - -```java -public final class String - implements java.io.Serializable, Comparable, CharSequence { - - // #docfragment "Constructor" - public String() { - this.value = new char[0]; - } - // #enddocfragment "Constructor" -} -``` - -The `#docfragment` and `#enddocfragment` tags are excluded from the embedded snippet. - -### Usage in documentation - -To embed a named fragment, add the following to your Markdown file: - -````markdown - -```java -``` -```` - -- **`file`**: The path to the source file relative to the `code-path` defined in your configuration. -- **`fragment`**: The name of the fragment to embed. If omitted, the entire file will be embedded. -- **`comments`**: Optional comment filtering mode. If omitted, all comments are retained. - -Fragment names can be any string, but avoid using double quotes (`"`) or characters reserved by XML. - -## Pattern-based fragments - -Alternatively, you can specify a fragment using `start` and `end` patterns: - -````markdown - -```java -``` -```` - -Patterns match the first and last lines of the desired fragment. -If a pattern is omitted, the fragment will start at the beginning or end at the end of the file, respectively. - -To embed a single line, use `line` with the same pattern syntax: - -````markdown - -```java -``` -```` - -The `line` attribute cannot be combined with `start`, `end`, or `fragment`. - -### Pattern syntax - -The tool supports an extended glob syntax for matching lines: - -- `?` — Matches any single character. -- `*` — Matches zero or more characters. -- `[set]` — Matches any single character from the specified set (similar to regex character classes). -- `^` — When used at the start of a pattern, matches the beginning of the line. -- `$` — When used at the end of a pattern, matches the end of the line. - -**Note on anchors:** -By default, patterns imply a wildcard (`*`) at both the start and end. -Use `^` and `$` to disable this behavior and match the exact line start or end. - -#### Multi-line patterns - -Use `\n` inside a `start`, `end`, or `line` pattern to match consecutive source lines. -Spaces around `\n` are ignored, and each pattern line uses the same glob syntax as a -regular one-line pattern. - -````markdown - -```java -``` -```` - -This matches a source range like: - -```java -@Test -@DisplayName("adds two values") -void addsTwoValues() { - int value = 1 + 1; - - assertEquals(2, value); -} -``` - -The `start` pattern above is interpreted as two consecutive line patterns: -`Test` and `adds two values`. Because ordinary patterns imply `*` at both ends, -these match `@Test` and `@DisplayName("adds two values")`. - -Use `^` and `$` on each pattern line when you need exact line matching: - -````markdown - -```java -``` -```` - -Without `\n`, a `start`, `end`, or `line` pattern matches only one source line. - -#### Escaping - -Use a backslash to match glob control characters literally. For example: - -- `\*` matches a literal `*`. -- `\?` matches a literal `?`. -- `\[` matches a literal `[`. - -Since `^` is only special at the start of a pattern, use `^^` to match a literal -`^` there. Since `$` is only special at the end of a pattern, use `$$` to match a -literal `$` there. - -To match literal `\n` text in a source line, write it as `\\n` in the pattern. - -````markdown - -```java -``` -```` - -It's possible to write quote characters in patterns as `\"` instead of the XML entity `"`. -For example, `line="println(\"Hello\")"` is equivalent to `line="println("Hello")"`. - -## Comment filtering - -Use the optional `comments` attribute to reduce comment noise in the embedded snippet: - -````markdown - -```java -``` -```` - -Supported values: - -- `all` — retain all comments. This is the default. -- `none` — strip all recognized comments. -- `documentation` — retain documentation comments such as Javadoc. -- `regular` — retain non-documentation line and block comments. -- `inline` — retain non-documentation line comments such as `//`. -- `block` — retain non-documentation block comments such as `/* */`. - -Unknown extensions are embedded unchanged. - -Not all languages distinguish documentation from regular comments or inline from block comments. - -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` | -| 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` | - -## Advanced use cases - -### Joining several parts of code into one fragment - -A named fragment may consist of one or several pieces declared in a single file. -When rendered, the pieces that belong to a single fragment are joined together. -It is possible to specify a separator between the joined pieces, -see [Configuration](./README.md#arguments) for the corresponding parameter. - -Here is an example of how a multi-piece fragment is rendered. - -**Code:** - -```java -public final class String - implements java.io.Serializable, Comparable, CharSequence { - - // #docfragment "Standard Object methods" - public int hashCode() { - // Method logic. - return hash; - } - // #enddocfragment "Standard Object methods" - - /* Here goes irrelevant code */ - - // #docfragment "Standard Object methods" - public boolean equals(Object anObject) { - // Method logic. - return false; - } - // #enddocfragment "Standard Object methods" - - /* Here goes more irrelevant code */ - - // #docfragment "Standard Object methods" - public String toString() { - return this; - } - // #enddocfragment "Standard Object methods" -} -``` - -**Result:** - -```java -public int hashCode() { - // Method logic. - return hash; -} -... -public boolean equals(Object anObject) { - // Method logic. - return false; -} -... -public String toString() { - return this; -} -``` - -### Declaring multiple fragments in one line - -Multiple fragments can start or end on a single line and may overlap: - -```java -public final class String - implements java.io.Serializable, Comparable, CharSequence { - - // #docfragment "Standard Object methods", "All methods" - public int hashCode() { - // Method logic. - return hash; - } - - public boolean equals(Object anObject) { - // Method logic. - return false; - } - - public String toString() { - return this; - } - // #enddocfragment "Standard Object methods" - - public boolean startsWith(String prefix, int toffset) { - // Method logic. - return true; - } - // #enddocfragment "All methods" -} -``` - -### Usage with other languages - -The fragments can also be used in other languages: - -```html - - - - - - - -``` diff --git a/PROJECT.md b/PROJECT.md index f52a40db..7807e430 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -1,8 +1,8 @@ # Project -This document gives agents and contributors the project overview and package -map. For agent operating policy, read [AGENTS.md](AGENTS.md). For implementation -details, use the matching discovered skill. +This document gives agents and contributors the project overview, package map, +documentation ownership, and CI notes. For agent operating policy, read +[AGENTS.md](AGENTS.md). ## Overview @@ -31,10 +31,37 @@ them inside code fences, and checks whether existing snippets are up-to-date. segment is `type`, but the Go package identifier is `_type` because `type` is a Go keyword. - `test/resources/`: parser, embedding, configuration, and source-code fixtures. +- `showcase/`: executable user guide and end-to-end example suite. -## Documentation +## Documentation Ownership -- `README.md`: user-facing usage. -- `EMBEDDING.md`: embedding syntax and examples. -- `.agents/skills/`: task-specific rules for implementation, testing, writing, - and review. +- `README.md`: project entry point, short run/build instructions, and links to + the complete guide. +- `showcase/README.md`: complete user guide entry point and runnable workflow. +- `showcase/configuration/README.md`: command-line flags, YAML configuration, + source roots, include/exclude patterns, and multiple embedding targets. +- `showcase/embedding/README.md`: `` instruction syntax, source + selection, fragments, patterns, comment filtering, and rendered examples. +- `PROJECT.md`: project map, package ownership, documentation ownership, and CI + notes for contributors and agents. +- `AGENTS.md`: repository operating policy for agents. + +Keep usage details in the showcase. Keep architecture and ownership details in +this file. Keep the root README short. + +## CI + +This repository is configured with these GitHub workflows: + +- `check`: runs linting, the normal Go test suite, and the showcase end-to-end + tests across supported platforms. +- `build_binaries`: builds binaries on pushes to `master`. + +`build_binaries` uses a deploy key instead of the default GitHub Actions bot so +it can push through branch protection. If it must be rotated: + +1. Generate an SSH key pair for GitHub: + `ssh -i ~/.ssh/workflow_deploy_key -T git@github.com`. +2. Add `workflow_deploy_key.pub` as a GitHub deploy key with write access. +3. Add `workflow_deploy_key` as a repository secret named + `WORKFLOW_DEPLOY_KEY`. diff --git a/README.md b/README.md index 551ac13a..7e175572 100644 --- a/README.md +++ b/README.md @@ -1,196 +1,69 @@ # Embed Code -Embed Code provides a way to embed code snippets into Markdown files. -This allows developers to easily include code examples within their documentation. +Embed Code is a Go command-line tool that keeps documentation snippets in sync +with source files. It scans Markdown and HTML documents for `` +instructions, resolves the requested source content, and manages the following +code fence. -Previously, we used the `embed-code` utility written in [Ruby for Jekyll][embed-code-jekyll]. -Since we standardize our sites on Hugo, we rewrote the utility in Go. -This project is the implementation of `embed-code` utility written in Go. +This project replaces the earlier [`embed-code` utility for Ruby/Jekyll][embed-code-jekyll]. -## Key features -- Extracts code fragments from source files and embeds them into documentation. -- Verifies that embedded code samples are up-to-date with the source. -- Supports configuration via command-line arguments or a YAML file. -- Allows embedding specific named fragments or matching code using line patterns. -- Maps multiple code sources to various documentation folders. +## Start Here -For the details of the usage in the documentation and the code, please refer to the [EMBEDDING.md](EMBEDDING.md). +The complete usage guide lives in the [showcase](showcase/README.md). It covers +configuration, embedding instructions, check mode, embed mode, expected +failures, and runnable examples. -## Running +## What It Does -Embed Code operates in two modes: +- Embeds whole files, named fragments, source ranges, or matching source lines. +- Supports multiple named source roots for one documentation tree. +- Filters comments when examples should omit implementation notes. +- Processes Markdown and HTML documents. +- Runs in `check` mode for CI and `embed` mode to update documentation. -1. **Embedding**: Scans documentation files for `` tags and performs the requested embeddings, - overwriting the content of the target documentation files. +## Run -2. **Up-to-Date Check**: Compares the content under `` tags with the corresponding source code fragments. - If they differ, the tool reports which files are out-of-date. +Use a prebuilt binary from [GitHub Releases][releases]: -The mode is selected using the mandatory `-mode` argument: -- `embed`: Performs the embedding process. -- `check`: Checks if embeddings are up-to-date. - -The tool can be run as a pre-compiled binary or via the Go compiler (requires Go [installed](#installation)). -Binaries are located in the `./bin` directory. - -The code and documentation files must be prepared for embedding. -The instructions are provided in the [Setting up documentation and code files](EMBEDDING.md) document. - -### Running the binary - -To run the binary, use: ```bash -./bin/ [arguments] +./bin/embed-code-linux -mode=check -config-path=showcase/embedding/embed-code.yml ``` -### Running the Go file +Or run it with Go: -#### Running with Go - -If you have Go installed, you can run the tool directly: ```bash -go run ./main.go [arguments] +go run ./main.go -mode=check -config-path=showcase/embedding/embed-code.yml ``` -It is better to use the same Go version as the one specified in [go.mod](./go.mod). - -### Arguments - -The available arguments are: - * `-mode`: (Mandatory) The execution mode: `embed` or `check`. - * `-code-path`: (Optional) Path to the source code root directory. - * `-docs-path`: (Optional) Path to the documentation root directory. - * `-config-path`: (Optional) Path to a YAML configuration file containing `code-path` and `docs-path`. - * `-doc-includes`: (Optional) Comma-separated glob patterns for documentation files to include. Defaults to `"**/*.md,**/*.html"`. - * `-doc-excludes`: (Optional) Comma-separated glob patterns for documentation files to exclude. - * `-separator`: (Optional) String used to separate joined code fragments. Defaults to `...`. - * `-info`: (Optional) Enables info-level logging when set to `true`. - * `-stacktrace`: (Optional) Prints stack traces for panics when set to `true`. - -Even though the `code-path`, `docs-path`, and `config-path` arguments are optional, -Embed Code still requires the root directories for code and documentation to be set. -This can be done in one of two ways: +Use `-mode=embed` when documentation should be rewritten with current source +content. See the [configuration guide](showcase/configuration/README.md) for +all command-line flags and YAML options. -1. Provide the `code-path` and `docs-path` arguments, in this case the roots are read directly from the provided paths. -2. Provide the `config-path` argument, in this case the roots are read from the given configuration file. +## Build -If neither of these options is provided, the embedding process will fail. -If both options are set, the embedding will also fail. +Use Go `1.26.4`. -### Configuration file - -Optional settings can be defined in a YAML configuration file: - -```yaml -code-path: path/to/code/root -docs-path: path/to/docs/root -doc-excludes: "**/*-old.*,**/deprecated/*.*" -``` - -For multiple independent documentation targets, use `embeddings` instead: - -```yaml -embeddings: - - name: java - code-path: path/to/code/root/java - docs-path: path/to/java/docs - - name: kotlin - code-path: - - name: samples - path: path/to/code/root/kotlin-samples - - name: runtime - path: path/to/code/root/kotlin-runtime - docs-path: path/to/kotlin/docs - separator: "---" -``` - -The available fields for the configuration file are: - * `code-path`: (Mandatory) Path to the source code root. - May be represented as: - * single path - ```yaml - code-path: path/to/code/root - ``` - * multiple named paths: - ```yaml - code-path: - - name: examples - path: path/to/code/root1 - - name: production - path: path/to/code/root2 - ``` - When a named path is specified, fragments must be referenced in the embedding instructions - using the corresponding path name: - ```md - - ``` - **Do not forget the dollar sign (`$`) before the path name.** - - Code source names must be unique. A configuration may use either one unnamed - code source or one or more named code sources, but named and unnamed sources - cannot be mixed. - - * `docs-path`: (Mandatory) Path to the documentation root. - * `doc-excludes`: (Optional) Glob patterns for documentation files to exclude. - It may be represented as a comma-separated string list or as a YAML sequence. - * `doc-includes`: (Optional) Glob patterns for documentation files to include. - It may be represented as a comma-separated string list or as a YAML sequence. - * `separator`: (Optional) Separator for fragments. - * `info`: (Optional) Enables info-level logging. - * `stacktrace`: (Optional) Prints stack traces for panics. - * `embeddings`: (Optional) A list of complete embedding configurations for multiple - documentation targets. When `embeddings` is set, do not set root-level `code-path` - or `docs-path`. Define `code-path`, `docs-path`, and optional settings inside each entry. - Each entry must set a unique `name`. - -These settings have the same role as the command-line arguments. - -## Installation - -* Go to https://go.dev/doc/install. -* Make sure your Go installed successfully with the command - ```bash - go version - ``` - -## Compilation - -Pre-compiled binaries are available in the `./bin` directory. -However, you can also compile the utility manually if Go is [installed](#installation). - -Navigate to the project root and run: ```bash go build -trimpath -o embed-code main.go ``` -There may be issues when running `go build` outside of the directory containing `main.go`, -even if the path is specified correctly. +This creates `embed-code` on Unix-like systems or `embed-code.exe` on Windows. +The `-trimpath` flag prevents local absolute paths from appearing in stack +traces. -This command creates an executable named `embed-code` (or `embed-code.exe` on Windows). -For further information, please refer to the [docs](https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies). +## Development -Without the `-trimpath` flag, Go includes absolute file paths in stack traces -based on the system where the binary was built. +Run the normal test suite: -Run the following command to build binaries for macOS, Windows and Ubuntu: ```bash -mkdir -p bin && \ -GOOS=darwin GOARCH=amd64 go build -trimpath -o bin/embed-code-macos main.go && chmod +x bin/embed-code-macos && \ -GOOS=windows GOARCH=amd64 go build -trimpath -o bin/embed-code-windows.exe main.go && \ -GOOS=linux GOARCH=amd64 go build -trimpath -o bin/embed-code-linux main.go && chmod +x bin/embed-code-linux +go test ./... ``` -## Development Notes +Run the executable showcase: -This repository is configured with the following GitHub workflows: -- `check` — runs tests across different platforms. -- `build_binaries` — builds binaries on push to the `master` branch. - > Note: This workflow uses a **Deploy Key** instead of the default GitHub Actions bot - > to bypass the `master` branch protection against direct pushes. - > - > If it is necessary to update the Deploy Key, follow these steps: - > 1. Generate an SSH key pair for GitHub: `ssh -i ~/.ssh/workflow_deploy_key -T git@github.com`. - > 2. Add the public key (`workflow_deploy_key.pub`) as a **Deploy Key** in GitHub with write access. - > 3. Add the private key (`workflow_deploy_key`) as a repository secret named `WORKFLOW_DEPLOY_KEY`. +```bash +go test -tags showcase ./showcase +``` [embed-code-jekyll]: https://github.com/SpineEventEngine/embed-code +[releases]: https://github.com/SpineEventEngine/embed-code-go/releases diff --git a/go.mod b/go.mod index 35fe01c4..7b2cf4a6 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 diff --git a/showcase/README.md b/showcase/README.md new file mode 100644 index 00000000..86eec3a2 --- /dev/null +++ b/showcase/README.md @@ -0,0 +1,53 @@ +# Embed Code Showcase + +This is the complete usage guide for `embed-code-go`. It is also executable: +the showcase test runs the examples through the real CLI so the guide stays in +sync with the application. + +## Workflow + +1. Configure source roots and documentation roots. +2. Add an `` instruction followed by a managed code fence. +3. Run check mode in CI to detect stale snippets. +4. Run embed mode when documentation should be rewritten from source. + +## Guide Map + +- [Configuration](configuration/README.md): how the CLI finds source files and documentation files. +- [Embedding](embedding/README.md): how instructions select and render source content. +- [Positive examples](embedding/positive): runnable examples that should pass. +- [Negative examples](embedding/negative/docs): intentionally broken examples + that document diagnostics. + +## Run The Showcase + +Run commands from the repository root. + +The end-to-end suite checks positive examples, expected failures, and all +configuration shapes: + +```bash +go test -tags showcase ./showcase +``` + +Check the positive embedding examples directly: + +```bash +go run ./main.go -mode=check -config-path=showcase/embedding/embed-code.yml +``` + +Check the configuration examples directly: + +```bash +go run ./main.go -mode=check -config-path=showcase/configuration/single-source.yml +go run ./main.go -mode=check -config-path=showcase/configuration/named-sources.yml +go run ./main.go -mode=check -config-path=showcase/configuration/include-exclude.yml +go run ./main.go -mode=check -config-path=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=showcase/embedding/negative/processing-errors.yml +go run ./main.go -mode=check -config-path=showcase/embedding/negative/stale.yml +``` diff --git a/showcase/code/java/org/showcase/CommentModes.java b/showcase/code/java/org/showcase/CommentModes.java new file mode 100644 index 00000000..600856c0 --- /dev/null +++ b/showcase/code/java/org/showcase/CommentModes.java @@ -0,0 +1,34 @@ +/* + * 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 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/showcase/code/java/org/showcase/Greeting.java b/showcase/code/java/org/showcase/Greeting.java new file mode 100644 index 00000000..9197a622 --- /dev/null +++ b/showcase/code/java/org/showcase/Greeting.java @@ -0,0 +1,37 @@ +/* + * 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 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/showcase/code/java/org/showcase/MultiPartWorkflow.java b/showcase/code/java/org/showcase/MultiPartWorkflow.java new file mode 100644 index 00000000..a58fcfc6 --- /dev/null +++ b/showcase/code/java/org/showcase/MultiPartWorkflow.java @@ -0,0 +1,43 @@ +/* + * 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 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/showcase/code/java/org/showcase/OverlappingFragments.java b/showcase/code/java/org/showcase/OverlappingFragments.java new file mode 100644 index 00000000..ca8f845f --- /dev/null +++ b/showcase/code/java/org/showcase/OverlappingFragments.java @@ -0,0 +1,48 @@ +/* + * 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 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/showcase/code/java/org/showcase/PatternSamples.java b/showcase/code/java/org/showcase/PatternSamples.java new file mode 100644 index 00000000..b4315df2 --- /dev/null +++ b/showcase/code/java/org/showcase/PatternSamples.java @@ -0,0 +1,42 @@ +/* + * 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 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/showcase/code/kotlin/org/showcase/KotlinGreeting.kt b/showcase/code/kotlin/org/showcase/KotlinGreeting.kt new file mode 100644 index 00000000..17e41d3b --- /dev/null +++ b/showcase/code/kotlin/org/showcase/KotlinGreeting.kt @@ -0,0 +1,31 @@ +/* + * 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 org.showcase + +object KotlinGreeting { + + // #docfragment "main()" + @JvmStatic + fun main(args: Array) { + println("Hello from Kotlin") + } + // #enddocfragment "main()" +} diff --git a/showcase/code/text/glob-patterns.txt b/showcase/code/text/glob-patterns.txt new file mode 100644 index 00000000..d7529af1 --- /dev/null +++ b/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/showcase/configuration/README.md b/showcase/configuration/README.md new file mode 100644 index 00000000..6d493fd3 --- /dev/null +++ b/showcase/configuration/README.md @@ -0,0 +1,163 @@ +# Configuration + +This guide owns command-line and YAML configuration. Start with the smallest +working shape, then add only the options your documentation needs. + +Run commands from the repository root. + +## Configuration Sources + +Embed Code needs source roots and a documentation root. Provide them in exactly +one of these ways: + +- Direct command-line roots: `-code-path` and `-docs-path`. +- A YAML file selected with `-config-path`. + +Do not combine direct roots with `-config-path`. + +## Command-Line Arguments + +- `-mode`: required execution mode, either `embed` or `check`. +- `-code-path`: source root directory. Use with `-docs-path`. +- `-docs-path`: documentation root directory. Use with `-code-path`. +- `-config-path`: YAML configuration file. +- `-doc-includes`: comma-separated documentation glob patterns to include. + Defaults to `"**/*.md,**/*.html"`. +- `-doc-excludes`: comma-separated documentation glob patterns to exclude. +- `-separator`: text inserted between joined fragment parts. Defaults to `...`. +- `-info`: enables info-level logging when set to `true`. +- `-stacktrace`: prints stack traces for panics when set to `true`. + +Direct roots are useful for small projects: + +```bash +go run ./main.go \ + -mode=check \ + -code-path=showcase/code/java \ + -docs-path=showcase/configuration/docs/single-source +``` + +YAML is easier to maintain when you need named roots, include/exclude patterns, +or multiple documentation targets. + +## YAML Fields + +- `code-path`: source root. Use a string for one unnamed root or a list of + `{name, path}` entries for named roots. Do not mix named and unnamed roots. +- `docs-path`: documentation root. +- `doc-includes`: string or list of glob patterns to include. +- `doc-excludes`: string or list of glob patterns to exclude. +- `separator`: text inserted between joined fragment parts. +- `info`: enables info-level logging. +- `stacktrace`: prints stack traces for panics. +- `embeddings`: list of complete configurations for independent documentation + targets. When `embeddings` is set, define `code-path`, `docs-path`, and + optional settings inside each entry instead of at the root. + +Each `embeddings` entry must have a unique `name`. + +## Minimal Config + +A YAML configuration needs one source root and one documentation root: + +```yaml +code-path: showcase/code/java +docs-path: showcase/configuration/docs/single-source +``` + +This config is shown by [single-source.yml](single-source.yml). + +The application scans files under `docs-path`, finds `` +instructions, and resolves each instruction's `file` path from `code-path`. For +example, see the instruction in [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: + +```bash +go run ./main.go -mode=check -config-path=showcase/configuration/single-source.yml +``` + +## Add Document Selection + +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: showcase/code/java +docs-path: 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 +[included.md](docs/include-exclude/included.md) and skips +[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=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: showcase/code/java + - name: kotlin + path: showcase/code/kotlin + - name: text + path: showcase/code/text +docs-path: showcase/configuration/docs/named-sources +``` + +This shape is shown by [named-sources.yml](named-sources.yml). Its docs live in +[docs/named-sources](docs/named-sources). + +Instructions choose a source root with the `$name` prefix: + +```markdown + +``` + +Run the named-source example: + +```bash +go run ./main.go -mode=check -config-path=showcase/configuration/named-sources.yml +``` + +## 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: showcase/code/java + docs-path: showcase/configuration/docs/multiple/java + - name: kotlin-guide + code-path: + - name: kotlin + path: showcase/code/kotlin + docs-path: 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=showcase/configuration/multiple-embeddings.yml +``` diff --git a/showcase/configuration/docs/include-exclude/excluded.md b/showcase/configuration/docs/include-exclude/excluded.md new file mode 100644 index 00000000..224dd915 --- /dev/null +++ b/showcase/configuration/docs/include-exclude/excluded.md @@ -0,0 +1,14 @@ +# Excluded Document + +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/showcase/configuration/docs/include-exclude/included.md b/showcase/configuration/docs/include-exclude/included.md new file mode 100644 index 00000000..965ec6c7 --- /dev/null +++ b/showcase/configuration/docs/include-exclude/included.md @@ -0,0 +1,18 @@ +# Included Document + +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) { + System.out.println(greeting("Ada")); +} +``` diff --git a/showcase/configuration/docs/multiple/java/greeting.md b/showcase/configuration/docs/multiple/java/greeting.md new file mode 100644 index 00000000..5304fc3f --- /dev/null +++ b/showcase/configuration/docs/multiple/java/greeting.md @@ -0,0 +1,17 @@ +# Java Embedding Entry + +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) { + System.out.println(greeting("Ada")); +} +``` diff --git a/showcase/configuration/docs/multiple/kotlin/greeting.md b/showcase/configuration/docs/multiple/kotlin/greeting.md new file mode 100644 index 00000000..7c6d29f4 --- /dev/null +++ b/showcase/configuration/docs/multiple/kotlin/greeting.md @@ -0,0 +1,19 @@ +# Kotlin Embedding Entry + +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 +fun main(args: Array) { + println("Hello from Kotlin") +} +``` diff --git a/showcase/configuration/docs/named-sources/java-greeting.md b/showcase/configuration/docs/named-sources/java-greeting.md new file mode 100644 index 00000000..bc5dda0e --- /dev/null +++ b/showcase/configuration/docs/named-sources/java-greeting.md @@ -0,0 +1,17 @@ +# Named Java Source + +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) { + System.out.println(greeting("Ada")); +} +``` diff --git a/showcase/configuration/docs/named-sources/kotlin-greeting.md b/showcase/configuration/docs/named-sources/kotlin-greeting.md new file mode 100644 index 00000000..d35675a2 --- /dev/null +++ b/showcase/configuration/docs/named-sources/kotlin-greeting.md @@ -0,0 +1,18 @@ +# Named Kotlin Source + +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 +fun main(args: Array) { + println("Hello from Kotlin") +} +``` diff --git a/showcase/configuration/docs/named-sources/text-line.md b/showcase/configuration/docs/named-sources/text-line.md new file mode 100644 index 00000000..c69d9412 --- /dev/null +++ b/showcase/configuration/docs/named-sources/text-line.md @@ -0,0 +1,16 @@ +# Named Text Source + +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/showcase/configuration/docs/single-source/greeting.md b/showcase/configuration/docs/single-source/greeting.md new file mode 100644 index 00000000..b74347a9 --- /dev/null +++ b/showcase/configuration/docs/single-source/greeting.md @@ -0,0 +1,16 @@ +# Single Source Root + +This config uses one unnamed `code-path`. + +## How It Works + +[single-source.yml](../../single-source.yml) points `code-path` directly +at `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) { + System.out.println(greeting("Ada")); +} +``` diff --git a/showcase/configuration/include-exclude.yml b/showcase/configuration/include-exclude.yml new file mode 100644 index 00000000..c5eec850 --- /dev/null +++ b/showcase/configuration/include-exclude.yml @@ -0,0 +1,6 @@ +code-path: showcase/code/java +docs-path: showcase/configuration/docs/include-exclude +doc-includes: + - "**/*.md" +doc-excludes: + - excluded.md diff --git a/showcase/configuration/multiple-embeddings.yml b/showcase/configuration/multiple-embeddings.yml new file mode 100644 index 00000000..1e05ee61 --- /dev/null +++ b/showcase/configuration/multiple-embeddings.yml @@ -0,0 +1,13 @@ +embeddings: + - name: java-guide + code-path: showcase/code/java + docs-path: showcase/configuration/docs/multiple/java + doc-includes: + - "**/*.md" + - name: kotlin-guide + code-path: + - name: kotlin + path: showcase/code/kotlin + docs-path: showcase/configuration/docs/multiple/kotlin + doc-includes: + - "**/*.md" diff --git a/showcase/configuration/named-sources.yml b/showcase/configuration/named-sources.yml new file mode 100644 index 00000000..eeb5f248 --- /dev/null +++ b/showcase/configuration/named-sources.yml @@ -0,0 +1,10 @@ +code-path: + - name: java + path: showcase/code/java + - name: kotlin + path: showcase/code/kotlin + - name: text + path: showcase/code/text +docs-path: showcase/configuration/docs/named-sources +doc-includes: + - "**/*.md" diff --git a/showcase/configuration/single-source.yml b/showcase/configuration/single-source.yml new file mode 100644 index 00000000..4180a08d --- /dev/null +++ b/showcase/configuration/single-source.yml @@ -0,0 +1,2 @@ +code-path: showcase/code/java +docs-path: showcase/configuration/docs/single-source diff --git a/showcase/embedding/README.md b/showcase/embedding/README.md new file mode 100644 index 00000000..09432fb9 --- /dev/null +++ b/showcase/embedding/README.md @@ -0,0 +1,64 @@ +# Embedding Instructions + +This folder is a runnable guide to `` instructions. The positive +examples show supported features, and the negative examples show failures users +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=showcase/embedding/embed-code.yml +``` + +## Feature Examples + +### Instruction Shape + +- [instruction-tag.md](positive/instruction-tag.md) + explains managed code fences, supported tag forms, attributes, and source + selection rules. +- [whole-file-source.md](positive/whole-file-source.md) + embeds a complete source file. +- [named-source-root.md](positive/named-source-root.md) + selects one configured source tree with `$name/`. + +### Line, Range, And Glob Matching + +- [source-line-pattern.md](positive/source-line-pattern.md) + embeds the first source line that matches a `line` pattern. +- [start-end-pattern.md](positive/start-end-pattern.md) + embeds an inclusive source range selected by `start` and `end`. +- [multi-line-pattern.md](positive/multi-line-pattern.md) + uses `\n` to match consecutive source lines. +- [pattern-escaping.md](positive/pattern-escaping.md) + shows how to match literal glob characters. + +### Fragments + +- [named-fragment.md](positive/named-fragment.md) + embeds a region wrapped with `#docfragment` and `#enddocfragment` markers. +- [multi-part-fragment-separator.md](positive/multi-part-fragment-separator.md) + joins repeated fragment parts with the configured separator. +- [overlapping-fragments.md](positive/overlapping-fragments.md) + shows fragment markers that share source lines. + +### Rendered Content And Documents + +- [comment-filtering.md](positive/comment-filtering.md) + compares `comments` modes and lists language support. +- [markdown-fence-shielding.md](positive/markdown-fence-shielding.md) + shows that instruction-looking text inside ordinary code fences is inert. +- [html-showcase.html](positive/html-showcase.html) + shows that HTML documents can be processed when include patterns allow them. + +## Negative Examples + +The negative examples are intentionally broken and should fail. Use them to +recognize common diagnostics: + +```bash +go run ./main.go -mode=check -config-path=showcase/embedding/negative/processing-errors.yml +go run ./main.go -mode=check -config-path=showcase/embedding/negative/stale.yml +``` + +The cases live in [negative/docs](negative/docs). diff --git a/showcase/embedding/embed-code.yml b/showcase/embedding/embed-code.yml new file mode 100644 index 00000000..550a13e9 --- /dev/null +++ b/showcase/embedding/embed-code.yml @@ -0,0 +1,12 @@ +code-path: + - name: java + path: showcase/code/java + - name: kotlin + path: showcase/code/kotlin + - name: text + path: showcase/code/text +docs-path: showcase/embedding/positive +doc-includes: + - "**/*.md" + - "**/*.html" +separator: "// ..." diff --git a/showcase/embedding/negative/docs/invalid-attributes.md b/showcase/embedding/negative/docs/invalid-attributes.md new file mode 100644 index 00000000..eb7b17d1 --- /dev/null +++ b/showcase/embedding/negative/docs/invalid-attributes.md @@ -0,0 +1,14 @@ +# Invalid Attributes + +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/showcase/embedding/negative/docs/missing-code-fence.md b/showcase/embedding/negative/docs/missing-code-fence.md new file mode 100644 index 00000000..1e51cdec --- /dev/null +++ b/showcase/embedding/negative/docs/missing-code-fence.md @@ -0,0 +1,14 @@ +# Missing 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/showcase/embedding/negative/docs/missing-fragment.md b/showcase/embedding/negative/docs/missing-fragment.md new file mode 100644 index 00000000..084b8a4f --- /dev/null +++ b/showcase/embedding/negative/docs/missing-fragment.md @@ -0,0 +1,15 @@ +# Missing Fragment + +This scenario shows what happens when the source file exists but the named +fragment does not. + +## How It Fails + +[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/showcase/embedding/negative/docs/missing-pattern.md b/showcase/embedding/negative/docs/missing-pattern.md new file mode 100644 index 00000000..68f99447 --- /dev/null +++ b/showcase/embedding/negative/docs/missing-pattern.md @@ -0,0 +1,15 @@ +# Missing Pattern + +This scenario shows what happens when a line pattern matches nothing. + +## How It Fails + +The source file is found, but no line in +[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/showcase/embedding/negative/docs/missing-source.md b/showcase/embedding/negative/docs/missing-source.md new file mode 100644 index 00000000..5a2acc0e --- /dev/null +++ b/showcase/embedding/negative/docs/missing-source.md @@ -0,0 +1,14 @@ +# Missing Source + +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 [showcase/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/showcase/embedding/negative/docs/stale-snippet.md b/showcase/embedding/negative/docs/stale-snippet.md new file mode 100644 index 00000000..35734bf4 --- /dev/null +++ b/showcase/embedding/negative/docs/stale-snippet.md @@ -0,0 +1,18 @@ +# Stale Snippet + +This scenario is syntactically valid, but the rendered code is out of date. + +## How It Fails + +Check mode resolves the `main()` fragment from +[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 +public static void main(String[] args) { + System.out.println("Out of date"); +} +``` diff --git a/showcase/embedding/negative/docs/unclosed-code-fence.md b/showcase/embedding/negative/docs/unclosed-code-fence.md new file mode 100644 index 00000000..35315ded --- /dev/null +++ b/showcase/embedding/negative/docs/unclosed-code-fence.md @@ -0,0 +1,15 @@ +# Unclosed Code Fence + +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 +public static void main(String[] args) { +} diff --git a/showcase/embedding/negative/processing-errors.yml b/showcase/embedding/negative/processing-errors.yml new file mode 100644 index 00000000..f6d85748 --- /dev/null +++ b/showcase/embedding/negative/processing-errors.yml @@ -0,0 +1,13 @@ +code-path: + - name: repo + path: . + - name: java + path: showcase/code/java +docs-path: showcase/embedding/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/showcase/embedding/negative/stale.yml b/showcase/embedding/negative/stale.yml new file mode 100644 index 00000000..17a77e0f --- /dev/null +++ b/showcase/embedding/negative/stale.yml @@ -0,0 +1,6 @@ +code-path: + - name: java + path: showcase/code/java +docs-path: showcase/embedding/negative/docs +doc-includes: + - stale-snippet.md diff --git a/showcase/embedding/positive/comment-filtering.md b/showcase/embedding/positive/comment-filtering.md new file mode 100644 index 00000000..371a8400 --- /dev/null +++ b/showcase/embedding/positive/comment-filtering.md @@ -0,0 +1,220 @@ +# Comment Filtering + +Use `comments` when examples should keep useful API documentation but omit +implementation notes. + +## How It Works + +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: + +- `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. Not every supported language distinguishes documentation, +regular, inline, and block comments, so unsupported categories simply have no +comments to keep. + +## Language Support + +| 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` | + +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 +/* + * 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 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 + +package org.showcase; + +/** + * Creates public greetings. + */ +public interface CommentModes { + String URL = "http://example.org/*not-comment*/"; + + String greet(String name); +} +``` + +## Regular Comments + +`comments="regular"` keeps non-documentation line and block comments and removes +documentation comments. + + +```java +/* + * 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 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 +/* + * 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 org.showcase; + +public interface CommentModes { + /* + * Internal implementation note. + */ + String URL = "http://example.org/*not-comment*/"; + + String greet(String name); +} +``` diff --git a/showcase/embedding/positive/html-showcase.html b/showcase/embedding/positive/html-showcase.html new file mode 100644 index 00000000..b8f1b1f4 --- /dev/null +++ b/showcase/embedding/positive/html-showcase.html @@ -0,0 +1,42 @@ + + + + +

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. +

+

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

+ + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` + + diff --git a/showcase/embedding/positive/instruction-tag.md b/showcase/embedding/positive/instruction-tag.md new file mode 100644 index 00000000..7f080594 --- /dev/null +++ b/showcase/embedding/positive/instruction-tag.md @@ -0,0 +1,76 @@ +# Instruction Tag + +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. + +## Attributes And Selection Rules + +Every instruction needs a `file` attribute. The value is resolved from the +configured `code-path`. With named source roots, start the value with `$name/`, +such as `$java/org/showcase/Greeting.java`. With one unnamed source root, use a +path relative to that root. + +Use one source-selection shape per instruction: + +- `file` alone embeds the whole source file. +- `file` with `fragment` embeds one named fragment. +- `file` with `line` embeds the first matching source line, or consecutive + source lines when the pattern contains `\n`. +- `file` with `start`, `end`, or both embeds an inclusive source range. Without + `start`, the range begins at the first source line. Without `end`, it + continues through the last source line. + +The `fragment` attribute cannot be combined with `start`, `end`, or `line`. +The `line` attribute cannot be combined with `start` or `end`. + +Add `comments` when embedded snippets should keep only selected source comments. +Supported values are `all`, `none`, `documentation`, `regular`, `inline`, and +`block`; omitting `comments` is the same as `comments="all"`. See +[comment-filtering.md](comment-filtering.md) for language support and examples. + +Pattern details live in [source-line-pattern.md](source-line-pattern.md), +[start-end-pattern.md](start-end-pattern.md), +[multi-line-pattern.md](multi-line-pattern.md), and +[pattern-escaping.md](pattern-escaping.md). + +## Managed Code Fence + +An instruction must be followed immediately by a Markdown code fence. Embed mode +replaces the fence content. Check mode compares the fence content with current +source and reports stale files without rewriting them. + +Use a language label on the fence for syntax highlighting: + +````markdown + +```java +``` +```` + +The same rule applies inside HTML documents when include patterns allow `.html` files. + +## Paired Tag + +The paired form is preferred in Markdown because it is displayed consistently +by most renderers. + + +```java +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` + +## 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 +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` diff --git a/showcase/embedding/positive/markdown-fence-shielding.md b/showcase/embedding/positive/markdown-fence-shielding.md new file mode 100644 index 00000000..4638674b --- /dev/null +++ b/showcase/embedding/positive/markdown-fence-shielding.md @@ -0,0 +1,23 @@ +# Instructions Inside Markdown Fences + +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 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 + +```go +``` +```` diff --git a/showcase/embedding/positive/multi-line-pattern.md b/showcase/embedding/positive/multi-line-pattern.md new file mode 100644 index 00000000..eab3dfb0 --- /dev/null +++ b/showcase/embedding/positive/multi-line-pattern.md @@ -0,0 +1,50 @@ +# Multi-Line Patterns + +Use `\n` inside a pattern when one source line is not specific enough to select +the right source range. + +## How It Works + +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. + +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 +@Scenario +@Name("adds two numbers") +void addsTwoNumbers() { + int total = 1 + 1; + + assertEquals(2, total); +} +``` + +## Line Pattern + +The `line` attribute also accepts `\n`. In this case the matched consecutive +source lines become the rendered snippet instead of a single source line. + + +```java +@Scenario +@Name("adds two numbers") +``` diff --git a/showcase/embedding/positive/multi-part-fragment-separator.md b/showcase/embedding/positive/multi-part-fragment-separator.md new file mode 100644 index 00000000..16cb0fe4 --- /dev/null +++ b/showcase/embedding/positive/multi-part-fragment-separator.md @@ -0,0 +1,36 @@ +# Multi-Part Fragment Separator + +Use a multi-part fragment when one documentation example should show several +non-adjacent pieces of a source file as one snippet. This is useful for keeping +the public shape of an example while hiding setup, implementation details, or +unrelated branches between the selected parts. + +## How It Works + +A fragment becomes multi-part when the same `#docfragment "name"` marker is +opened and closed more than once in the same source file. Embed-code collects +the selected parts in source order, normalizes common indentation across all of +them, and inserts the configured `separator` between neighboring parts. + +The default separator is `...`. This showcase uses `// ...` in +[embed-code.yml](../embed-code.yml) so the separator is valid inside Java +snippets. Separator indentation follows the surrounding rendered code, which +keeps skipped sections readable inside classes and methods. + +## Embedding Instruction + +[MultiPartWorkflow.java](../../code/java/org/showcase/MultiPartWorkflow.java) +opens and closes the `Workflow` fragment several times. The instruction below +renders those selected parts as one snippet. + + +```java +public final class MultiPartWorkflow { + // ... + public static void start() { + // ... + System.out.println("Start workflow"); + } +// ... +} +``` diff --git a/showcase/embedding/positive/named-fragment.md b/showcase/embedding/positive/named-fragment.md new file mode 100644 index 00000000..4554487b --- /dev/null +++ b/showcase/embedding/positive/named-fragment.md @@ -0,0 +1,45 @@ +# Named Fragment + +Use `fragment` when the source file can mark a stable region that documentation +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. + +## 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 + +[Greeting.java](../../code/java/org/showcase/Greeting.java) +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 +public static void main(String[] args) { + System.out.println(greeting("Ada")); +} +``` diff --git a/showcase/embedding/positive/named-source-root.md b/showcase/embedding/positive/named-source-root.md new file mode 100644 index 00000000..5dc6edc1 --- /dev/null +++ b/showcase/embedding/positive/named-source-root.md @@ -0,0 +1,25 @@ +# Named Source Roots + +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 below chooses the Kotlin root before +resolving `org/showcase/KotlinGreeting.kt`. + +## Embedding Instruction + + +```kotlin +@JvmStatic +fun main(args: Array) { + println("Hello from Kotlin") +} +``` diff --git a/showcase/embedding/positive/overlapping-fragments.md b/showcase/embedding/positive/overlapping-fragments.md new file mode 100644 index 00000000..eb828b95 --- /dev/null +++ b/showcase/embedding/positive/overlapping-fragments.md @@ -0,0 +1,30 @@ +# Overlapping Fragments + +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 + +[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 the rendered snippet keeps the +shared class wrapper and the greeting method while replacing skipped details +with the configured separator. + + +```java +public final class OverlappingFragments { + // ... + public static String greeting(String name) { + // ... + return "Hello, " + normalized + "!"; + } +// ... +} +``` diff --git a/showcase/embedding/positive/pattern-escaping.md b/showcase/embedding/positive/pattern-escaping.md new file mode 100644 index 00000000..bdfd16a3 --- /dev/null +++ b/showcase/embedding/positive/pattern-escaping.md @@ -0,0 +1,58 @@ +# Pattern Escaping + +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 +wildcard. It matches a line in +[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/showcase/embedding/positive/source-line-pattern.md b/showcase/embedding/positive/source-line-pattern.md new file mode 100644 index 00000000..3ebb831c --- /dev/null +++ b/showcase/embedding/positive/source-line-pattern.md @@ -0,0 +1,25 @@ +# Source Line Pattern + +Use `line` when the documentation needs one source line instead of a whole +fragment or range. + +## How It Works + +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 +[Greeting.java](../../code/java/org/showcase/Greeting.java) +and renders the first line that contains `Hello`. + + +```java +return "Hello, " + name + "!"; +``` diff --git a/showcase/embedding/positive/start-end-pattern.md b/showcase/embedding/positive/start-end-pattern.md new file mode 100644 index 00000000..d50fc9ad --- /dev/null +++ b/showcase/embedding/positive/start-end-pattern.md @@ -0,0 +1,36 @@ +# Start And End Patterns + +Use `start` and `end` patterns to select the code snippet. + +## How It Works + +`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 +[PatternSamples.java](../../code/java/org/showcase/PatternSamples.java). +It then stops at the next line that is exactly four spaces followed by `}`. + + +```java +@Scenario +@Name("adds two numbers") +void addsTwoNumbers() { + int total = 1 + 1; + + assertEquals(2, total); +} +``` diff --git a/showcase/embedding/positive/whole-file-source.md b/showcase/embedding/positive/whole-file-source.md new file mode 100644 index 00000000..f85a8136 --- /dev/null +++ b/showcase/embedding/positive/whole-file-source.md @@ -0,0 +1,55 @@ +# Whole File Source + +Use a whole-file embedding when the documentation should mirror a complete +small source file. This is the smallest useful instruction: it only needs +`file`, followed by the code fence. + +## How It Works + +The `file` attribute is resolved from the configured source roots. + +In this example, the `$java` prefix selects the Java source root from +[embed-code.yml](../embed-code.yml) before resolving `org/showcase/Greeting.java`. + +## Embedding Instruction + +The instruction below embeds the contents of +[Greeting.java](../../code/java/org/showcase/Greeting.java) +into the following code fence, without the embed-code-related instructions. + + +```java +/* + * 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 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/showcase/showcase_test.go b/showcase/showcase_test.go new file mode 100644 index 00000000..3dc5a164 --- /dev/null +++ b/showcase/showcase_test.go @@ -0,0 +1,331 @@ +//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" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// TestShowcase runs the showcase example suite. +func TestShowcase(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Showcase Suite") +} + +var _ = Describe("Showcase", func() { + var repoRoot string + + BeforeEach(func() { + repoRoot = findRepoRoot() + }) + + 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) + + checkOutput, err := runEmbedCode(repoRoot, "check", configPath) + Expect(err).ShouldNot(HaveOccurred(), "expected positive showcase check to pass:\n%s", checkOutput) + + staleDoc := filepath.Join(docsRoot, "whole-file-source.md") + replaceInFile(staleDoc, "package org.showcase;", "package stale.showcase;") + + 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("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 +} + +// namedSource is the named code source path. +type namedSource struct { + name string + path string +} + +// negativeShowcaseCases returns the expected failures for the broken embedding examples. +func negativeShowcaseCases() []negativeShowcaseCase { + return []negativeShowcaseCase{ + { + name: "missing source", + doc: "missing-source.md", + expected: []string{ + "code file `$java/org/showcase/DoesNotExist.java", + "not found", + }, + }, + { + name: "missing fragment", + doc: "missing-fragment.md", + expected: []string{ + "fragment `does not exist`", + "not found", + }, + }, + { + name: "missing pattern", + doc: "missing-pattern.md", + expected: []string{ + "matches the line pattern", + "doesNotExistPattern", + }, + }, + { + name: "invalid attributes", + doc: "invalid-attributes.md", + expected: []string{ + "must NOT specify both a fragment name and start/end/line patterns", + }, + }, + { + name: "missing code fence", + doc: "missing-code-fence.md", + expected: []string{ + "expected a markdown code fence after the embedding instruction", + }, + }, + { + name: "unclosed code fence", + doc: "unclosed-code-fence.md", + expected: []string{ + "the markdown code fence after the embedding instruction is not closed", + }, + }, + { + name: "stale snippet", + doc: "stale-snippet.md", + expected: []string{ + "File to update:", + "stale-snippet.md", + "the documentation files are not up-to-date with code files", + }, + }, + } +} + +// javaSource returns the Java showcase source root. +func javaSource(repoRoot string) namedSource { + return namedSource{ + name: "java", + path: filepath.Join(repoRoot, "showcase", "code", "java"), + } +} + +// findRepoRoot returns the repository root by walking up from this test file. +func findRepoRoot() string { + GinkgoHelper() + + _, filePath, _, ok := runtime.Caller(0) + 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(repoRoot string, relativeSource string) string { + GinkgoHelper() + + sourceRoot := filepath.Join(repoRoot, "showcase", relativeSource) + 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(sourceRoot string, targetRoot string) { + GinkgoHelper() + + 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()) + }) + Expect(err).ShouldNot(HaveOccurred(), "failed to copy showcase docs") +} + +// writeShowcaseConfig creates a temp config that points at copied positive docs. +func writeShowcaseConfig(repoRoot string, docsRoot string) string { + GinkgoHelper() + + return writeConfig(docsRoot, []namedSource{ + javaSource(repoRoot), + {name: "kotlin", path: filepath.Join(repoRoot, "showcase", "code", "kotlin")}, + {name: "text", path: filepath.Join(repoRoot, "showcase", "code", "text")}, + }, []string{"**/*.md", "**/*.html"}) +} + +// writeSingleDocConfig creates a temp config for one negative showcase document. +func writeSingleDocConfig( + docsRoot string, + sources []namedSource, + docInclude string, +) string { + GinkgoHelper() + + return writeConfig(docsRoot, sources, []string{docInclude}) +} + +// writeConfig writes a YAML config with absolute source and documentation paths. +func writeConfig( + docsRoot string, + sources []namedSource, + includes []string, +) string { + GinkgoHelper() + + 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)) + } + builder.WriteString("separator: \"// ...\"\n") + + 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(repoRoot string, mode string, configPath string) (string, error) { + GinkgoHelper() + + 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(path string, oldText string, newText string) { + GinkgoHelper() + + data, err := os.ReadFile(path) + Expect(err).ShouldNot(HaveOccurred(), "failed to read %s", path) + content := string(data) + Expect(content).Should(ContainSubstring(oldText), "expected %s to contain %q", path, oldText) + content = strings.Replace(content, oldText, newText, 1) + Expect(os.WriteFile(path, []byte(content), 0o644)). + Should(Succeed(), "failed to write %s", path) +}