Templates/Parts now have access to an EmbeddableText object.
This commit is contained in:
parent
a1f1871d1a
commit
34d9cd52b0
@ -7,6 +7,9 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.jetbrains/annotations
|
||||
api 'org.jetbrains:annotations:24.0.0'
|
||||
|
||||
/**
|
||||
* Logging
|
||||
*/
|
||||
|
@ -69,21 +69,14 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
|
||||
}
|
||||
logger.debug('found template: {}', template)
|
||||
|
||||
// Render the text (i.e., transform text to html)
|
||||
def textRenderResult = it.type.renderer.render(it, globals)
|
||||
String renderedText
|
||||
if (textRenderResult.v1.size() > 0) {
|
||||
logger.debug('diagnostics for rendering {}: {}', it.path, textRenderResult.v1)
|
||||
diagnostics.addAll(textRenderResult.v1)
|
||||
logger.trace(exit, '')
|
||||
return
|
||||
} else {
|
||||
renderedText = textRenderResult.v2
|
||||
logger.debug('renderedText: {}', renderedText)
|
||||
}
|
||||
|
||||
// Render the template using the result of rendering the text earlier
|
||||
def templateRenderResult = template.type.renderer.render(template, frontMatter, renderedText, parts, globals)
|
||||
def templateRenderResult = template.type.renderer.render(
|
||||
template,
|
||||
frontMatter,
|
||||
it,
|
||||
parts,
|
||||
globals
|
||||
)
|
||||
String renderedTemplate
|
||||
if (templateRenderResult.v1.size() > 0) {
|
||||
diagnostics.addAll(templateRenderResult.v1)
|
||||
|
@ -1,8 +1,10 @@
|
||||
package com.jessebrault.ssg.part
|
||||
|
||||
import com.jessebrault.ssg.text.EmbeddableText
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.TupleConstructor
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck
|
||||
@ -13,8 +15,16 @@ class EmbeddablePart {
|
||||
private final Map globals
|
||||
private final Closure onDiagnostics
|
||||
|
||||
@Nullable
|
||||
private final EmbeddableText text
|
||||
|
||||
String render(Map binding = [:]) {
|
||||
def result = part.type.renderer.render(this.part, binding, this.globals)
|
||||
def result = part.type.renderer.render(
|
||||
this.part,
|
||||
binding,
|
||||
this.globals,
|
||||
this.text
|
||||
)
|
||||
if (result.v1.size() > 0) {
|
||||
this.onDiagnostics.call(result.v1)
|
||||
''
|
||||
|
@ -1,18 +1,26 @@
|
||||
package com.jessebrault.ssg.part
|
||||
|
||||
import com.jessebrault.ssg.text.EmbeddableText
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
@NullCheck
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
class EmbeddablePartsMap {
|
||||
|
||||
@Delegate
|
||||
private final Map<String, EmbeddablePart> partsMap = [:]
|
||||
|
||||
EmbeddablePartsMap(Collection<Part> parts, Map globals, Closure onDiagnostics) {
|
||||
EmbeddablePartsMap(
|
||||
Collection<Part> parts,
|
||||
Map globals,
|
||||
Closure onDiagnostics,
|
||||
@Nullable EmbeddableText text = null
|
||||
) {
|
||||
Objects.requireNonNull(parts)
|
||||
Objects.requireNonNull(globals)
|
||||
Objects.requireNonNull(onDiagnostics)
|
||||
parts.each {
|
||||
this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics))
|
||||
this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics, text))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
package com.jessebrault.ssg.part
|
||||
|
||||
import com.jessebrault.ssg.Diagnostic
|
||||
import com.jessebrault.ssg.text.EmbeddableText
|
||||
import groovy.text.GStringTemplateEngine
|
||||
import groovy.text.TemplateEngine
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
@EqualsAndHashCode
|
||||
class GspPartRenderer implements PartRenderer {
|
||||
@ -11,15 +13,27 @@ class GspPartRenderer implements PartRenderer {
|
||||
private static final TemplateEngine engine = new GStringTemplateEngine()
|
||||
|
||||
@Override
|
||||
Tuple2<Collection<Diagnostic>, String> render(Part part, Map binding, Map globals) {
|
||||
Tuple2<Collection<Diagnostic>, String> render(
|
||||
Part part,
|
||||
Map binding,
|
||||
Map globals,
|
||||
@Nullable EmbeddableText text = null
|
||||
) {
|
||||
Objects.requireNonNull(part)
|
||||
Objects.requireNonNull(binding)
|
||||
Objects.requireNonNull(globals)
|
||||
try {
|
||||
def result = engine.createTemplate(part.text).make([
|
||||
binding: binding,
|
||||
globals: globals
|
||||
globals: globals,
|
||||
text: text
|
||||
])
|
||||
new Tuple2<>([], result.toString())
|
||||
} catch (Exception e) {
|
||||
new Tuple2<>([new Diagnostic("An exception occurred while rendering part ${ part.path }:\n${ e }", e)], '')
|
||||
new Tuple2<>(
|
||||
[new Diagnostic("An exception occurred while rendering part ${ part.path }:\n${ e }", e)],
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,16 @@
|
||||
package com.jessebrault.ssg.part
|
||||
|
||||
import com.jessebrault.ssg.Diagnostic
|
||||
import com.jessebrault.ssg.text.EmbeddableText
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
interface PartRenderer {
|
||||
Tuple2<Collection<Diagnostic>, String> render(Part part, Map binding, Map globals)
|
||||
|
||||
Tuple2<Collection<Diagnostic>, String> render(
|
||||
Part part,
|
||||
Map binding,
|
||||
Map globals,
|
||||
@Nullable EmbeddableText text
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package com.jessebrault.ssg.template
|
||||
import com.jessebrault.ssg.Diagnostic
|
||||
import com.jessebrault.ssg.part.EmbeddablePartsMap
|
||||
import com.jessebrault.ssg.part.Part
|
||||
import com.jessebrault.ssg.text.EmbeddableText
|
||||
import com.jessebrault.ssg.text.FrontMatter
|
||||
import com.jessebrault.ssg.text.Text
|
||||
import groovy.text.GStringTemplateEngine
|
||||
import groovy.text.TemplateEngine
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
@ -19,19 +21,21 @@ class GspTemplateRenderer implements TemplateRenderer {
|
||||
Tuple2<Collection<Diagnostic>, String> render(
|
||||
Template template,
|
||||
FrontMatter frontMatter,
|
||||
String text,
|
||||
Text text,
|
||||
Collection<Part> parts,
|
||||
Map globals
|
||||
) {
|
||||
try {
|
||||
Collection<Diagnostic> diagnostics = []
|
||||
def onDiagnostics = { Collection<Diagnostic> partDiagnostics ->
|
||||
diagnostics.addAll(partDiagnostics)
|
||||
}
|
||||
def embeddableText = new EmbeddableText(text, globals, onDiagnostics)
|
||||
def result = engine.createTemplate(template.text).make([
|
||||
frontMatter: frontMatter,
|
||||
globals: globals,
|
||||
parts: new EmbeddablePartsMap(parts, globals, { Collection<Diagnostic> partDiagnostics ->
|
||||
diagnostics.addAll(partDiagnostics)
|
||||
}),
|
||||
text: text
|
||||
parts: new EmbeddablePartsMap(parts, globals, onDiagnostics, embeddableText),
|
||||
text: embeddableText
|
||||
])
|
||||
new Tuple2<>(diagnostics, result.toString())
|
||||
} catch (Exception e) {
|
||||
|
@ -3,13 +3,17 @@ package com.jessebrault.ssg.template
|
||||
import com.jessebrault.ssg.Diagnostic
|
||||
import com.jessebrault.ssg.part.Part
|
||||
import com.jessebrault.ssg.text.FrontMatter
|
||||
import com.jessebrault.ssg.text.Text
|
||||
|
||||
interface TemplateRenderer {
|
||||
|
||||
/**
|
||||
* TODO: get rid of frontMatter param since we can obtain it from the text
|
||||
*/
|
||||
Tuple2<Collection<Diagnostic>, String> render(
|
||||
Template template,
|
||||
FrontMatter frontMatter,
|
||||
String text,
|
||||
Text text,
|
||||
Collection<Part> parts,
|
||||
Map globals
|
||||
)
|
||||
|
@ -61,7 +61,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
|
||||
@Test
|
||||
void simple() {
|
||||
new File(this.textsDir, 'test.md').write('---\ntemplate: test.gsp\n---\n**Hello, World!**')
|
||||
new File(this.templatesDir, 'test.gsp').write('<%= text %>')
|
||||
new File(this.templatesDir, 'test.gsp').write('<%= text.render() %>')
|
||||
|
||||
def result = this.ssg.generate(this.build)
|
||||
|
||||
@ -81,7 +81,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
new File(this.templatesDir, 'nested.gsp').write('<%= text %>')
|
||||
new File(this.templatesDir, 'nested.gsp').write('<%= text.render() %>')
|
||||
|
||||
def result = this.ssg.generate(this.build)
|
||||
|
||||
|
@ -1,18 +1,34 @@
|
||||
package com.jessebrault.ssg.part
|
||||
|
||||
import com.jessebrault.ssg.Diagnostic
|
||||
import com.jessebrault.ssg.text.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue
|
||||
import static org.mockito.ArgumentMatchers.any
|
||||
import static org.mockito.Mockito.mock
|
||||
import static org.mockito.Mockito.when
|
||||
|
||||
class GspPartRendererTests {
|
||||
|
||||
/**
|
||||
* TODO: move to a fixture
|
||||
*/
|
||||
private static Text mockRenderableText(String text) {
|
||||
def textRenderer = mock(TextRenderer)
|
||||
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
|
||||
def frontMatterGetter = mock(FrontMatterGetter)
|
||||
def excerptGetter = mock(ExcerptGetter)
|
||||
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
|
||||
}
|
||||
|
||||
private final PartRenderer renderer = new GspPartRenderer()
|
||||
|
||||
@Test
|
||||
void rendersWithNoBindingOrGlobals() {
|
||||
def part = new Part('', null, 'Hello, World!')
|
||||
def r = this.renderer.render(part, [:], [:])
|
||||
def r = this.renderer.render(part, [:], [:], null)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
@ -20,7 +36,7 @@ class GspPartRendererTests {
|
||||
@Test
|
||||
void rendersWithBinding() {
|
||||
def part = new Part('', null, "<%= binding['greeting'] %>")
|
||||
def r = this.renderer.render(part, [greeting: 'Hello, World!'], [:])
|
||||
def r = this.renderer.render(part, [greeting: 'Hello, World!'], [:], null)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
@ -28,9 +44,28 @@ class GspPartRendererTests {
|
||||
@Test
|
||||
void rendersWithGlobals() {
|
||||
def part = new Part(null, null, "<%= globals['greeting'] %>")
|
||||
def r = this.renderer.render(part, [:], [greeting: 'Hello, World!'])
|
||||
def r = this.renderer.render(part, [:], [greeting: 'Hello, World!'], null)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
|
||||
@Test
|
||||
void textAvailable() {
|
||||
def part = new Part('', null, '<%= text.render() %>')
|
||||
def textDiagnostics = []
|
||||
def r = this.renderer.render(
|
||||
part,
|
||||
[:],
|
||||
[:],
|
||||
new EmbeddableText(
|
||||
mockRenderableText('Hello, World!'),
|
||||
[:],
|
||||
{ Collection<Diagnostic> diagnostics -> textDiagnostics.addAll(diagnostics) }
|
||||
)
|
||||
)
|
||||
assertTrue(textDiagnostics.isEmpty())
|
||||
assertTrue(r.v1.isEmpty())
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,11 +3,7 @@ package com.jessebrault.ssg.specialpage
|
||||
import com.jessebrault.ssg.part.Part
|
||||
import com.jessebrault.ssg.part.PartRenderer
|
||||
import com.jessebrault.ssg.part.PartType
|
||||
import com.jessebrault.ssg.text.FrontMatterGetter
|
||||
import com.jessebrault.ssg.text.MarkdownExcerptGetter
|
||||
import com.jessebrault.ssg.text.Text
|
||||
import com.jessebrault.ssg.text.TextRenderer
|
||||
import com.jessebrault.ssg.text.TextType
|
||||
import com.jessebrault.ssg.text.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.Mock
|
||||
@ -35,7 +31,7 @@ class GspSpecialPageRendererTests {
|
||||
|
||||
@Test
|
||||
void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) {
|
||||
when(partRenderer.render(any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
when(partRenderer.render(any(), any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
def partType = new PartType([], partRenderer)
|
||||
def part = new Part('test', partType , '')
|
||||
|
||||
@ -47,11 +43,16 @@ class GspSpecialPageRendererTests {
|
||||
|
||||
@Test
|
||||
void rendersPartWithBinding(@Mock PartRenderer partRenderer) {
|
||||
when(partRenderer.render(any(), argThat { Map m -> m.get('greeting') == 'Hello, World!'}, any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
when(partRenderer.render(any(), argThat { Map m -> m.get('greeting') == 'Hello, World!'}, any(), any()))
|
||||
.thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
def partType = new PartType([], partRenderer)
|
||||
def part = new Part('test', partType, '')
|
||||
|
||||
def specialPage = new SpecialPage("<%= parts['test'].render([greeting: 'Hello, World!'])", null, null)
|
||||
def specialPage = new SpecialPage(
|
||||
"<%= parts['test'].render([greeting: 'Hello, World!'])",
|
||||
null,
|
||||
null
|
||||
)
|
||||
def r = this.renderer.render(specialPage, [], [part], [:])
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
@ -59,11 +60,16 @@ class GspSpecialPageRendererTests {
|
||||
|
||||
@Test
|
||||
void rendersText(@Mock TextRenderer textRenderer, @Mock FrontMatterGetter frontMatterGetter) {
|
||||
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], '<p><strong>Hello, World!</strong></p>\n'))
|
||||
when(textRenderer.render(any(), any()))
|
||||
.thenReturn(new Tuple2<>([], '<p><strong>Hello, World!</strong></p>\n'))
|
||||
def textType = new TextType([], textRenderer, frontMatterGetter, new MarkdownExcerptGetter())
|
||||
def text = new Text('', 'test', textType)
|
||||
|
||||
def specialPage = new SpecialPage("<%= texts.find { it.path == 'test' }.render() %>", null, null)
|
||||
def specialPage = new SpecialPage(
|
||||
"<%= texts.find { it.path == 'test' }.render() %>",
|
||||
null,
|
||||
null
|
||||
)
|
||||
def r = this.renderer.render(specialPage, [text], [], [:])
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('<p><strong>Hello, World!</strong></p>\n', r.v2)
|
||||
|
@ -3,7 +3,12 @@ package com.jessebrault.ssg.template
|
||||
import com.jessebrault.ssg.part.Part
|
||||
import com.jessebrault.ssg.part.PartRenderer
|
||||
import com.jessebrault.ssg.part.PartType
|
||||
import com.jessebrault.ssg.text.ExcerptGetter
|
||||
import com.jessebrault.ssg.text.FrontMatter
|
||||
import com.jessebrault.ssg.text.FrontMatterGetter
|
||||
import com.jessebrault.ssg.text.Text
|
||||
import com.jessebrault.ssg.text.TextRenderer
|
||||
import com.jessebrault.ssg.text.TextType
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.Mock
|
||||
@ -13,11 +18,33 @@ import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue
|
||||
import static org.mockito.ArgumentMatchers.any
|
||||
import static org.mockito.ArgumentMatchers.argThat
|
||||
import static org.mockito.Mockito.mock
|
||||
import static org.mockito.Mockito.when
|
||||
|
||||
@ExtendWith(MockitoExtension)
|
||||
class GspTemplateRendererTests {
|
||||
|
||||
/**
|
||||
* TODO: move to a fixture
|
||||
*/
|
||||
private static Text mockBlankText() {
|
||||
def textRenderer = mock(TextRenderer)
|
||||
def frontMatterGetter = mock(FrontMatterGetter)
|
||||
def excerptGetter = mock(ExcerptGetter)
|
||||
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: move to a fixture
|
||||
*/
|
||||
private static Text mockRenderableText(String text) {
|
||||
def textRenderer = mock(TextRenderer)
|
||||
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
|
||||
def frontMatterGetter = mock(FrontMatterGetter)
|
||||
def excerptGetter = mock(ExcerptGetter)
|
||||
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
|
||||
}
|
||||
|
||||
private final TemplateRenderer renderer = new GspTemplateRenderer()
|
||||
|
||||
@Test
|
||||
@ -28,11 +55,17 @@ class GspTemplateRendererTests {
|
||||
null
|
||||
)
|
||||
|
||||
when(partRenderer.render(any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
when(partRenderer.render(any(), any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
def partType = new PartType([], partRenderer)
|
||||
def part = new Part('test', partType, null)
|
||||
|
||||
def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [part], [:])
|
||||
def r = this.renderer.render(
|
||||
template,
|
||||
new FrontMatter(null, [:]),
|
||||
mockBlankText(),
|
||||
[part],
|
||||
[:]
|
||||
)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
@ -45,11 +78,18 @@ class GspTemplateRendererTests {
|
||||
null
|
||||
)
|
||||
|
||||
when(partRenderer.render(any(), argThat { Map m -> m.get('person') == 'World' }, any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
when(partRenderer.render(any(), argThat { Map m -> m.get('person') == 'World' }, any(), any()))
|
||||
.thenReturn(new Tuple2<>([], 'Hello, World!'))
|
||||
def partType = new PartType([], partRenderer)
|
||||
def part = new Part('greeting', partType, null)
|
||||
|
||||
def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [part], [:])
|
||||
def r = this.renderer.render(
|
||||
template,
|
||||
new FrontMatter(null, [:]),
|
||||
mockBlankText(),
|
||||
[part],
|
||||
[:]
|
||||
)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
@ -57,7 +97,13 @@ class GspTemplateRendererTests {
|
||||
@Test
|
||||
void rendersFrontMatter() {
|
||||
def template = new Template("<%= frontMatter['title'] %>", null, null)
|
||||
def r = this.renderer.render(template, new FrontMatter(null, [title: ['Hello!']]), '', [], [:])
|
||||
def r = this.renderer.render(
|
||||
template,
|
||||
new FrontMatter(null, [title: ['Hello!']]),
|
||||
mockBlankText(),
|
||||
[],
|
||||
[:]
|
||||
)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello!', r.v2)
|
||||
}
|
||||
@ -65,15 +111,27 @@ class GspTemplateRendererTests {
|
||||
@Test
|
||||
void rendersGlobal() {
|
||||
def template = new Template("<%= globals['test'] %>", null, null)
|
||||
def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [], [test: 'Hello, World!'])
|
||||
def r = this.renderer.render(
|
||||
template,
|
||||
new FrontMatter(null, [:]),
|
||||
mockBlankText(),
|
||||
[],
|
||||
[test: 'Hello, World!']
|
||||
)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
|
||||
@Test
|
||||
void rendersText() {
|
||||
def template = new Template('<%= text %>', null, null)
|
||||
def r = this.renderer.render(template, new FrontMatter(null, [:]), 'Hello, World!', [], [:])
|
||||
void textAvailableToRender() {
|
||||
def template = new Template('<%= text.render() %>', null, null)
|
||||
def r = this.renderer.render(
|
||||
template,
|
||||
new FrontMatter(null, [:]),
|
||||
mockRenderableText('Hello, World!'),
|
||||
[],
|
||||
[:]
|
||||
)
|
||||
assertTrue(r.v1.size() == 0)
|
||||
assertEquals('Hello, World!', r.v2)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user