-
Notifications
You must be signed in to change notification settings - Fork 44
/
resolvers.scala
173 lines (149 loc) · 6.56 KB
/
resolvers.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
168
169
170
171
172
173
package laika.ast
import laika.api.config.ConfigError.InvalidType
import laika.api.config.{ ConfigError, ConfigValue, Key }
import laika.api.config.ConfigValue.{ ASTValue, SimpleValue }
/** Represents a placeholder inline element that needs
* to be resolved in a rewrite step.
* Useful for elements that need access to the
* document, structure, title or configuration before
* being fully resolved.
*/
trait SpanResolver extends Span with Unresolved {
def resolve(cursor: DocumentCursor): Span
def runsIn(phase: RewritePhase): Boolean
}
import laika.parse.SourceFragment
/** Represents a placeholder block element that needs to be resolved in a rewrite step.
* Useful for elements that need access to the document, structure, title
* or configuration before being fully resolved.
*/
trait BlockResolver extends Block with Unresolved {
def resolve(cursor: DocumentCursor): Block
def runsIn(phase: RewritePhase): Boolean
}
/** Represents an element that introduces new context that can be used in substitution references
* in any child element.
*
* Usually used in directive implementations and not contributing to the rendered output itself.
*/
trait ElementScope[E <: Element] extends Unresolved {
def content: E
def context: ConfigValue
}
/** Represents a block element that introduces new context that can be used in substitution references
* in any child element.
*
* Usually used in directive implementations and not contributing to the rendered output itself.
*/
case class BlockScope(
content: Block,
context: ConfigValue,
source: SourceFragment,
options: Options = Options.empty
) extends ElementScope[Block] with Block {
type Self = BlockScope
def withOptions(options: Options): BlockScope = copy(options = options)
lazy val unresolvedMessage: String = s"Unresolved block scope"
}
/** Represents a span element that introduces new context that can be used in substitution references
* in any child element.
*
* Usually used in directive implementations and not contributing to the rendered output itself.
*/
case class SpanScope(
content: Span,
context: ConfigValue,
source: SourceFragment,
options: Options = Options.empty
) extends ElementScope[Span] with Span {
type Self = SpanScope
def withOptions(options: Options): SpanScope = copy(options = options)
lazy val unresolvedMessage: String = s"Unresolved span scope"
}
/** Represents a template span element that introduces new context that can be used in substitution references
* in any child element.
*
* Usually used in directive implementations and not contributing to the rendered output itself.
*/
case class TemplateScope(
content: TemplateSpan,
context: ConfigValue,
source: SourceFragment,
options: Options = Options.empty
) extends ElementScope[TemplateSpan] with TemplateSpan {
type Self = TemplateScope
def withOptions(options: Options): TemplateScope = copy(options = options)
lazy val unresolvedMessage: String = s"Unresolved template scope"
}
/** Represents a reference to a value from the context
* of the current document. The `ref` attribute
* is a simple path expression in dot notation
* for accessing properties of an object (or keys
* of a Map).
*
* The root elements accessible to such a reference are:
*
* - `document`: the current document with all of its public properties
* - `parent`: the parent tree of the current document
* - `root`: the root tree
* - `config`: all configuration values for the current document,
* including those inherited from parent trees
*/
abstract class ContextReference[T <: Span](ref: Key, source: SourceFragment) extends SpanResolver {
protected def missing: InvalidSpan = InvalidSpan(s"Missing required reference: '$ref'", source)
protected def invalid(cError: ConfigError): InvalidSpan =
InvalidSpan(s"Error resolving reference: '$ref': ${cError.message}", source)
protected def invalidType(value: ConfigValue): InvalidSpan = InvalidSpan(
s"Error resolving reference: '$ref': " +
InvalidType("AST Element or Simple Value", value).message,
source
)
}
/** A context reference specifically for use in template documents.
*/
case class TemplateContextReference(
ref: Key,
required: Boolean,
source: SourceFragment,
options: Options = Options.empty
) extends ContextReference[TemplateSpan](ref, source) with TemplateSpan {
type Self = TemplateContextReference
def resolve(cursor: DocumentCursor): Span = cursor.resolveReference(ref) match {
case Right(Some(ASTValue(s: TemplateSpan))) => s
case Right(Some(ASTValue(RootElement(content, _)))) => EmbeddedRoot(content)
case Right(Some(ASTValue(e: Element))) => TemplateElement(e)
case Right(Some(simple: SimpleValue)) => TemplateString(simple.render)
case Right(None) if !required => TemplateString("")
case Right(None) => TemplateElement(missing)
case Right(Some(unsupported)) => TemplateElement(invalidType(unsupported))
case Left(configError) => TemplateElement(invalid(configError))
}
def withOptions(options: Options): TemplateContextReference = copy(options = options)
def runsIn(phase: RewritePhase): Boolean = phase.isInstanceOf[RewritePhase.Render]
lazy val unresolvedMessage: String =
s"Unresolved template context reference with key '${ref.toString}'"
}
/** A context reference specifically for use in markup documents.
*/
case class MarkupContextReference(
ref: Key,
required: Boolean,
source: SourceFragment,
options: Options = Options.empty
) extends ContextReference[Span](ref, source) {
type Self = MarkupContextReference
def resolve(cursor: DocumentCursor): Span = cursor.resolveReference(ref) match {
case Right(Some(ASTValue(s: Span))) => s
case Right(Some(ASTValue(e: Element))) => TemplateElement(e)
case Right(Some(simple: SimpleValue)) => Text(simple.render)
case Right(None) if !required => Text("")
case Right(None) => missing
case Right(Some(unsupported)) => invalidType(unsupported)
case Left(configError) => invalid(configError)
}
def withOptions(options: Options): MarkupContextReference = copy(options = options)
def runsIn(phase: RewritePhase): Boolean =
phase.isInstanceOf[RewritePhase.Render] // TODO - test earlier phases
lazy val unresolvedMessage: String =
s"Unresolved markup context reference with key '${ref.toString}'"
}