@zio.dev/zio-blocks 0.0.33 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
package/reference/docs.md
CHANGED
|
@@ -1,524 +1,1640 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: docs
|
|
3
|
-
title: "Docs
|
|
3
|
+
title: "ZIO Blocks Docs (Markdown)"
|
|
4
|
+
sidebar_label: "Docs"
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
ZIO Blocks Markdown is a **pure, zero-dependency GitHub Flavored Markdown library** providing an immutable ADT for markdown documents, a strict parser with error handling, multiple renderers (GFM markdown, HTML, terminal), and a compile-time validated string interpolator. Core types: `Doc`, `Block`, `Inline`, `Parser`, `Renderer`, `ToMarkdown`.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Here are the core type definitions:
|
|
10
|
+
|
|
11
|
+
```scala
|
|
12
|
+
import zio.blocks.chunk.Chunk
|
|
13
|
+
|
|
14
|
+
final case class Doc(blocks: Chunk[Block], metadata: Map[String, String] = Map.empty)
|
|
15
|
+
|
|
16
|
+
sealed trait Block extends Product with Serializable
|
|
17
|
+
final case class Paragraph(content: Chunk[Inline]) extends Block
|
|
18
|
+
final case class Heading(level: HeadingLevel, content: Chunk[Inline]) extends Block
|
|
19
|
+
final case class CodeBlock(info: Option[String], code: String) extends Block
|
|
20
|
+
|
|
21
|
+
sealed trait Inline extends Product with Serializable
|
|
22
|
+
final case class Text(value: String) extends Inline
|
|
23
|
+
final case class Code(value: String) extends Inline
|
|
24
|
+
final case class Link(text: Chunk[Inline], url: String, title: Option[String]) extends Inline
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Motivation
|
|
28
|
+
|
|
29
|
+
Markdown is the de facto standard for documentation, READMEs, and formatted text in software development. However, parsing and generating markdown at runtime often requires hand-crafted parsers or fragile string concatenation. ZIO Blocks Markdown provides:
|
|
30
|
+
|
|
31
|
+
- **Type-safe ADT**: Every markdown element is a Scala case class—no stringly-typed HTML generation
|
|
32
|
+
- **Strict parsing**: Rejects invalid markdown with precise error locations (line, column)
|
|
33
|
+
- **Round-trip semantics**: Parse-render-parse cycles preserve document structure
|
|
34
|
+
- **Normalized equality**: Two documents are equal if their normalized forms are equal (adjacent text nodes merged, empty blocks removed)
|
|
35
|
+
- **Multiple output formats**: Render to GFM markdown, HTML (full document or fragment), or colorized terminal
|
|
36
|
+
- **Compile-time validation**: The `md"..."` string interpolator validates markdown syntax at compile time, catching errors before runtime
|
|
37
|
+
- **Type class system**: `ToMarkdown` lets you interpolate custom types into markdown documents
|
|
9
38
|
|
|
10
39
|
## Installation
|
|
11
40
|
|
|
12
|
-
|
|
13
|
-
|
|
41
|
+
Add the dependency to your `build.sbt`:
|
|
42
|
+
|
|
43
|
+
```sbt
|
|
44
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-markdown" % "0.0.51"
|
|
14
45
|
```
|
|
15
46
|
|
|
16
|
-
|
|
47
|
+
For Scala.js, use the cross-build syntax:
|
|
17
48
|
|
|
18
|
-
|
|
49
|
+
```sbt
|
|
50
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-markdown" % "0.0.51"
|
|
51
|
+
```
|
|
19
52
|
|
|
20
|
-
|
|
53
|
+
Supported Scala versions: 2.13.x and 3.x
|
|
54
|
+
|
|
55
|
+
## How They Work Together
|
|
56
|
+
|
|
57
|
+
Markdown documents flow through a **parsing → normalization → rendering pipeline**:
|
|
21
58
|
|
|
22
|
-
```scala
|
|
23
|
-
final case class Doc(blocks: Chunk[Block], metadata: Map[String, String] = Map.empty)
|
|
24
59
|
```
|
|
60
|
+
Markdown String ──> Parser ──> Doc (blocks) ──> Renderer ──> Markdown/HTML/Terminal
|
|
61
|
+
│ │
|
|
62
|
+
ParseError ToMarkdown
|
|
63
|
+
(type class)
|
|
64
|
+
|
|
65
|
+
Doc = Chunk[Block]
|
|
66
|
+
Block = Paragraph | Heading | CodeBlock | List | Table | ... (contains Inlines)
|
|
67
|
+
Inline = Text | Code | Emphasis | Strong | Link | Image | ... (leaf nodes)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Typical workflow:**
|
|
25
71
|
|
|
26
|
-
**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- `toTerminal`: Render with ANSI escape codes for terminal display
|
|
32
|
-
- `toString`: Render back to GFM Markdown
|
|
72
|
+
1. **Parse markdown** — `Parser.parse("# Hello")` returns `Either[ParseError, Doc]`
|
|
73
|
+
2. **Build programmatically** — construct `Doc` with `Heading`, `Paragraph`, `BulletList`, etc.
|
|
74
|
+
3. **Normalize** — `doc.normalize` merges adjacent text, removes empty blocks
|
|
75
|
+
4. **Render to desired format** — GFM (`Renderer.render`), HTML (`HtmlRenderer.render`), terminal (`TerminalRenderer.render`)
|
|
76
|
+
5. **Use interpolator** — `md"# Title with $interpolation"` validates markdown at compile time
|
|
33
77
|
|
|
34
|
-
|
|
78
|
+
Here's a complete example of composing a document:
|
|
35
79
|
|
|
36
|
-
**Example:**
|
|
37
80
|
```scala
|
|
81
|
+
import zio.blocks.chunk.Chunk
|
|
38
82
|
import zio.blocks.docs._
|
|
39
83
|
|
|
40
|
-
val doc =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
val
|
|
44
|
-
|
|
84
|
+
val doc = Doc(Chunk(
|
|
85
|
+
Heading(HeadingLevel.H1, Chunk(Text("My Document"))),
|
|
86
|
+
Paragraph(Chunk(Text("This is a paragraph with "), Strong(Chunk(Text("bold"))), Text(" text."))),
|
|
87
|
+
CodeBlock(Some("scala"), "val x = 42"),
|
|
88
|
+
BulletList(Chunk(
|
|
89
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Item 1")))), None),
|
|
90
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Item 2")))), None)
|
|
91
|
+
), tight = true)
|
|
92
|
+
))
|
|
45
93
|
```
|
|
46
94
|
|
|
47
|
-
|
|
95
|
+
Rendering to GFM markdown:
|
|
48
96
|
|
|
49
|
-
|
|
97
|
+
```scala
|
|
98
|
+
import zio.blocks.chunk.Chunk
|
|
99
|
+
import zio.blocks.docs._
|
|
50
100
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| `Table(header: TableRow, alignments: Chunk[Alignment], rows: Chunk[TableRow])` | GFM table |
|
|
101
|
+
Renderer.render(doc)
|
|
102
|
+
// res0: String = """# My Document
|
|
103
|
+
// This is a paragraph with **bold** text.
|
|
104
|
+
//
|
|
105
|
+
// ```scala
|
|
106
|
+
// val x = 42
|
|
107
|
+
// ```
|
|
108
|
+
// - Item 1
|
|
109
|
+
// - Item 2
|
|
110
|
+
// """
|
|
111
|
+
```
|
|
63
112
|
|
|
64
|
-
|
|
113
|
+
Rendering to HTML:
|
|
65
114
|
|
|
66
|
-
|
|
115
|
+
```scala
|
|
116
|
+
import zio.blocks.chunk.Chunk
|
|
117
|
+
import zio.blocks.docs._
|
|
67
118
|
|
|
68
|
-
|
|
119
|
+
HtmlRenderer.render(doc)
|
|
120
|
+
// res1: String = "<!DOCTYPE html><html><head></head><body><h1>My Document</h1><p>This is a paragraph with <strong>bold</strong> text.</p><pre><code class=\"language-scala\">val x = 42</code></pre><ul><li><p>Item 1</p></li><li><p>Item 2</p></li></ul></body></html>"
|
|
121
|
+
```
|
|
69
122
|
|
|
70
|
-
|
|
71
|
-
|---------|-------------|
|
|
72
|
-
| `Text(value: String)` | Plain text |
|
|
73
|
-
| `Code(value: String)` | Inline code (backticks) |
|
|
74
|
-
| `Emphasis(content: Chunk[Inline])` | Italic text (`*text*` or `_text_`) |
|
|
75
|
-
| `Strong(content: Chunk[Inline])` | Bold text (`**text**` or `__text__`) |
|
|
76
|
-
| `Strikethrough(content: Chunk[Inline])` | Strikethrough (`~~text~~`) |
|
|
77
|
-
| `Link(text: Chunk[Inline], url: String, title: Option[String])` | Hyperlink |
|
|
78
|
-
| `Image(alt: String, url: String, title: Option[String])` | Image |
|
|
79
|
-
| `HtmlInline(content: String)` | Raw inline HTML |
|
|
80
|
-
| `SoftBreak` | Soft line break (rendered as space in HTML) |
|
|
81
|
-
| `HardBreak` | Hard line break (two spaces or backslash before newline) |
|
|
82
|
-
| `Autolink(url: String, isEmail: Boolean)` | Auto-detected URL or email |
|
|
123
|
+
Rendering to terminal:
|
|
83
124
|
|
|
84
|
-
|
|
125
|
+
```scala
|
|
126
|
+
import zio.blocks.chunk.Chunk
|
|
127
|
+
import zio.blocks.docs._
|
|
85
128
|
|
|
86
|
-
|
|
129
|
+
TerminalRenderer.render(doc)
|
|
130
|
+
// res2: String = """My Document
|
|
131
|
+
//
|
|
132
|
+
// This is a paragraph with bold text.
|
|
133
|
+
//
|
|
134
|
+
// val x = 42
|
|
135
|
+
//
|
|
136
|
+
// • Item 1
|
|
137
|
+
// • Item 2
|
|
138
|
+
// """
|
|
139
|
+
```
|
|
87
140
|
|
|
88
|
-
|
|
141
|
+
Parsing markdown strings to create documents:
|
|
89
142
|
|
|
90
143
|
```scala
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
144
|
+
import zio.blocks.chunk.Chunk
|
|
145
|
+
import zio.blocks.docs._
|
|
146
|
+
|
|
147
|
+
val markdown = """# My Document
|
|
148
|
+
|
|
149
|
+
This is a paragraph with **bold** text.
|
|
150
|
+
|
|
151
|
+
- Item 1
|
|
152
|
+
- Item 2
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
Parser.parse(markdown) match {
|
|
156
|
+
case Right(parsedDoc) => println(s"Successfully parsed ${parsedDoc.blocks.size} blocks")
|
|
157
|
+
case Left(err) => println(s"Parse error: ${err.message}")
|
|
102
158
|
}
|
|
103
159
|
```
|
|
104
160
|
|
|
105
|
-
|
|
161
|
+
Round-trip verification (parse → render → parse preserves structure):
|
|
162
|
+
|
|
106
163
|
```scala
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
164
|
+
import zio.blocks.chunk.Chunk
|
|
165
|
+
import zio.blocks.docs._
|
|
166
|
+
|
|
167
|
+
val input = "# Hello\n\nWorld"
|
|
168
|
+
// input: String = """# Hello
|
|
169
|
+
//
|
|
170
|
+
// World"""
|
|
171
|
+
val parsed1 = Parser.parse(input).toOption.get
|
|
172
|
+
// parsed1: Doc = Doc(
|
|
173
|
+
// blocks = IndexedSeq(
|
|
174
|
+
// Heading(level = H1, content = IndexedSeq(Text("Hello"))),
|
|
175
|
+
// Paragraph(IndexedSeq(Text("World")))
|
|
176
|
+
// ),
|
|
177
|
+
// metadata = Map()
|
|
178
|
+
// )
|
|
179
|
+
val rendered = Renderer.render(parsed1)
|
|
180
|
+
// rendered: String = """# Hello
|
|
181
|
+
// World
|
|
182
|
+
//
|
|
183
|
+
// """
|
|
184
|
+
val parsed2 = Parser.parse(rendered).toOption.get
|
|
185
|
+
// parsed2: Doc = Doc(
|
|
186
|
+
// blocks = IndexedSeq(
|
|
187
|
+
// Heading(level = H1, content = IndexedSeq(Text("Hello"))),
|
|
188
|
+
// Paragraph(IndexedSeq(Text("World")))
|
|
189
|
+
// ),
|
|
190
|
+
// metadata = Map()
|
|
191
|
+
// )
|
|
192
|
+
parsed1 == parsed2 // Equal after normalization
|
|
193
|
+
// res5: Boolean = true
|
|
111
194
|
```
|
|
112
195
|
|
|
113
|
-
|
|
196
|
+
## Common Patterns
|
|
197
|
+
|
|
198
|
+
The markdown module provides several patterns for working with documents and types. Here are the most common usage scenarios:
|
|
114
199
|
|
|
115
|
-
|
|
200
|
+
### String Interpolation with Compile-Time Validation
|
|
201
|
+
|
|
202
|
+
The `md"..."` interpolator validates markdown at compile time and supports interpolation of any type with a `ToMarkdown` instance:
|
|
116
203
|
|
|
117
204
|
```scala
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
case object None extends Alignment // Default alignment (---)
|
|
121
|
-
case object Left extends Alignment // Left aligned (:---)
|
|
122
|
-
case object Center extends Alignment // Center aligned (:---:)
|
|
123
|
-
case object Right extends Alignment // Right aligned (---:)
|
|
124
|
-
}
|
|
125
|
-
```
|
|
205
|
+
import zio.blocks.chunk.Chunk
|
|
206
|
+
import zio.blocks.docs._
|
|
126
207
|
|
|
127
|
-
|
|
208
|
+
val name = "Alice"
|
|
209
|
+
val count = 42
|
|
210
|
+
```
|
|
128
211
|
|
|
129
|
-
|
|
212
|
+
Now interpolate these values into markdown:
|
|
130
213
|
|
|
131
214
|
```scala
|
|
132
|
-
|
|
215
|
+
import zio.blocks.chunk.Chunk
|
|
216
|
+
import zio.blocks.docs._
|
|
217
|
+
|
|
218
|
+
md"# Welcome $name\nYou have $count items."
|
|
219
|
+
// res6: Doc = Doc(
|
|
220
|
+
// blocks = IndexedSeq(
|
|
221
|
+
// Heading(
|
|
222
|
+
// level = H1,
|
|
223
|
+
// content = IndexedSeq(Text("Welcome Alice\\nYou have 42 items."))
|
|
224
|
+
// )
|
|
225
|
+
// ),
|
|
226
|
+
// metadata = Map()
|
|
227
|
+
// )
|
|
133
228
|
```
|
|
134
229
|
|
|
135
|
-
|
|
230
|
+
### Task Lists (GFM Feature)
|
|
231
|
+
|
|
232
|
+
Task list items use `ListItem` with `checked: Option[Boolean]`:
|
|
136
233
|
|
|
137
|
-
|
|
234
|
+
```scala
|
|
235
|
+
import zio.blocks.chunk.Chunk
|
|
236
|
+
import zio.blocks.docs._
|
|
138
237
|
|
|
139
|
-
|
|
238
|
+
val tasks = BulletList(Chunk(
|
|
239
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Buy groceries")))), Some(true)),
|
|
240
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Write docs")))), Some(false)),
|
|
241
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Call Alice")))), None) // No checkbox
|
|
242
|
+
), tight = true)
|
|
243
|
+
```
|
|
140
244
|
|
|
141
|
-
|
|
245
|
+
Rendering the task list:
|
|
142
246
|
|
|
143
247
|
```scala
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
248
|
+
import zio.blocks.chunk.Chunk
|
|
249
|
+
import zio.blocks.docs._
|
|
250
|
+
|
|
251
|
+
Renderer.render(Doc(Chunk(tasks)))
|
|
252
|
+
// res7: String = """- [x] Buy groceries
|
|
253
|
+
// - [ ] Write docs
|
|
254
|
+
// - Call Alice
|
|
255
|
+
// """
|
|
147
256
|
```
|
|
148
257
|
|
|
149
|
-
|
|
258
|
+
### Tables with Alignment
|
|
259
|
+
|
|
260
|
+
Tables require a header row, alignment specification, and data rows:
|
|
261
|
+
|
|
150
262
|
```scala
|
|
263
|
+
import zio.blocks.chunk.Chunk
|
|
151
264
|
import zio.blocks.docs._
|
|
152
265
|
|
|
153
|
-
val
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
266
|
+
val table = Table(
|
|
267
|
+
header = TableRow(Chunk(
|
|
268
|
+
Chunk(Text("Name")),
|
|
269
|
+
Chunk(Text("Age")),
|
|
270
|
+
Chunk(Text("City"))
|
|
271
|
+
)),
|
|
272
|
+
alignments = Chunk(Alignment.Left, Alignment.Right, Alignment.Center),
|
|
273
|
+
rows = Chunk(
|
|
274
|
+
TableRow(Chunk(
|
|
275
|
+
Chunk(Text("Alice")),
|
|
276
|
+
Chunk(Text("30")),
|
|
277
|
+
Chunk(Text("NYC"))
|
|
278
|
+
)),
|
|
279
|
+
TableRow(Chunk(
|
|
280
|
+
Chunk(Text("Bob")),
|
|
281
|
+
Chunk(Text("25")),
|
|
282
|
+
Chunk(Text("LA"))
|
|
283
|
+
))
|
|
284
|
+
)
|
|
285
|
+
)
|
|
158
286
|
```
|
|
159
287
|
|
|
160
|
-
|
|
288
|
+
Rendering the table to markdown:
|
|
289
|
+
|
|
290
|
+
```scala
|
|
291
|
+
import zio.blocks.chunk.Chunk
|
|
292
|
+
import zio.blocks.docs._
|
|
161
293
|
|
|
162
|
-
|
|
294
|
+
Renderer.render(Doc(Chunk(table)))
|
|
295
|
+
// res8: String = """| Name | Age | City |
|
|
296
|
+
// |:---|---:|:--:|
|
|
297
|
+
// | Alice | 30 | NYC |
|
|
298
|
+
// | Bob | 25 | LA |
|
|
299
|
+
// """
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Parsing with Error Handling
|
|
163
303
|
|
|
164
|
-
|
|
165
|
-
- **Fenced code blocks** (``` or ~~~)
|
|
166
|
-
- **Thematic breaks** (---, ***, ___)
|
|
167
|
-
- **Block quotes** (> prefix)
|
|
168
|
-
- **Bullet and ordered lists**
|
|
169
|
-
- **Task lists** (- [ ] and - [x])
|
|
170
|
-
- **Tables with alignment**
|
|
171
|
-
- **Inline formatting** (emphasis, strong, strikethrough, code)
|
|
172
|
-
- **Links and images**
|
|
173
|
-
- **Autolinks** (`<url>` or plain URLs)
|
|
174
|
-
- **HTML blocks and inline HTML**
|
|
304
|
+
Parser returns `Either[ParseError, Doc]` with precise location information:
|
|
175
305
|
|
|
176
|
-
|
|
306
|
+
```scala
|
|
307
|
+
import zio.blocks.chunk.Chunk
|
|
308
|
+
import zio.blocks.docs._
|
|
177
309
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
310
|
+
Parser.parse("# Hello\n[invalid link](") match {
|
|
311
|
+
case Right(doc) => println("Parsed successfully")
|
|
312
|
+
case Left(err) => println(s"Error at line ${err.line}, column ${err.column}: ${err.message}")
|
|
313
|
+
}
|
|
314
|
+
```
|
|
182
315
|
|
|
183
|
-
###
|
|
316
|
+
### Round-Trip Semantics
|
|
184
317
|
|
|
185
|
-
|
|
318
|
+
Parse-render-parse cycles preserve document meaning (normalized forms are equal):
|
|
186
319
|
|
|
187
320
|
```scala
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
321
|
+
import zio.blocks.chunk.Chunk
|
|
322
|
+
import zio.blocks.docs._
|
|
323
|
+
|
|
324
|
+
val input = "# Hello\n\nWorld"
|
|
325
|
+
val parsed1 = Parser.parse(input).toOption.get
|
|
326
|
+
val rendered = Renderer.render(parsed1)
|
|
327
|
+
val parsed2 = Parser.parse(rendered).toOption.get
|
|
328
|
+
assert(parsed1 == parsed2) // Equal after normalization
|
|
194
329
|
```
|
|
195
330
|
|
|
196
|
-
|
|
331
|
+
### Custom Type Interpolation
|
|
332
|
+
|
|
333
|
+
Implement `ToMarkdown` for your types to enable interpolation:
|
|
334
|
+
|
|
197
335
|
```scala
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
336
|
+
import zio.blocks.chunk.Chunk
|
|
337
|
+
import zio.blocks.docs._
|
|
338
|
+
|
|
339
|
+
case class User(name: String, role: String)
|
|
340
|
+
|
|
341
|
+
implicit val userToMarkdown: ToMarkdown[User] = { user =>
|
|
342
|
+
Strong(Chunk(Text(user.name), Text(s" – ${user.role}")))
|
|
203
343
|
}
|
|
344
|
+
|
|
345
|
+
val user = User("Alice", "Engineer")
|
|
346
|
+
val doc = md"# Team\n$user"
|
|
204
347
|
```
|
|
205
348
|
|
|
206
|
-
|
|
349
|
+
Additional use cases for custom type interpolation include building documentation programmatically with rich data types, generating reports with structured business objects, and creating templates that mix markdown formatting with domain-specific data. For example, you could create instances for your API models to automatically format them as markdown tables or code examples, making it easy to generate consistent documentation from live data structures.
|
|
350
|
+
|
|
351
|
+
## Integration Points
|
|
352
|
+
|
|
353
|
+
The markdown module integrates seamlessly with other ZIO Blocks components to provide a cohesive ecosystem for working with structured data and documentation.
|
|
354
|
+
|
|
355
|
+
**Schema Integration**: The `schema` module can derive codecs for markdown documents, enabling serialization and deserialization of `Doc` and related types. This allows you to persist markdown structures to various formats (JSON, MessagePack, BSON, etc.) while maintaining type safety and schema validation.
|
|
356
|
+
|
|
357
|
+
**Chunk Integration**: Core data structures like `Doc`, `Block`, and `Inline` use `Chunk[T]` throughout for efficient immutable sequences. This provides O(1) concatenation, memory efficiency, and seamless interoperability with other ZIO libraries that also rely on chunks for data streaming and collection manipulation.
|
|
358
|
+
|
|
359
|
+
**HTTP Integration**: Markdown documents can be served directly as documentation endpoints in HTTP servers. Combined with the multiple renderer options (GFM, HTML, terminal), you can build documentation APIs that serve content in multiple formats based on client preferences, making it trivial to expose living documentation alongside your application.
|
|
207
360
|
|
|
208
|
-
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Doc
|
|
364
|
+
|
|
365
|
+
A complete GitHub Flavored Markdown document.
|
|
366
|
+
|
|
367
|
+
### Definition
|
|
209
368
|
|
|
210
|
-
|
|
369
|
+
Here is the `Doc` type definition:
|
|
211
370
|
|
|
212
371
|
```scala
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def renderInlines(inlines: Chunk[Inline]): String
|
|
217
|
-
def renderInline(inline: Inline): String
|
|
218
|
-
}
|
|
372
|
+
import zio.blocks.chunk.Chunk
|
|
373
|
+
|
|
374
|
+
final case class Doc(blocks: Chunk[Block], metadata: Map[String, String] = Map.empty)
|
|
219
375
|
```
|
|
220
376
|
|
|
221
|
-
|
|
377
|
+
A parsed or constructed markdown document. The `metadata` field is reserved for future use and defaults to an empty map.
|
|
378
|
+
|
|
379
|
+
### Construction
|
|
380
|
+
|
|
381
|
+
Create an empty document:
|
|
382
|
+
|
|
222
383
|
```scala
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
384
|
+
import zio.blocks.chunk.Chunk
|
|
385
|
+
import zio.blocks.docs._
|
|
386
|
+
|
|
387
|
+
val empty = Doc.empty
|
|
226
388
|
```
|
|
227
389
|
|
|
228
|
-
|
|
390
|
+
Construct a document from blocks:
|
|
391
|
+
|
|
392
|
+
```scala
|
|
393
|
+
import zio.blocks.chunk.Chunk
|
|
394
|
+
import zio.blocks.docs._
|
|
229
395
|
|
|
230
|
-
|
|
396
|
+
val doc = Doc(Chunk(
|
|
397
|
+
Heading(HeadingLevel.H1, Chunk(Text("Title"))),
|
|
398
|
+
Paragraph(Chunk(Text("Content")))
|
|
399
|
+
))
|
|
400
|
+
```
|
|
231
401
|
|
|
232
|
-
|
|
402
|
+
Parse a markdown string to create a document:
|
|
233
403
|
|
|
234
404
|
```scala
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def escape(s: String): String // HTML entity escaping
|
|
405
|
+
import zio.blocks.chunk.Chunk
|
|
406
|
+
import zio.blocks.docs._
|
|
407
|
+
|
|
408
|
+
Parser.parse("# Title\n\nContent") match {
|
|
409
|
+
case Right(doc) => // use doc
|
|
410
|
+
case Left(err) => // handle error
|
|
242
411
|
}
|
|
243
412
|
```
|
|
244
413
|
|
|
245
|
-
|
|
246
|
-
```scala
|
|
247
|
-
val doc = Parser.parse("# Hello\n\n**Bold**").toOption.get
|
|
414
|
+
### Core Operations
|
|
248
415
|
|
|
249
|
-
|
|
250
|
-
val fullHtml = HtmlRenderer.render(doc)
|
|
416
|
+
Merge documents using concatenation with `Doc#++`:
|
|
251
417
|
|
|
252
|
-
|
|
253
|
-
|
|
418
|
+
```scala
|
|
419
|
+
import zio.blocks.chunk.Chunk
|
|
420
|
+
import zio.blocks.docs._
|
|
421
|
+
|
|
422
|
+
val doc1 = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Part 1")))))
|
|
423
|
+
val doc2 = Doc(Chunk(Paragraph(Chunk(Text("Part 2")))))
|
|
424
|
+
val combined = doc1 ++ doc2
|
|
254
425
|
```
|
|
255
426
|
|
|
256
|
-
|
|
257
|
-
- Code blocks with language classes (`language-scala`, etc.)
|
|
258
|
-
- Tables with proper alignment styles
|
|
259
|
-
- Task list items with disabled checkboxes
|
|
260
|
-
- Proper HTML entity escaping for safety
|
|
427
|
+
Render a document to GFM markdown:
|
|
261
428
|
|
|
262
|
-
|
|
429
|
+
```scala
|
|
430
|
+
import zio.blocks.chunk.Chunk
|
|
431
|
+
import zio.blocks.docs._
|
|
432
|
+
|
|
433
|
+
val doc = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Hello")))))
|
|
434
|
+
// doc: Doc = Doc(
|
|
435
|
+
// blocks = IndexedSeq(Heading(level = H1, content = IndexedSeq(Text("Hello")))),
|
|
436
|
+
// metadata = Map()
|
|
437
|
+
// )
|
|
438
|
+
val markdown: String = doc.toString // Calls Renderer.render internally
|
|
439
|
+
// markdown: String = """# Hello
|
|
440
|
+
// """
|
|
441
|
+
```
|
|
263
442
|
|
|
264
|
-
Render with
|
|
443
|
+
Render to HTML with full document structure including DOCTYPE (returns complete HTML5 document with `<!DOCTYPE html>` wrapper):
|
|
265
444
|
|
|
266
445
|
```scala
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
446
|
+
import zio.blocks.chunk.Chunk
|
|
447
|
+
import zio.blocks.docs._
|
|
448
|
+
|
|
449
|
+
val doc = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Hello")))))
|
|
450
|
+
// doc: Doc = Doc(
|
|
451
|
+
// blocks = IndexedSeq(Heading(level = H1, content = IndexedSeq(Text("Hello")))),
|
|
452
|
+
// metadata = Map()
|
|
453
|
+
// )
|
|
454
|
+
val html = doc.toHtml
|
|
455
|
+
// html: String = "<!DOCTYPE html><html><head></head><body><h1>Hello</h1></body></html>"
|
|
273
456
|
```
|
|
274
457
|
|
|
275
|
-
|
|
458
|
+
Render to HTML fragment containing only the content (returns just the rendered HTML blocks without wrapper tags):
|
|
459
|
+
|
|
276
460
|
```scala
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
461
|
+
import zio.blocks.chunk.Chunk
|
|
462
|
+
import zio.blocks.docs._
|
|
463
|
+
|
|
464
|
+
val doc = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Hello")))))
|
|
465
|
+
// doc: Doc = Doc(
|
|
466
|
+
// blocks = IndexedSeq(Heading(level = H1, content = IndexedSeq(Text("Hello")))),
|
|
467
|
+
// metadata = Map()
|
|
468
|
+
// )
|
|
469
|
+
val fragment = doc.toHtmlFragment
|
|
470
|
+
// fragment: String = "<h1>Hello</h1>"
|
|
280
471
|
```
|
|
281
472
|
|
|
282
|
-
|
|
283
|
-
- **Headings:** Bold + colored (H1=red, H2=yellow, H3=green, H4=cyan, H5=blue, H6=magenta)
|
|
284
|
-
- **Code blocks:** Gray background
|
|
285
|
-
- **Inline code:** Gray background
|
|
286
|
-
- **Emphasis:** Italic
|
|
287
|
-
- **Strong:** Bold
|
|
288
|
-
- **Strikethrough:** Strike-through style
|
|
289
|
-
- **Links:** Blue + underlined
|
|
290
|
-
- **Block quotes:** Prefixed with │
|
|
473
|
+
Render to colorized terminal output (returns ANSI-colored string suitable for terminal display):
|
|
291
474
|
|
|
292
|
-
|
|
475
|
+
```scala
|
|
476
|
+
import zio.blocks.chunk.Chunk
|
|
477
|
+
import zio.blocks.docs._
|
|
293
478
|
|
|
294
|
-
|
|
479
|
+
val doc = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Hello")))))
|
|
480
|
+
// doc: Doc = Doc(
|
|
481
|
+
// blocks = IndexedSeq(Heading(level = H1, content = IndexedSeq(Text("Hello")))),
|
|
482
|
+
// metadata = Map()
|
|
483
|
+
// )
|
|
484
|
+
val terminal = doc.toTerminal
|
|
485
|
+
// terminal: String = """Hello
|
|
486
|
+
//
|
|
487
|
+
// """
|
|
488
|
+
```
|
|
295
489
|
|
|
296
|
-
|
|
490
|
+
Canonicalize document structure with `Doc#normalize` to merge adjacent text nodes and remove empty blocks:
|
|
297
491
|
|
|
298
492
|
```scala
|
|
493
|
+
import zio.blocks.chunk.Chunk
|
|
299
494
|
import zio.blocks.docs._
|
|
300
495
|
|
|
301
|
-
val
|
|
302
|
-
|
|
303
|
-
|
|
496
|
+
val doc = Doc(Chunk(
|
|
497
|
+
Paragraph(Chunk(Text("A"), Text("B"))), // Adjacent text nodes
|
|
498
|
+
Paragraph(Chunk()), // Empty paragraph
|
|
499
|
+
Paragraph(Chunk(Text("C")))
|
|
500
|
+
))
|
|
501
|
+
```
|
|
304
502
|
|
|
305
|
-
|
|
306
|
-
val list = md"""
|
|
307
|
-
# My List
|
|
503
|
+
Call `Doc#normalize` to see the result:
|
|
308
504
|
|
|
309
|
-
|
|
310
|
-
|
|
505
|
+
```scala
|
|
506
|
+
import zio.blocks.chunk.Chunk
|
|
507
|
+
import zio.blocks.docs._
|
|
508
|
+
|
|
509
|
+
val normalized = doc.normalize
|
|
510
|
+
// normalized: Doc = Doc(
|
|
511
|
+
// blocks = IndexedSeq(
|
|
512
|
+
// Paragraph(IndexedSeq(Text("AB"))),
|
|
513
|
+
// Paragraph(IndexedSeq(Text("C")))
|
|
514
|
+
// ),
|
|
515
|
+
// metadata = Map()
|
|
516
|
+
// )
|
|
311
517
|
```
|
|
312
518
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
- **Supports multi-line markdown** with triple quotes
|
|
519
|
+
### Equality and Hashing
|
|
520
|
+
|
|
521
|
+
Two documents are equal if their **normalized forms** are equal. This means:
|
|
317
522
|
|
|
318
|
-
**Example with validation:**
|
|
319
523
|
```scala
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
524
|
+
import zio.blocks.chunk.Chunk
|
|
525
|
+
import zio.blocks.docs._
|
|
526
|
+
|
|
527
|
+
val doc1 = Doc(Chunk(Paragraph(Chunk(Text("Hello"), Text(" "), Text("World")))))
|
|
528
|
+
val doc2 = Doc(Chunk(Paragraph(Chunk(Text("Hello World")))))
|
|
529
|
+
assert(doc1 == doc2) // Equal after normalization
|
|
323
530
|
```
|
|
324
531
|
|
|
325
|
-
|
|
532
|
+
Hash code computes from the normalized form for consistency with `equals`.
|
|
326
533
|
|
|
327
|
-
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Block (Sealed Trait)
|
|
537
|
+
|
|
538
|
+
A block-level markdown element:
|
|
328
539
|
|
|
329
540
|
```scala
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
541
|
+
import zio.blocks.chunk.Chunk
|
|
542
|
+
import zio.blocks.docs._
|
|
543
|
+
|
|
544
|
+
sealed trait Block extends Product with Serializable
|
|
333
545
|
```
|
|
334
546
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
547
|
+
Block is a sealed trait with the following concrete subtypes.
|
|
548
|
+
|
|
549
|
+
### Paragraph
|
|
550
|
+
|
|
551
|
+
A paragraph containing inline content:
|
|
340
552
|
|
|
341
|
-
**Custom instance example:**
|
|
342
553
|
```scala
|
|
343
|
-
|
|
554
|
+
import zio.blocks.chunk.Chunk
|
|
555
|
+
import zio.blocks.docs._
|
|
556
|
+
|
|
557
|
+
final case class Paragraph(content: Chunk[Inline]) extends Block
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Here's an example of creating a paragraph with mixed content:
|
|
344
561
|
|
|
345
|
-
|
|
346
|
-
|
|
562
|
+
```scala
|
|
563
|
+
import zio.blocks.chunk.Chunk
|
|
564
|
+
import zio.blocks.docs._
|
|
347
565
|
|
|
348
|
-
val
|
|
349
|
-
|
|
350
|
-
|
|
566
|
+
val para = Paragraph(Chunk(
|
|
567
|
+
Text("Hello "),
|
|
568
|
+
Strong(Chunk(Text("world")))
|
|
569
|
+
))
|
|
351
570
|
```
|
|
352
571
|
|
|
353
|
-
|
|
572
|
+
### Heading
|
|
573
|
+
|
|
574
|
+
An ATX-style heading (# to ######) with a level and inline content:
|
|
575
|
+
|
|
354
576
|
```scala
|
|
355
|
-
|
|
577
|
+
import zio.blocks.chunk.Chunk
|
|
578
|
+
import zio.blocks.docs._
|
|
356
579
|
|
|
357
|
-
|
|
358
|
-
|
|
580
|
+
final case class Heading(level: HeadingLevel, content: Chunk[Inline]) extends Block
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Here's an example of creating headings with different levels:
|
|
584
|
+
|
|
585
|
+
```scala
|
|
586
|
+
import zio.blocks.chunk.Chunk
|
|
587
|
+
import zio.blocks.docs._
|
|
359
588
|
|
|
360
|
-
val
|
|
361
|
-
val
|
|
589
|
+
val h1 = Heading(HeadingLevel.H1, Chunk(Text("Title")))
|
|
590
|
+
val h3 = Heading(HeadingLevel.H3, Chunk(Text("Subsection")))
|
|
362
591
|
```
|
|
363
592
|
|
|
364
|
-
|
|
593
|
+
### CodeBlock
|
|
365
594
|
|
|
366
|
-
|
|
595
|
+
A fenced code block with optional language/info string:
|
|
367
596
|
|
|
368
597
|
```scala
|
|
598
|
+
import zio.blocks.chunk.Chunk
|
|
369
599
|
import zio.blocks.docs._
|
|
600
|
+
|
|
601
|
+
final case class CodeBlock(info: Option[String], code: String) extends Block
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Here are examples of creating code blocks with and without language specification:
|
|
605
|
+
|
|
606
|
+
```scala
|
|
370
607
|
import zio.blocks.chunk.Chunk
|
|
608
|
+
import zio.blocks.docs._
|
|
371
609
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
Text(".")
|
|
378
|
-
)),
|
|
379
|
-
CodeBlock(Some("scala"), "val x = 42"),
|
|
380
|
-
BulletList(Chunk(
|
|
381
|
-
ListItem(Chunk(Paragraph(Chunk(Text("Item 1")))), None),
|
|
382
|
-
ListItem(Chunk(Paragraph(Chunk(Text("Done")))), Some(true)),
|
|
383
|
-
ListItem(Chunk(Paragraph(Chunk(Text("Todo")))), Some(false))
|
|
384
|
-
), tight = true)
|
|
385
|
-
))
|
|
610
|
+
// Scala code block
|
|
611
|
+
val scalaBlock = CodeBlock(Some("scala"), "val x = 42\nprintln(x)")
|
|
612
|
+
|
|
613
|
+
// No language specified
|
|
614
|
+
val plainBlock = CodeBlock(None, "some code")
|
|
386
615
|
```
|
|
387
616
|
|
|
388
|
-
###
|
|
617
|
+
### ThematicBreak
|
|
389
618
|
|
|
390
|
-
|
|
619
|
+
A thematic break (horizontal rule):
|
|
391
620
|
|
|
392
621
|
```scala
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
val footer = md"---\n*Footer*"
|
|
622
|
+
import zio.blocks.chunk.Chunk
|
|
623
|
+
import zio.blocks.docs._
|
|
396
624
|
|
|
397
|
-
|
|
625
|
+
case object ThematicBreak extends Block
|
|
398
626
|
```
|
|
399
627
|
|
|
400
|
-
|
|
628
|
+
Create a thematic break:
|
|
629
|
+
|
|
401
630
|
```scala
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
631
|
+
import zio.blocks.chunk.Chunk
|
|
632
|
+
import zio.blocks.docs._
|
|
633
|
+
|
|
634
|
+
val break = ThematicBreak
|
|
406
635
|
```
|
|
407
636
|
|
|
408
|
-
|
|
637
|
+
**Renders as:** `---\n` (or `***` or `___`)
|
|
638
|
+
|
|
639
|
+
### BlockQuote
|
|
409
640
|
|
|
410
|
-
|
|
411
|
-
- Merges adjacent `Text` nodes
|
|
412
|
-
- Removes empty paragraphs and other empty blocks
|
|
413
|
-
- Recursively normalizes nested structures (lists, block quotes, tables)
|
|
641
|
+
A block quote containing nested blocks:
|
|
414
642
|
|
|
415
643
|
```scala
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
Text("Hello "),
|
|
419
|
-
Text("World") // Adjacent Text nodes
|
|
420
|
-
)),
|
|
421
|
-
Paragraph(Chunk.empty) // Empty paragraph
|
|
422
|
-
))
|
|
644
|
+
import zio.blocks.chunk.Chunk
|
|
645
|
+
import zio.blocks.docs._
|
|
423
646
|
|
|
424
|
-
|
|
425
|
-
// Doc(Chunk(Paragraph(Chunk(Text("Hello World")))))
|
|
647
|
+
final case class BlockQuote(content: Chunk[Block]) extends Block
|
|
426
648
|
```
|
|
427
649
|
|
|
428
|
-
|
|
429
|
-
- Before comparing documents for equality (equality uses normalized form)
|
|
430
|
-
- After programmatic AST construction with potential duplicates
|
|
431
|
-
- When cleaning up parsed or generated content
|
|
650
|
+
Here's an example of creating a block quote:
|
|
432
651
|
|
|
433
|
-
|
|
652
|
+
```scala
|
|
653
|
+
import zio.blocks.chunk.Chunk
|
|
654
|
+
import zio.blocks.docs._
|
|
434
655
|
|
|
435
|
-
|
|
656
|
+
val quote = BlockQuote(Chunk(
|
|
657
|
+
Paragraph(Chunk(Text("This is a famous quote.")))
|
|
658
|
+
))
|
|
659
|
+
```
|
|
436
660
|
|
|
437
|
-
###
|
|
661
|
+
### BulletList
|
|
438
662
|
|
|
439
|
-
|
|
663
|
+
An unordered list with bullet markers (-, *, +):
|
|
440
664
|
|
|
441
665
|
```scala
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
case Heading(level, content) =>
|
|
445
|
-
s"${"=" * level.value} ${renderInlines(content)}\n"
|
|
446
|
-
case Paragraph(content) =>
|
|
447
|
-
renderInlines(content) + "\n\n"
|
|
448
|
-
case _ =>
|
|
449
|
-
Renderer.renderBlock(_)
|
|
450
|
-
}.mkString
|
|
451
|
-
}
|
|
452
|
-
```
|
|
666
|
+
import zio.blocks.chunk.Chunk
|
|
667
|
+
import zio.blocks.docs._
|
|
453
668
|
|
|
454
|
-
|
|
669
|
+
final case class BulletList(items: Chunk[ListItem], tight: Boolean) extends Block
|
|
670
|
+
```
|
|
455
671
|
|
|
456
|
-
|
|
672
|
+
The `tight` parameter controls spacing: `true` removes blank lines between items for compact rendering. Here's an example:
|
|
457
673
|
|
|
458
674
|
```scala
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
case Heading(level, content) =>
|
|
462
|
-
(level.value, Renderer.renderInlines(content))
|
|
463
|
-
}.toList
|
|
464
|
-
}
|
|
675
|
+
import zio.blocks.chunk.Chunk
|
|
676
|
+
import zio.blocks.docs._
|
|
465
677
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
case Strong(content) => findLinksInInlines(content)
|
|
471
|
-
case Emphasis(content) => findLinksInInlines(content)
|
|
472
|
-
case _ => Nil
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
doc.blocks.flatMap {
|
|
477
|
-
case Paragraph(content) => findLinksInInlines(content)
|
|
478
|
-
case Heading(_, content) => findLinksInInlines(content)
|
|
479
|
-
case _ => Nil
|
|
480
|
-
}.toList
|
|
481
|
-
}
|
|
678
|
+
val list = BulletList(Chunk(
|
|
679
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Item 1")))), None),
|
|
680
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Item 2")))), None)
|
|
681
|
+
), tight = true)
|
|
482
682
|
```
|
|
483
683
|
|
|
484
|
-
###
|
|
684
|
+
### OrderedList
|
|
485
685
|
|
|
486
|
-
|
|
686
|
+
An ordered list with numeric markers (1., 2., etc.):
|
|
487
687
|
|
|
488
688
|
```scala
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
case Text(value) => Text(value.toUpperCase)
|
|
494
|
-
case other => other
|
|
495
|
-
}
|
|
496
|
-
Heading(level, upperContent)
|
|
497
|
-
case other => other
|
|
498
|
-
}
|
|
499
|
-
Doc(transformedBlocks, doc.metadata)
|
|
500
|
-
}
|
|
689
|
+
import zio.blocks.chunk.Chunk
|
|
690
|
+
import zio.blocks.docs._
|
|
691
|
+
|
|
692
|
+
final case class OrderedList(start: Int, items: Chunk[ListItem], tight: Boolean) extends Block
|
|
501
693
|
```
|
|
502
694
|
|
|
503
|
-
|
|
695
|
+
The `start` parameter specifies the starting number (typically 1). Here's an example:
|
|
504
696
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
- Use the interpolator for static markdown (compile-time validation)
|
|
697
|
+
```scala
|
|
698
|
+
import zio.blocks.chunk.Chunk
|
|
699
|
+
import zio.blocks.docs._
|
|
509
700
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
701
|
+
val list = OrderedList(
|
|
702
|
+
start = 1,
|
|
703
|
+
items = Chunk(
|
|
704
|
+
ListItem(Chunk(Paragraph(Chunk(Text("First")))), None),
|
|
705
|
+
ListItem(Chunk(Paragraph(Chunk(Text("Second")))), None)
|
|
706
|
+
),
|
|
707
|
+
tight = true
|
|
708
|
+
)
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### ListItem
|
|
712
|
+
|
|
713
|
+
A list item, optionally a task list item:
|
|
714
|
+
|
|
715
|
+
```scala
|
|
716
|
+
import zio.blocks.chunk.Chunk
|
|
717
|
+
import zio.blocks.docs._
|
|
718
|
+
|
|
719
|
+
final case class ListItem(content: Chunk[Block], checked: Option[Boolean]) extends Block
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
The `checked` parameter: `Some(true)` renders as `[x]`, `Some(false)` renders as `[ ]`, `None` for regular list items. Here are examples of each type:
|
|
723
|
+
|
|
724
|
+
```scala
|
|
725
|
+
import zio.blocks.chunk.Chunk
|
|
726
|
+
import zio.blocks.docs._
|
|
727
|
+
|
|
728
|
+
// Regular list item
|
|
729
|
+
val item = ListItem(Chunk(Paragraph(Chunk(Text("Task")))), None)
|
|
730
|
+
|
|
731
|
+
// Completed task
|
|
732
|
+
val completed = ListItem(Chunk(Paragraph(Chunk(Text("Done")))), Some(true))
|
|
733
|
+
|
|
734
|
+
// Incomplete task
|
|
735
|
+
val incomplete = ListItem(Chunk(Paragraph(Chunk(Text("TODO")))), Some(false))
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### HtmlBlock
|
|
739
|
+
|
|
740
|
+
Raw HTML block content:
|
|
741
|
+
|
|
742
|
+
```scala
|
|
743
|
+
import zio.blocks.chunk.Chunk
|
|
744
|
+
import zio.blocks.docs._
|
|
745
|
+
|
|
746
|
+
final case class HtmlBlock(content: String) extends Block
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
Here's an example of creating an HTML block:
|
|
750
|
+
|
|
751
|
+
```scala
|
|
752
|
+
import zio.blocks.chunk.Chunk
|
|
753
|
+
import zio.blocks.docs._
|
|
754
|
+
|
|
755
|
+
val html = HtmlBlock("<div class='alert'>Custom HTML</div>")
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
### Table
|
|
759
|
+
|
|
760
|
+
A GitHub Flavored Markdown table with aligned columns:
|
|
761
|
+
|
|
762
|
+
```scala
|
|
763
|
+
import zio.blocks.chunk.Chunk
|
|
764
|
+
import zio.blocks.docs._
|
|
765
|
+
|
|
766
|
+
final case class Table(header: TableRow, alignments: Chunk[Alignment], rows: Chunk[TableRow]) extends Block
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
Here's an example of creating a table:
|
|
770
|
+
|
|
771
|
+
```scala
|
|
772
|
+
import zio.blocks.chunk.Chunk
|
|
773
|
+
import zio.blocks.docs._
|
|
774
|
+
|
|
775
|
+
val table = Table(
|
|
776
|
+
header = TableRow(Chunk(Chunk(Text("Name")), Chunk(Text("Age")))),
|
|
777
|
+
alignments = Chunk(Alignment.Left, Alignment.Right),
|
|
778
|
+
rows = Chunk(
|
|
779
|
+
TableRow(Chunk(Chunk(Text("Alice")), Chunk(Text("30")))),
|
|
780
|
+
TableRow(Chunk(Chunk(Text("Bob")), Chunk(Text("25"))))
|
|
781
|
+
)
|
|
782
|
+
)
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
---
|
|
786
|
+
|
|
787
|
+
## Inline (Sealed Trait)
|
|
788
|
+
|
|
789
|
+
An inline-level markdown element:
|
|
790
|
+
|
|
791
|
+
```scala
|
|
792
|
+
import zio.blocks.chunk.Chunk
|
|
793
|
+
import zio.blocks.docs._
|
|
794
|
+
|
|
795
|
+
sealed trait Inline extends Product with Serializable
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
Inline is a sealed trait with concrete subtypes, defining both object-level and top-level forms for API compatibility.
|
|
799
|
+
|
|
800
|
+
### Text
|
|
801
|
+
|
|
802
|
+
Plain text content:
|
|
803
|
+
|
|
804
|
+
```scala
|
|
805
|
+
import zio.blocks.chunk.Chunk
|
|
806
|
+
import zio.blocks.docs._
|
|
807
|
+
|
|
808
|
+
final case class Text(value: String) extends Inline
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
Here's an example of creating text:
|
|
812
|
+
|
|
813
|
+
```scala
|
|
814
|
+
import zio.blocks.chunk.Chunk
|
|
815
|
+
import zio.blocks.docs._
|
|
816
|
+
|
|
817
|
+
val text = Text("Hello world")
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Code
|
|
821
|
+
|
|
822
|
+
Inline code span (backtick-delimited):
|
|
823
|
+
|
|
824
|
+
```scala
|
|
825
|
+
import zio.blocks.chunk.Chunk
|
|
826
|
+
import zio.blocks.docs._
|
|
827
|
+
|
|
828
|
+
final case class Code(value: String) extends Inline
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
Here's an example of creating inline code:
|
|
832
|
+
|
|
833
|
+
```scala
|
|
834
|
+
import zio.blocks.chunk.Chunk
|
|
835
|
+
import zio.blocks.docs._
|
|
836
|
+
|
|
837
|
+
val code = Code("val x = 42")
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Emphasis
|
|
841
|
+
|
|
842
|
+
Emphasized (italic) text:
|
|
843
|
+
|
|
844
|
+
```scala
|
|
845
|
+
import zio.blocks.chunk.Chunk
|
|
846
|
+
import zio.blocks.docs._
|
|
847
|
+
|
|
848
|
+
final case class Emphasis(content: Chunk[Inline]) extends Inline
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
Here's an example of creating emphasized text:
|
|
852
|
+
|
|
853
|
+
```scala
|
|
854
|
+
import zio.blocks.chunk.Chunk
|
|
855
|
+
import zio.blocks.docs._
|
|
856
|
+
|
|
857
|
+
val emphasis = Emphasis(Chunk(Text("italic")))
|
|
858
|
+
// Renders as: *italic*
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### Strong
|
|
862
|
+
|
|
863
|
+
Strong (bold) text:
|
|
864
|
+
|
|
865
|
+
```scala
|
|
866
|
+
import zio.blocks.chunk.Chunk
|
|
867
|
+
import zio.blocks.docs._
|
|
868
|
+
|
|
869
|
+
final case class Strong(content: Chunk[Inline]) extends Inline
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
Here's an example of creating strong text:
|
|
873
|
+
|
|
874
|
+
```scala
|
|
875
|
+
import zio.blocks.chunk.Chunk
|
|
876
|
+
import zio.blocks.docs._
|
|
877
|
+
|
|
878
|
+
val strong = Strong(Chunk(Text("bold")))
|
|
879
|
+
// Renders as: **bold**
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### Strikethrough
|
|
883
|
+
|
|
884
|
+
Strikethrough text (GFM feature):
|
|
885
|
+
|
|
886
|
+
```scala
|
|
887
|
+
import zio.blocks.chunk.Chunk
|
|
888
|
+
import zio.blocks.docs._
|
|
889
|
+
|
|
890
|
+
final case class Strikethrough(content: Chunk[Inline]) extends Inline
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
Here's an example of creating strikethrough text:
|
|
894
|
+
|
|
895
|
+
```scala
|
|
896
|
+
import zio.blocks.chunk.Chunk
|
|
897
|
+
import zio.blocks.docs._
|
|
898
|
+
|
|
899
|
+
val struck = Strikethrough(Chunk(Text("deprecated")))
|
|
900
|
+
// Renders as: ~~deprecated~~
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
### Link
|
|
904
|
+
|
|
905
|
+
A hyperlink:
|
|
906
|
+
|
|
907
|
+
```scala
|
|
908
|
+
import zio.blocks.chunk.Chunk
|
|
909
|
+
import zio.blocks.docs._
|
|
910
|
+
|
|
911
|
+
final case class Link(text: Chunk[Inline], url: String, title: Option[String]) extends Inline
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
The `title` parameter is optional link title text. Here are examples of creating links:
|
|
915
|
+
|
|
916
|
+
```scala
|
|
917
|
+
import zio.blocks.chunk.Chunk
|
|
918
|
+
import zio.blocks.docs._
|
|
919
|
+
|
|
920
|
+
// Simple link
|
|
921
|
+
val link = Link(Chunk(Text("Click here")), "https://example.com", None)
|
|
922
|
+
|
|
923
|
+
// Link with title
|
|
924
|
+
val titled = Link(Chunk(Text("Docs")), "/docs", Some("Documentation"))
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
### Image
|
|
928
|
+
|
|
929
|
+
An image reference:
|
|
930
|
+
|
|
931
|
+
```scala
|
|
932
|
+
import zio.blocks.chunk.Chunk
|
|
933
|
+
import zio.blocks.docs._
|
|
934
|
+
|
|
935
|
+
final case class Image(alt: String, url: String, title: Option[String]) extends Inline
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
Here are examples of creating images:
|
|
939
|
+
|
|
940
|
+
```scala
|
|
941
|
+
import zio.blocks.chunk.Chunk
|
|
942
|
+
import zio.blocks.docs._
|
|
943
|
+
|
|
944
|
+
val img = Image(alt = "Logo", url = "/logo.png", None)
|
|
945
|
+
val imgWithTitle = Image(alt = "Icon", url = "/icon.svg", Some("App Icon"))
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
### HtmlInline
|
|
949
|
+
|
|
950
|
+
Raw HTML inline content:
|
|
951
|
+
|
|
952
|
+
```scala
|
|
953
|
+
import zio.blocks.chunk.Chunk
|
|
954
|
+
import zio.blocks.docs._
|
|
955
|
+
|
|
956
|
+
final case class HtmlInline(content: String) extends Inline
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
Here's an example of creating HTML inline content:
|
|
960
|
+
|
|
961
|
+
```scala
|
|
962
|
+
import zio.blocks.chunk.Chunk
|
|
963
|
+
import zio.blocks.docs._
|
|
964
|
+
|
|
965
|
+
val html = HtmlInline("<span class='highlight'>custom</span>")
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### SoftBreak
|
|
969
|
+
|
|
970
|
+
A soft line break (single newline, rendered as space or newline depending on context):
|
|
971
|
+
|
|
972
|
+
```scala
|
|
973
|
+
import zio.blocks.chunk.Chunk
|
|
974
|
+
import zio.blocks.docs._
|
|
975
|
+
|
|
976
|
+
case object SoftBreak extends Inline
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
### HardBreak
|
|
980
|
+
|
|
981
|
+
A hard line break (two spaces or backslash before newline):
|
|
982
|
+
|
|
983
|
+
```scala
|
|
984
|
+
import zio.blocks.chunk.Chunk
|
|
985
|
+
import zio.blocks.docs._
|
|
986
|
+
|
|
987
|
+
case object HardBreak extends Inline
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
### Autolink
|
|
991
|
+
|
|
992
|
+
An autolink (URL or email in angle brackets):
|
|
993
|
+
|
|
994
|
+
```scala
|
|
995
|
+
import zio.blocks.chunk.Chunk
|
|
996
|
+
import zio.blocks.docs._
|
|
997
|
+
|
|
998
|
+
final case class Autolink(url: String, isEmail: Boolean) extends Inline
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
Here are examples of creating autolinks:
|
|
1002
|
+
|
|
1003
|
+
```scala
|
|
1004
|
+
import zio.blocks.chunk.Chunk
|
|
1005
|
+
import zio.blocks.docs._
|
|
1006
|
+
|
|
1007
|
+
val urlLink = Autolink("https://example.com", isEmail = false)
|
|
1008
|
+
val emailLink = Autolink("user@example.com", isEmail = true)
|
|
1009
|
+
// Renders as: <https://example.com> and <user@example.com>
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
---
|
|
1013
|
+
|
|
1014
|
+
## HeadingLevel
|
|
1015
|
+
|
|
1016
|
+
Heading levels from H1 to H6:
|
|
1017
|
+
|
|
1018
|
+
```scala
|
|
1019
|
+
import zio.blocks.chunk.Chunk
|
|
1020
|
+
import zio.blocks.docs._
|
|
1021
|
+
|
|
1022
|
+
sealed abstract class HeadingLevel(val value: Int) extends Product with Serializable
|
|
1023
|
+
|
|
1024
|
+
object HeadingLevel {
|
|
1025
|
+
case object H1 extends HeadingLevel(1)
|
|
1026
|
+
case object H2 extends HeadingLevel(2)
|
|
1027
|
+
case object H3 extends HeadingLevel(3)
|
|
1028
|
+
case object H4 extends HeadingLevel(4)
|
|
1029
|
+
case object H5 extends HeadingLevel(5)
|
|
1030
|
+
case object H6 extends HeadingLevel(6)
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
### Safe Construction
|
|
1035
|
+
|
|
1036
|
+
Construct a heading level from an integer, returning an `Option`:
|
|
1037
|
+
|
|
1038
|
+
```scala
|
|
1039
|
+
import zio.blocks.chunk.Chunk
|
|
1040
|
+
import zio.blocks.docs._
|
|
1041
|
+
|
|
1042
|
+
HeadingLevel.fromInt(2) == Some(HeadingLevel.H2)
|
|
1043
|
+
HeadingLevel.fromInt(7) == None // Out of range
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
Construct a heading level unsafely, throwing if input is invalid:
|
|
1047
|
+
|
|
1048
|
+
```scala
|
|
1049
|
+
import zio.blocks.chunk.Chunk
|
|
1050
|
+
import zio.blocks.docs._
|
|
1051
|
+
|
|
1052
|
+
HeadingLevel.unsafeFromInt(3) // HeadingLevel.H3
|
|
1053
|
+
HeadingLevel.unsafeFromInt(7) // Throws IllegalArgumentException
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
### Accessing Value
|
|
1057
|
+
|
|
1058
|
+
Access the numeric value of a heading level:
|
|
1059
|
+
|
|
1060
|
+
```scala
|
|
1061
|
+
import zio.blocks.chunk.Chunk
|
|
1062
|
+
import zio.blocks.docs._
|
|
1063
|
+
|
|
1064
|
+
HeadingLevel.H1.value == 1
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
---
|
|
1068
|
+
|
|
1069
|
+
## Alignment
|
|
1070
|
+
|
|
1071
|
+
Table column alignment specification:
|
|
1072
|
+
|
|
1073
|
+
```scala
|
|
1074
|
+
import zio.blocks.chunk.Chunk
|
|
1075
|
+
import zio.blocks.docs._
|
|
1076
|
+
|
|
1077
|
+
sealed trait Alignment extends Product with Serializable
|
|
1078
|
+
|
|
1079
|
+
object Alignment {
|
|
1080
|
+
case object Left extends Alignment
|
|
1081
|
+
case object Right extends Alignment
|
|
1082
|
+
case object Center extends Alignment
|
|
1083
|
+
case object None extends Alignment
|
|
1084
|
+
}
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
### Predefined Alignments
|
|
1088
|
+
|
|
1089
|
+
Use predefined alignment values for table columns:
|
|
1090
|
+
|
|
1091
|
+
```scala
|
|
1092
|
+
import zio.blocks.chunk.Chunk
|
|
1093
|
+
import zio.blocks.docs._
|
|
1094
|
+
|
|
1095
|
+
Alignment.Left // Renders as :---
|
|
1096
|
+
Alignment.Right // Renders as ---:
|
|
1097
|
+
Alignment.Center // Renders as :---:
|
|
1098
|
+
Alignment.None // Renders as ---
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
### Use in Tables
|
|
1102
|
+
|
|
1103
|
+
Create a table with specific column alignments:
|
|
1104
|
+
|
|
1105
|
+
```scala
|
|
1106
|
+
import zio.blocks.chunk.Chunk
|
|
1107
|
+
import zio.blocks.docs._
|
|
1108
|
+
val header = TableRow(Chunk(
|
|
1109
|
+
Chunk(Text("Name")),
|
|
1110
|
+
Chunk(Text("Age")),
|
|
1111
|
+
Chunk(Text("City"))
|
|
1112
|
+
))
|
|
1113
|
+
val rows = Chunk(
|
|
1114
|
+
TableRow(Chunk(
|
|
1115
|
+
Chunk(Text("Alice")),
|
|
1116
|
+
Chunk(Text("30")),
|
|
1117
|
+
Chunk(Text("NYC"))
|
|
1118
|
+
)),
|
|
1119
|
+
TableRow(Chunk(
|
|
1120
|
+
Chunk(Text("Bob")),
|
|
1121
|
+
Chunk(Text("25")),
|
|
1122
|
+
Chunk(Text("LA"))
|
|
1123
|
+
))
|
|
1124
|
+
)
|
|
1125
|
+
val alignments = Chunk(Alignment.Left, Alignment.Center, Alignment.Right)
|
|
1126
|
+
val table = Table(header, alignments, rows)
|
|
1127
|
+
val doc = Doc(Chunk(table))
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
If we render this document, the table will have left-aligned "Name", center-aligned "Age", and right-aligned "City":
|
|
1131
|
+
|
|
1132
|
+
```scala
|
|
1133
|
+
import zio.blocks.chunk.Chunk
|
|
1134
|
+
import zio.blocks.docs._
|
|
1135
|
+
|
|
1136
|
+
Renderer.render(doc)
|
|
1137
|
+
// res45: String = """| Name | Age | City |
|
|
1138
|
+
// |:---|:--:|---:|
|
|
1139
|
+
// | Alice | 30 | NYC |
|
|
1140
|
+
// | Bob | 25 | LA |
|
|
1141
|
+
// """
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
---
|
|
1145
|
+
|
|
1146
|
+
## TableRow
|
|
1147
|
+
|
|
1148
|
+
A single row in a table, containing cells as inline content:
|
|
1149
|
+
|
|
1150
|
+
```scala
|
|
1151
|
+
import zio.blocks.chunk.Chunk
|
|
1152
|
+
import zio.blocks.docs._
|
|
1153
|
+
|
|
1154
|
+
final case class TableRow(cells: Chunk[Chunk[Inline]])
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
Each cell is a `Chunk[Inline]`, allowing formatted content (text, links, emphasis, etc.). Here's an example:
|
|
1158
|
+
|
|
1159
|
+
```scala
|
|
1160
|
+
import zio.blocks.chunk.Chunk
|
|
1161
|
+
import zio.blocks.docs._
|
|
1162
|
+
|
|
1163
|
+
val row = TableRow(Chunk(
|
|
1164
|
+
Chunk(Text("Alice")),
|
|
1165
|
+
Chunk(Strong(Chunk(Text("30")))),
|
|
1166
|
+
Chunk(Link(Chunk(Text("NYC")), "/cities/nyc", None))
|
|
1167
|
+
))
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
---
|
|
1171
|
+
|
|
1172
|
+
## Parser
|
|
1173
|
+
|
|
1174
|
+
A singleton object providing strict GitHub Flavored Markdown parsing with position-aware error reporting.
|
|
1175
|
+
|
|
1176
|
+
### Parsing
|
|
1177
|
+
|
|
1178
|
+
Use the main entry point to parse markdown:
|
|
1179
|
+
|
|
1180
|
+
```scala
|
|
1181
|
+
import zio.blocks.chunk.Chunk
|
|
1182
|
+
import zio.blocks.docs._
|
|
1183
|
+
|
|
1184
|
+
Parser.parse(input: String): Either[ParseError, Doc]
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
Returns `Right(doc)` on success or `Left(error)` on parse failure. Here's an example:
|
|
1188
|
+
|
|
1189
|
+
```scala
|
|
1190
|
+
import zio.blocks.chunk.Chunk
|
|
1191
|
+
import zio.blocks.docs._
|
|
1192
|
+
|
|
1193
|
+
val result = Parser.parse("# Hello\n\nWorld")
|
|
1194
|
+
result match {
|
|
1195
|
+
case Right(doc) => println(s"Parsed ${doc.blocks.size} blocks")
|
|
1196
|
+
case Left(err) => println(s"Parse error at line ${err.line}: ${err.message}")
|
|
1197
|
+
}
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
### Supported Features
|
|
1201
|
+
|
|
1202
|
+
- ATX headings (# to ######)
|
|
1203
|
+
- Fenced code blocks (``` or ~~~)
|
|
1204
|
+
- Thematic breaks (---, ***, ___)
|
|
1205
|
+
- Block quotes (> prefix)
|
|
1206
|
+
- Bullet lists (-, *, +)
|
|
1207
|
+
- Ordered lists (1., 2., etc.)
|
|
1208
|
+
- Task lists (- [ ] and - [x])
|
|
1209
|
+
- Tables with alignment (GFM)
|
|
1210
|
+
- Inline formatting (emphasis, strong, strikethrough)
|
|
1211
|
+
- Links and images with optional titles
|
|
1212
|
+
- Autolinks (`<url>` or `<email>`)
|
|
1213
|
+
- HTML blocks and inline HTML
|
|
1214
|
+
- Soft and hard line breaks
|
|
1215
|
+
|
|
1216
|
+
### Unsupported Features
|
|
1217
|
+
|
|
1218
|
+
- YAML frontmatter (causes parse error)
|
|
1219
|
+
- Setext headings (use ATX style with # instead)
|
|
1220
|
+
- Indented code blocks (use fenced with ``` instead)
|
|
1221
|
+
- Link reference definitions
|
|
1222
|
+
|
|
1223
|
+
---
|
|
1224
|
+
|
|
1225
|
+
## ParseError
|
|
1226
|
+
|
|
1227
|
+
Error information from parsing, with precise location data:
|
|
1228
|
+
|
|
1229
|
+
```scala
|
|
1230
|
+
import zio.blocks.chunk.Chunk
|
|
1231
|
+
import zio.blocks.docs._
|
|
1232
|
+
|
|
1233
|
+
final case class ParseError(
|
|
1234
|
+
message: String, // Human-readable error description
|
|
1235
|
+
line: Int, // 1-based line number
|
|
1236
|
+
column: Int, // 1-based column number
|
|
1237
|
+
input: String // The line that caused the error
|
|
1238
|
+
)
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
Here's an example of creating a `ParseError`:
|
|
1242
|
+
|
|
1243
|
+
```scala
|
|
1244
|
+
import zio.blocks.chunk.Chunk
|
|
1245
|
+
import zio.blocks.docs._
|
|
1246
|
+
|
|
1247
|
+
val err = ParseError("Unexpected token", line = 5, column = 12, input = "[invalid markdown]")
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
Get the string representation of the error:
|
|
1251
|
+
|
|
1252
|
+
```scala
|
|
1253
|
+
import zio.blocks.chunk.Chunk
|
|
1254
|
+
import zio.blocks.docs._
|
|
1255
|
+
|
|
1256
|
+
err.toString
|
|
1257
|
+
// res48: String = """ParseError at line 5, column 12: Unexpected token
|
|
1258
|
+
// [invalid markdown]"""
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
---
|
|
1262
|
+
|
|
1263
|
+
## Renderer
|
|
1264
|
+
|
|
1265
|
+
A singleton object that renders markdown documents back to GitHub Flavored Markdown string format:
|
|
1266
|
+
|
|
1267
|
+
```scala
|
|
1268
|
+
import zio.blocks.chunk.Chunk
|
|
1269
|
+
import zio.blocks.docs._
|
|
1270
|
+
|
|
1271
|
+
object Renderer
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
### Rendering
|
|
1275
|
+
|
|
1276
|
+
Render an entire document to GFM markdown:
|
|
1277
|
+
|
|
1278
|
+
```scala
|
|
1279
|
+
import zio.blocks.chunk.Chunk
|
|
1280
|
+
import zio.blocks.docs._
|
|
1281
|
+
|
|
1282
|
+
Renderer.render(doc: Doc): String
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
Here's an example of rendering a document:
|
|
1286
|
+
|
|
1287
|
+
```scala
|
|
1288
|
+
import zio.blocks.chunk.Chunk
|
|
1289
|
+
import zio.blocks.docs._
|
|
1290
|
+
|
|
1291
|
+
val doc = Doc(Chunk(
|
|
1292
|
+
Heading(HeadingLevel.H1, Chunk(Text("Title"))),
|
|
1293
|
+
Paragraph(Chunk(Text("Content")))
|
|
1294
|
+
))
|
|
1295
|
+
val rendered = Renderer.render(doc)
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
The result:
|
|
1299
|
+
|
|
1300
|
+
```scala
|
|
1301
|
+
import zio.blocks.chunk.Chunk
|
|
1302
|
+
import zio.blocks.docs._
|
|
1303
|
+
|
|
1304
|
+
rendered
|
|
1305
|
+
// res50: String = """# Title
|
|
1306
|
+
// Content
|
|
1307
|
+
//
|
|
1308
|
+
// """
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
Render individual blocks:
|
|
1312
|
+
|
|
1313
|
+
```scala
|
|
1314
|
+
import zio.blocks.chunk.Chunk
|
|
1315
|
+
import zio.blocks.docs._
|
|
1316
|
+
|
|
1317
|
+
Renderer.renderBlock(block: Block): String
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
Render inline content:
|
|
1321
|
+
|
|
1322
|
+
```scala
|
|
1323
|
+
import zio.blocks.chunk.Chunk
|
|
1324
|
+
import zio.blocks.docs._
|
|
1325
|
+
|
|
1326
|
+
Renderer.renderInlines(inlines: Chunk[Inline]): String
|
|
1327
|
+
Renderer.renderInline(inline: Inline): String
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
### Normalization During Rendering
|
|
1331
|
+
|
|
1332
|
+
The renderer does not normalize; use `doc.normalize` before rendering if normalization is needed.
|
|
1333
|
+
|
|
1334
|
+
### Round-Trip Semantics
|
|
1335
|
+
|
|
1336
|
+
Re-parse rendered output to verify round-trip semantics:
|
|
1337
|
+
|
|
1338
|
+
```scala
|
|
1339
|
+
import zio.blocks.chunk.Chunk
|
|
1340
|
+
import zio.blocks.docs._
|
|
1341
|
+
|
|
1342
|
+
val original = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Title")))))
|
|
1343
|
+
val markdown = Renderer.render(original)
|
|
1344
|
+
val reparsed = Parser.parse(markdown).toOption.get
|
|
1345
|
+
assert(original == reparsed) // Equal after normalization
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
---
|
|
1349
|
+
|
|
1350
|
+
## HtmlRenderer
|
|
1351
|
+
|
|
1352
|
+
A singleton object that renders markdown documents to HTML5:
|
|
1353
|
+
|
|
1354
|
+
```scala
|
|
1355
|
+
import zio.blocks.chunk.Chunk
|
|
1356
|
+
import zio.blocks.docs._
|
|
1357
|
+
|
|
1358
|
+
object HtmlRenderer
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
### Full Document Rendering
|
|
1362
|
+
|
|
1363
|
+
Render a document as complete HTML with DOCTYPE and html wrapper tags:
|
|
1364
|
+
|
|
1365
|
+
```scala
|
|
1366
|
+
import zio.blocks.chunk.Chunk
|
|
1367
|
+
import zio.blocks.docs._
|
|
1368
|
+
|
|
1369
|
+
HtmlRenderer.render(doc: Doc): String
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
Here's an example of rendering a complete HTML document:
|
|
1373
|
+
|
|
1374
|
+
```scala
|
|
1375
|
+
import zio.blocks.chunk.Chunk
|
|
1376
|
+
import zio.blocks.docs._
|
|
1377
|
+
|
|
1378
|
+
val doc = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Title")))))
|
|
1379
|
+
val html = HtmlRenderer.render(doc)
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
The result:
|
|
1383
|
+
|
|
1384
|
+
```scala
|
|
1385
|
+
import zio.blocks.chunk.Chunk
|
|
1386
|
+
import zio.blocks.docs._
|
|
1387
|
+
|
|
1388
|
+
html
|
|
1389
|
+
// res54: String = "<!DOCTYPE html><html><head></head><body><h1>Title</h1></body></html>"
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
### Fragment Rendering
|
|
1393
|
+
|
|
1394
|
+
Render content-only HTML without wrapper tags:
|
|
1395
|
+
|
|
1396
|
+
```scala
|
|
1397
|
+
import zio.blocks.chunk.Chunk
|
|
1398
|
+
import zio.blocks.docs._
|
|
1399
|
+
|
|
1400
|
+
HtmlRenderer.renderFragment(doc: Doc): String
|
|
1401
|
+
```
|
|
1402
|
+
|
|
1403
|
+
Here's an example of rendering an HTML fragment:
|
|
1404
|
+
|
|
1405
|
+
```scala
|
|
1406
|
+
import zio.blocks.chunk.Chunk
|
|
1407
|
+
import zio.blocks.docs._
|
|
1408
|
+
|
|
1409
|
+
val fragment = HtmlRenderer.renderFragment(doc)
|
|
1410
|
+
```
|
|
1411
|
+
|
|
1412
|
+
Use fragments to embed markdown content into existing HTML templates.
|
|
1413
|
+
|
|
1414
|
+
### HTML Feature Mapping
|
|
1415
|
+
|
|
1416
|
+
- Headings → `<h1>` through `<h6>`
|
|
1417
|
+
- Paragraphs → `<p>`
|
|
1418
|
+
- Code blocks → `<pre><code class="language-...">` (language from info string)
|
|
1419
|
+
- Thematic breaks → `<hr>`
|
|
1420
|
+
- Block quotes → `<blockquote>`
|
|
1421
|
+
- Lists → `<ul>` and `<ol>` with `<li>`
|
|
1422
|
+
- Task lists → `<li>` with checkbox-like rendering
|
|
1423
|
+
- Tables → `<table>`, `<tr>`, `<td>` with alignment classes
|
|
1424
|
+
- Inline code → `<code>`
|
|
1425
|
+
- Emphasis → `<em>`
|
|
1426
|
+
- Strong → `<strong>`
|
|
1427
|
+
- Strikethrough → `<del>` or `<s>`
|
|
1428
|
+
- Links → `<a href="...">`
|
|
1429
|
+
- Images → `<img alt="..." src="...">`
|
|
1430
|
+
- HTML blocks and inlines → pass through as-is
|
|
1431
|
+
|
|
1432
|
+
---
|
|
1433
|
+
|
|
1434
|
+
## TerminalRenderer
|
|
1435
|
+
|
|
1436
|
+
A singleton object that renders markdown to ANSI-colored terminal output optimized for console display:
|
|
1437
|
+
|
|
1438
|
+
```scala
|
|
1439
|
+
import zio.blocks.chunk.Chunk
|
|
1440
|
+
import zio.blocks.docs._
|
|
1441
|
+
|
|
1442
|
+
object TerminalRenderer
|
|
1443
|
+
```
|
|
514
1444
|
|
|
515
1445
|
### Rendering
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1446
|
+
|
|
1447
|
+
Render a document to ANSI-colored terminal output:
|
|
1448
|
+
```scala
|
|
1449
|
+
import zio.blocks.chunk.Chunk
|
|
1450
|
+
import zio.blocks.docs._
|
|
1451
|
+
|
|
1452
|
+
TerminalRenderer.render(doc: Doc): String
|
|
1453
|
+
```
|
|
1454
|
+
|
|
1455
|
+
Here's an example of rendering to terminal:
|
|
1456
|
+
```scala
|
|
1457
|
+
import zio.blocks.chunk.Chunk
|
|
1458
|
+
import zio.blocks.docs._
|
|
1459
|
+
|
|
1460
|
+
val doc = Doc(Chunk(Heading(HeadingLevel.H1, Chunk(Text("Title")))))
|
|
1461
|
+
val terminal = TerminalRenderer.render(doc)
|
|
1462
|
+
// Returns ANSI-colored string: [31m[1mTitle[0m\n\n
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
### Color Scheme
|
|
1466
|
+
|
|
1467
|
+
Headings use different colors by level:
|
|
1468
|
+
- H1 → Red
|
|
1469
|
+
- H2 → Yellow
|
|
1470
|
+
- H3 → Green
|
|
1471
|
+
- H4 → Cyan
|
|
1472
|
+
- H5 → Blue
|
|
1473
|
+
- H6 → Magenta
|
|
1474
|
+
|
|
1475
|
+
Inline elements use ANSI styles:
|
|
1476
|
+
- Strong/Bold → ANSI bold
|
|
1477
|
+
- Emphasis/Italic → ANSI italic
|
|
1478
|
+
- Code → Gray background
|
|
1479
|
+
- Links → Underlined
|
|
1480
|
+
- Strikethrough → ANSI strikethrough style
|
|
1481
|
+
|
|
1482
|
+
The output is designed to be readable on both light and dark terminal backgrounds.
|
|
1483
|
+
|
|
1484
|
+
---
|
|
1485
|
+
|
|
1486
|
+
## ToMarkdown
|
|
1487
|
+
|
|
1488
|
+
Type class for converting Scala values to markdown inline elements, enabling interpolation in the `md"..."` string interpolator.
|
|
1489
|
+
|
|
1490
|
+
### Definition
|
|
1491
|
+
|
|
1492
|
+
Define the `ToMarkdown` type class:
|
|
1493
|
+
|
|
1494
|
+
```scala
|
|
1495
|
+
import zio.blocks.chunk.Chunk
|
|
1496
|
+
import zio.blocks.docs._
|
|
1497
|
+
|
|
1498
|
+
trait ToMarkdown[-A] {
|
|
1499
|
+
def toMarkdown(a: A): Inline
|
|
1500
|
+
}
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
The type parameter is contravariant (`-A`), allowing subtypes to use supertype instances.
|
|
1504
|
+
|
|
1505
|
+
### Summon an Instance
|
|
1506
|
+
|
|
1507
|
+
Summon a ToMarkdown instance:
|
|
1508
|
+
|
|
1509
|
+
```scala
|
|
1510
|
+
import zio.blocks.chunk.Chunk
|
|
1511
|
+
import zio.blocks.docs._
|
|
1512
|
+
|
|
1513
|
+
ToMarkdown[String] // Implicitly summons the ToMarkdown[String] instance
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
### Built-In Instances
|
|
1517
|
+
|
|
1518
|
+
Convert primitive types to plain text:
|
|
1519
|
+
|
|
1520
|
+
```scala
|
|
1521
|
+
import zio.blocks.chunk.Chunk
|
|
1522
|
+
import zio.blocks.docs._
|
|
1523
|
+
|
|
1524
|
+
ToMarkdown[String] // a: String => Text(a)
|
|
1525
|
+
ToMarkdown[Int] // a: Int => Text(a.toString)
|
|
1526
|
+
ToMarkdown[Long] // a: Long => Text(a.toString)
|
|
1527
|
+
ToMarkdown[Double] // a: Double => Text(a.toString)
|
|
1528
|
+
ToMarkdown[Boolean] // a: Boolean => Text(a.toString)
|
|
1529
|
+
```
|
|
1530
|
+
|
|
1531
|
+
Pass inline elements through unchanged:
|
|
1532
|
+
|
|
1533
|
+
```scala
|
|
1534
|
+
import zio.blocks.chunk.Chunk
|
|
1535
|
+
import zio.blocks.docs._
|
|
1536
|
+
|
|
1537
|
+
ToMarkdown[Inline] // a: Inline => a (identity)
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
Convert collections to comma-separated text:
|
|
1541
|
+
|
|
1542
|
+
```scala
|
|
1543
|
+
import zio.blocks.chunk.Chunk
|
|
1544
|
+
import zio.blocks.docs._
|
|
1545
|
+
|
|
1546
|
+
ToMarkdown[List[A]] // as: List[A] => Text(as.map(...).mkString(", "))
|
|
1547
|
+
ToMarkdown[Chunk[A]] // as: Chunk[A] => Text(as.map(...).mkString(", "))
|
|
1548
|
+
ToMarkdown[Vector[A]] // as: Vector[A] => Text(as.map(...).mkString(", "))
|
|
1549
|
+
ToMarkdown[Seq[A]] // as: Seq[A] => Text(as.map(...).mkString(", "))
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
For collection instances, each element is converted using its `ToMarkdown[A]` instance, then joined with ", ".
|
|
1553
|
+
|
|
1554
|
+
Render blocks to markdown text:
|
|
1555
|
+
|
|
1556
|
+
```scala
|
|
1557
|
+
import zio.blocks.chunk.Chunk
|
|
1558
|
+
import zio.blocks.docs._
|
|
1559
|
+
|
|
1560
|
+
ToMarkdown[Block] // b: Block => Text(Renderer.render(Doc(Chunk(b))).trim)
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
### Custom Implementations
|
|
1564
|
+
|
|
1565
|
+
Define custom `ToMarkdown` instances for your types:
|
|
1566
|
+
|
|
1567
|
+
```scala
|
|
1568
|
+
import zio.blocks.chunk.Chunk
|
|
1569
|
+
import zio.blocks.docs._
|
|
1570
|
+
|
|
1571
|
+
case class Person(name: String, age: Int)
|
|
1572
|
+
|
|
1573
|
+
implicit val personToMarkdown: ToMarkdown[Person] = { person =>
|
|
1574
|
+
Strong(Chunk(Text(person.name)))
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
val p = Person("Alice", 30)
|
|
1578
|
+
val doc = md"## User: $p" // Interpolates as strong text "Alice"
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
---
|
|
1582
|
+
|
|
1583
|
+
## String Interpolator (`md"..."`)
|
|
1584
|
+
|
|
1585
|
+
The `md` string interpolator validates markdown at compile time and supports runtime interpolation of values.
|
|
1586
|
+
|
|
1587
|
+
### Compile-Time Validation
|
|
1588
|
+
|
|
1589
|
+
Markdown syntax inside `md"..."` is validated at compile time:
|
|
1590
|
+
|
|
1591
|
+
```scala
|
|
1592
|
+
import zio.blocks.chunk.Chunk
|
|
1593
|
+
import zio.blocks.docs._
|
|
1594
|
+
|
|
1595
|
+
val doc = md"# Valid heading" // Compiles
|
|
1596
|
+
|
|
1597
|
+
val invalid = md"# [unclosed link](" // Compile error: Invalid markdown
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
### Runtime Interpolation
|
|
1601
|
+
|
|
1602
|
+
Values are interpolated and converted to inline markdown using `ToMarkdown`:
|
|
1603
|
+
|
|
1604
|
+
```scala
|
|
1605
|
+
import zio.blocks.chunk.Chunk
|
|
1606
|
+
import zio.blocks.docs._
|
|
1607
|
+
|
|
1608
|
+
val name = "Alice"
|
|
1609
|
+
val count = 42
|
|
1610
|
+
val doc = md"""
|
|
1611
|
+
# Welcome $name
|
|
1612
|
+
|
|
1613
|
+
You have $count items.
|
|
1614
|
+
"""
|
|
1615
|
+
// Equivalent to parsing: "# Welcome Alice\n\nYou have 42 items."
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
### Custom Type Interpolation
|
|
1619
|
+
|
|
1620
|
+
Any type with a `ToMarkdown` instance can be interpolated:
|
|
1621
|
+
|
|
1622
|
+
```scala
|
|
1623
|
+
import zio.blocks.chunk.Chunk
|
|
1624
|
+
import zio.blocks.docs._
|
|
1625
|
+
|
|
1626
|
+
case class Tag(label: String)
|
|
1627
|
+
implicit val tagToMarkdown: ToMarkdown[Tag] = tag =>
|
|
1628
|
+
Code(tag.label)
|
|
1629
|
+
|
|
1630
|
+
val tag = Tag("important")
|
|
1631
|
+
val doc = md"Please review: $tag" // Renders tag as inline code
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
### Interpolation Mechanics
|
|
1635
|
+
|
|
1636
|
+
The interpolator:
|
|
1637
|
+
1. Collects string parts and interpolated values (as `Inline` elements via `ToMarkdown`)
|
|
1638
|
+
2. Combines parts and rendered inlines into a markdown string
|
|
1639
|
+
3. Parses the combined string at runtime
|
|
1640
|
+
4. Returns a `Doc` or throws `IllegalArgumentException` if parsing fails
|