-
Notifications
You must be signed in to change notification settings - Fork 44
/
containers.scala
167 lines (132 loc) · 5.89 KB
/
containers.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package laika.ast
import laika.api.Renderer
import laika.format.AST
/** A generic container.
* Usually not mixed in directly, instead one of the sub-traits
* `TextContainer`, `ListContainer`, `SpanContainer` or `BlockContainer` should be used.
*/
trait Container[+T] extends Element {
def content: T
}
/** A container for plain text.
*/
trait TextContainer extends Container[String]
/** A generic container of other elements.
* Provides means to traverse, select and rewrite children of
* this container.
*
* Usually not mixed in directly, instead one of the sub-traits
* `ListContainer`, `SpanContainer` or `BlockContainer` should be used.
*/
trait ElementContainer[+E <: Element] extends Container[Seq[E]] with ElementTraversal {
override def toString: String = FormattedElementString.render(this)
}
/** A generic container of child elements which can have
* rewrite rules applied to them in recursive tree rewriting.
*/
trait RewritableContainer extends Element {
type Self <: RewritableContainer
/** Rewrites all children of this container based on the specified rules.
*
* Concrete types are expected to support rewriting at least for all standard block, span and template span
* elements they contain, plus optionally for any other elements that have custom support for rewriting.
*/
def rewriteChildren(rules: RewriteRules): Self
/** Rewrites all span children of this container based on the specified rules.
*
* Concrete types are expected to support rewriting at least for all standard block, span and template span
* elements they contain, plus optionally for any other elements that have custom support for rewriting.
*/
def rewriteSpans(rule: RewriteRule[Span]): Self = rewriteChildren(
RewriteRules.forSpans(rule)
)
}
/** A container of other Block elements. Such a container is usually
* also a Block itself.
*/
trait BlockContainer extends ElementContainer[Block] with RewritableContainer {
type Self <: BlockContainer
/** Rewrites all block children of this container based on the specified rules.
*
* Concrete types are expected to support rewriting at least for all standard block, span and template span
* elements they contain, plus optionally for any other elements that have custom support for rewriting.
*/
def rewriteBlocks(rules: RewriteRule[Block]): Self = rewriteChildren(
RewriteRules.forBlocks(rules)
)
def rewriteChildren(rules: RewriteRules): Self = withContent(rules.rewriteBlocks(content))
/** Creates a copy of this instance with the specified new content.
*
* Implementation note: This method exists to deal with the fact that there is no polymorphic copy method
* and trades a small bit of boilerplate for avoiding the compile time hit of using shapeless for this.
*/
def withContent(newContent: Seq[Block]): Self
}
/** A container of other Span elements. Such a container may be a Block
* or a Span itself.
*/
trait SpanContainer extends ElementContainer[Span] with RewritableContainer {
type Self <: SpanContainer
def rewriteChildren(rules: RewriteRules): Self = withContent(rules.rewriteSpans(content))
/** Creates a copy of this instance with the specified new content.
*
* Implementation note: This method exists to deal with the fact that there is no polymorphic copy method
* and trades a small bit of boilerplate for avoiding the compile time hit of using shapeless for this.
*/
def withContent(newContent: Seq[Span]): Self
/** Extracts the text from the spans of this container, removing
* any formatting or links.
*/
def extractText: String = content.map {
case tc: TextContainer => tc.content
case sc: SpanContainer => sc.extractText
case _ => ""
}.mkString
}
/** A container of list items. Such a container is usually a Block itself.
*/
trait ListContainer extends ElementContainer[ListItem]
/** Common methods for simple span containers (without additional parameters). */
trait SpanContainerCompanion {
type ContainerType
/** Creates an empty instance */
def empty: ContainerType = createSpanContainer(Nil)
/** Create an instance only containing only one or more Text spans */
def apply(text: String, texts: String*): ContainerType = createSpanContainer(
(text +: texts).map(Text(_))
)
/** Create an instance containing one or more spans */
def apply(span: Span, spans: Span*): ContainerType = createSpanContainer(span +: spans.toList)
protected def createSpanContainer(spans: Seq[Span]): ContainerType
}
/** Common methods for simple block containers (without additional parameters). */
trait BlockContainerCompanion extends SpanContainerCompanion {
override def empty: ContainerType = createBlockContainer(Nil)
protected def createSpanContainer(spans: Seq[Span]): ContainerType = createBlockContainer(
spans.map(Paragraph(_))
)
/** Create an instance containing one or more blocks */
def apply(block: Block, blocks: Block*): ContainerType = createBlockContainer(
block +: blocks.toList
)
protected def createBlockContainer(blocks: Seq[Block]): ContainerType
}
private[ast] object FormattedElementString {
private lazy val renderer: Renderer = Renderer.of(AST).build.skipRewritePhase
def render(elem: Element): String = "\n" + renderer.render(elem) + "\n"
}