Skip to content

Commit 51d0502

Browse files
committed
feat: let subdocuments edit their own document metadata
1 parent 09cc967 commit 51d0502

File tree

10 files changed

+122
-36
lines changed

10 files changed

+122
-36
lines changed

quarkdown-core/src/main/kotlin/com/quarkdown/core/context/BaseContext.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,5 @@ open class BaseContext(
106106
?: throw LocalizationKeyNotFoundException(tableName, locale, key)
107107
}
108108

109-
override fun fork(subdocument: Subdocument): ScopeContext =
110-
throw UnsupportedOperationException("Forking is not supported in BaseContext")
109+
override fun fork(): ScopeContext = throw UnsupportedOperationException("Forking is not supported in BaseContext")
111110
}

quarkdown-core/src/main/kotlin/com/quarkdown/core/context/Context.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,9 @@ interface Context {
158158
): String
159159

160160
/**
161-
* @param subdocument optional subdocument to fork the context for
162-
* @return a new scope context, forked from this context, with the same base inherited properties
161+
* @return a new scope context, forked from this context, that shares several base properties
163162
*/
164-
fun fork(subdocument: Subdocument = this.subdocument): ScopeContext
163+
fun fork(): ScopeContext
165164
}
166165

167166
/**

quarkdown-core/src/main/kotlin/com/quarkdown/core/context/MutableContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ open class MutableContext(
7272
it.onComplete = { attributes.functionCalls.remove(call) }
7373
}
7474

75-
override fun fork(subdocument: Subdocument): ScopeContext = ScopeContext(parent = this, subdocument)
75+
override fun fork(): ScopeContext = ScopeContext(parent = this)
7676

7777
/**
7878
* Loads a loadable library by name and registers it in the context.
Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,18 @@
11
package com.quarkdown.core.context
22

3-
import com.quarkdown.core.document.sub.Subdocument
4-
import com.quarkdown.core.function.Function
5-
import com.quarkdown.core.pipeline.Pipeline
6-
73
/**
84
* A context that is the result of a fork from an original parent [Context].
95
* All properties are inherited from it, but not all, such as libraries, are shared mutably.
106
* @param parent context this scope was forked from
117
* @param subdocument the subdocument this context is processing
8+
* @see SubdocumentContext to see what's inherited from the parent context
129
*/
1310
open class ScopeContext(
14-
override val parent: MutableContext,
15-
subdocument: Subdocument = parent.subdocument,
16-
) : MutableContext(
17-
flavor = parent.flavor,
18-
libraries = emptySet(),
19-
subdocument = subdocument,
20-
),
21-
ChildContext<MutableContext> {
22-
override val attachedPipeline: Pipeline?
23-
get() = super.attachedPipeline ?: parent.attachedPipeline
24-
11+
parent: MutableContext,
12+
) : SubdocumentContext(
13+
parent = parent,
14+
subdocument = parent.subdocument,
15+
) {
16+
// A scope context shares the parent's document info.
2517
override var documentInfo by parent::documentInfo
26-
override val options by parent::options
27-
override val attributes by parent::attributes
28-
override val loadableLibraries by parent::loadableLibraries
29-
override val localizationTables by parent::localizationTables
30-
override val mediaStorage by parent::mediaStorage
31-
override var subdocumentGraph by parent::subdocumentGraph
32-
33-
/**
34-
* If no matching function is found among this [ScopeContext]'s own [libraries],
35-
* [parent]'s libraries are scanned.
36-
* @see Context.getFunctionByName
37-
*/
38-
override fun getFunctionByName(name: String): Function<*>? = super.getFunctionByName(name) ?: parent.getFunctionByName(name)
3918
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.quarkdown.core.context
2+
3+
import com.quarkdown.core.document.DocumentInfo
4+
import com.quarkdown.core.document.sub.Subdocument
5+
import com.quarkdown.core.function.Function
6+
import com.quarkdown.core.pipeline.Pipeline
7+
8+
/**
9+
* A context that is the result of a fork from an original parent [Context].
10+
* All properties are inherited from it, but not all, such as libraries, are shared mutably.
11+
*
12+
* This is mainly designed to be forked for subdocuments, where a new context is needed to process them.
13+
* Each subdocument context shares most properties with its parent context, but maintains its own state for certain aspects like document info.
14+
*
15+
* [ScopeContext] inherits from this class, and shares the parent's document info instead.
16+
*
17+
* @param parent context this scope was forked from
18+
* @param subdocument the subdocument this context is processing
19+
*/
20+
open class SubdocumentContext(
21+
override val parent: MutableContext,
22+
subdocument: Subdocument,
23+
) : MutableContext(
24+
flavor = parent.flavor,
25+
libraries = emptySet(),
26+
subdocument = subdocument,
27+
),
28+
ChildContext<MutableContext> {
29+
override val attachedPipeline: Pipeline?
30+
get() = super.attachedPipeline ?: parent.attachedPipeline
31+
32+
// A subdocument inherits the parent's document info, but changes to it are local to this subdocument.
33+
override var documentInfo: DocumentInfo = parent.documentInfo
34+
35+
override val options by parent::options
36+
override val attributes by parent::attributes
37+
override val loadableLibraries by parent::loadableLibraries
38+
override val localizationTables by parent::localizationTables
39+
override val mediaStorage by parent::mediaStorage
40+
override var subdocumentGraph by parent::subdocumentGraph
41+
42+
/**
43+
* If no matching function is found among this [SubdocumentContext]'s own [libraries],
44+
* [parent]'s libraries are scanned.
45+
* @see Context.getFunctionByName
46+
*/
47+
override fun getFunctionByName(name: String): Function<*>? = super.getFunctionByName(name) ?: parent.getFunctionByName(name)
48+
}

quarkdown-core/src/main/kotlin/com/quarkdown/core/pipeline/stages/ResourceGenerationStage.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.quarkdown.core.pipeline.stages
22

33
import com.quarkdown.core.context.MutableContext
4+
import com.quarkdown.core.context.SubdocumentContext
45
import com.quarkdown.core.document.sub.Subdocument
56
import com.quarkdown.core.pipeline.Pipeline
67
import com.quarkdown.core.pipeline.PipelineHooks
@@ -52,7 +53,7 @@ class ResourceGenerationStage(
5253
.asSequence()
5354
.filterIsInstance<Subdocument.Resource>()
5455
.flatMap { nextSubdocument ->
55-
val subContext = context.fork(nextSubdocument)
56+
val subContext = SubdocumentContext(parent = context, subdocument = nextSubdocument)
5657
pipeline.copy(subContext).executeUnwrapped(nextSubdocument.content)
5758
}.toSet()
5859
}

quarkdown-test/src/test/kotlin/com/quarkdown/test/DocumentTest.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,18 @@ class DocumentTest {
167167
)
168168
}
169169
}
170+
171+
@Test
172+
fun `document info modification from scope`() {
173+
execute(
174+
"""
175+
.docname {Original Name}
176+
177+
.if {yes}
178+
.docname {Modified Name}
179+
""".trimIndent(),
180+
) {
181+
assertEquals("Modified Name", documentInfo.name)
182+
}
183+
}
170184
}

quarkdown-test/src/test/kotlin/com/quarkdown/test/EcosystemTest.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ class EcosystemTest {
3838
}
3939
}
4040

41+
@Test
42+
fun `modify document info from included source`() {
43+
execute(
44+
"""
45+
.include {include/document-info-modification.md}
46+
""".trimIndent(),
47+
) {
48+
assertEquals("Modified Title", documentInfo.name)
49+
assertEquals("it", documentInfo.locale?.shortTag)
50+
}
51+
}
52+
4153
@Test
4254
fun `include function from source`() {
4355
execute(

quarkdown-test/src/test/kotlin/com/quarkdown/test/SubdocumentTest.kt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ class SubdocumentTest {
3636
private val simpleSubdoc = subdoc("subdoc1", content = "Content")
3737
private val referenceToParentSubdoc = subdoc("subdoc2", content = ".$NON_EXISTENT_FUNCTION")
3838
private val definitionSubdoc = subdoc("subdoc3", content = ".function {$NON_EXISTENT_FUNCTION}\n\thello")
39-
private val thirdPartySubdoc = subdoc("subdoc3", content = ".mermaid\n\tgraph TD\n\t\tA-->B")
39+
private val thirdPartySubdoc = subdoc("subdoc4", content = ".mermaid\n\tgraph TD\n\t\tA-->B")
40+
private val echoDocumentNameSubdoc = subdoc("subdoc5", content = ".docname")
41+
private val modifyAndEchoDocumentNameSubdoc = subdoc("subdoc6", content = ".docname {Changed name}\n\n.docname")
4042

4143
private fun getResource(
4244
group: OutputResource?,
@@ -125,6 +127,36 @@ class SubdocumentTest {
125127
) {}
126128
}
127129

130+
@Test
131+
fun `subdocument inherits parent's document info`() {
132+
execute(
133+
".docname {My doc}",
134+
subdocumentGraph = { it.addVertex(echoDocumentNameSubdoc).addEdge(Subdocument.Root, echoDocumentNameSubdoc) },
135+
outputResourceHook = { group ->
136+
val subdocResource = getResource(group, echoDocumentNameSubdoc, this)
137+
assertEquals("My doc", group?.name)
138+
assertEquals("My doc", documentInfo.name)
139+
assertContains(subdocResource.content, "<title>My doc</title>")
140+
},
141+
) {}
142+
}
143+
144+
@Test
145+
fun `subdocument should not share document info modifications with parent`() {
146+
execute(
147+
".docname {Parent doc}",
148+
subdocumentGraph = { it.addVertex(modifyAndEchoDocumentNameSubdoc).addEdge(Subdocument.Root, modifyAndEchoDocumentNameSubdoc) },
149+
outputResourceHook = { group ->
150+
val mainResource = getResource(group, Subdocument.Root, this)
151+
val subdocResource = getResource(group, modifyAndEchoDocumentNameSubdoc, this)
152+
assertEquals("Parent doc", group?.name)
153+
assertEquals("Parent doc", documentInfo.name)
154+
assertContains(mainResource.content, "<title>Parent doc</title>")
155+
assertContains(subdocResource.content, "<title>Changed name</title>")
156+
},
157+
) {}
158+
}
159+
128160
@Test
129161
fun `simple subdocument from file`() {
130162
arrayOf(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.docname {Modified Title}
2+
.doclang {it}

0 commit comments

Comments
 (0)